Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-25 08:29:11

0001 """
0002 Workflow-specific views for the SWF monitor application.
0003 """
0004 
0005 from django.shortcuts import render, get_object_or_404
0006 from django.http import JsonResponse
0007 from django.contrib.auth.decorators import login_required
0008 from django.urls import reverse
0009 from django.db.models import Count
0010 from django.utils import timezone
0011 
0012 from .models import StfFile
0013 from .workflow_models import WorkflowDefinition, WorkflowExecution
0014 
0015 
0016 @login_required
0017 def workflows_home(request):
0018     """Workflows landing page with links to different workflow views."""
0019     return render(request, 'monitor_app/workflows_home.html')
0020 
0021 
0022 @login_required
0023 def workflow_definitions_list(request):
0024     """
0025     Professional workflow definitions list view using server-side DataTables.
0026     """
0027     # Get filter parameters (for initial state)
0028     workflow_name = request.GET.get('workflow_name')
0029     workflow_type = request.GET.get('workflow_type')
0030     created_by = request.GET.get('created_by')
0031 
0032     # Get unique values for filter links
0033     workflow_names = WorkflowDefinition.objects.values_list('workflow_name', flat=True).distinct().order_by('workflow_name')
0034     workflow_types = WorkflowDefinition.objects.values_list('workflow_type', flat=True).distinct().order_by('workflow_type')
0035     created_bys = WorkflowDefinition.objects.values_list('created_by', flat=True).distinct().order_by('created_by')
0036 
0037     columns = [
0038         {'name': 'workflow_name', 'title': 'Workflow Name', 'orderable': True},
0039         {'name': 'version', 'title': 'Version', 'orderable': True},
0040         {'name': 'workflow_type', 'title': 'Type', 'orderable': True},
0041         {'name': 'created_by', 'title': 'Created By', 'orderable': True},
0042         {'name': 'created_at', 'title': 'Created', 'orderable': True},
0043         {'name': 'execution_count', 'title': 'Executions', 'orderable': True},
0044         {'name': 'actions', 'title': 'Actions', 'orderable': False},
0045     ]
0046 
0047     filter_fields = [
0048         {'name': 'workflow_type', 'label': 'Type', 'type': 'select'},
0049         {'name': 'created_by', 'label': 'Created By', 'type': 'select'},
0050     ]
0051 
0052     context = {
0053         'table_title': 'Workflow Definitions',
0054         'table_description': 'View and manage workflow templates and configurations.',
0055         'ajax_url': reverse('monitor_app:workflow_definitions_datatable_ajax'),
0056         'filter_counts_url': reverse('monitor_app:workflow_definitions_filter_counts'),
0057         'columns': columns,
0058         'filter_fields': filter_fields,
0059         'workflow_names': list(workflow_names),
0060         'workflow_types': list(workflow_types),
0061         'created_bys': list(created_bys),
0062         'selected_workflow_name': workflow_name,
0063         'selected_workflow_type': workflow_type,
0064         'selected_created_by': created_by,
0065     }
0066     return render(request, 'monitor_app/workflow_definitions_list.html', context)
0067 
0068 
0069 def workflow_definitions_datatable_ajax(request):
0070     """AJAX endpoint for server-side DataTables processing of workflow definitions."""
0071     from .utils import DataTablesProcessor, format_datetime
0072 
0073     columns = ['workflow_name', 'version', 'workflow_type', 'created_by', 'created_at', 'execution_count', 'actions']
0074     dt = DataTablesProcessor(request, columns, default_order_column=4, default_order_direction='desc')
0075 
0076     # Build queryset with execution count
0077     queryset = WorkflowDefinition.objects.annotate(
0078         execution_count=Count('executions')
0079     )
0080 
0081     # Apply filters
0082     workflow_name = request.GET.get('workflow_name')
0083     if workflow_name:
0084         queryset = queryset.filter(workflow_name=workflow_name)
0085 
0086     workflow_type = request.GET.get('workflow_type')
0087     if workflow_type:
0088         queryset = queryset.filter(workflow_type=workflow_type)
0089 
0090     created_by = request.GET.get('created_by')
0091     if created_by:
0092         queryset = queryset.filter(created_by=created_by)
0093 
0094     # Get counts and apply search/pagination
0095     records_total = WorkflowDefinition.objects.count()
0096     search_fields = ['workflow_name', 'version', 'workflow_type', 'created_by']
0097     queryset = dt.apply_search(queryset, search_fields)
0098     records_filtered = queryset.count()
0099 
0100     queryset = queryset.order_by(dt.get_order_by())
0101     definitions = dt.apply_pagination(queryset)
0102 
0103     # Format data for DataTables
0104     data = []
0105     for definition in definitions:
0106         data.append([
0107             f'<a href="{reverse("monitor_app:workflow_definition_detail", args=[definition.workflow_name, definition.version])}" class="text-decoration-none">{definition.workflow_name}</a>',
0108             definition.version,
0109             definition.workflow_type,
0110             definition.created_by,
0111             format_datetime(definition.created_at),
0112             definition.execution_count,
0113             f'<a href="{reverse("monitor_app:workflow_definition_detail", args=[definition.workflow_name, definition.version])}" class="btn btn-sm btn-outline-primary">View</a>'
0114         ])
0115 
0116     return dt.create_response(data, records_total, records_filtered)
0117 
0118 
0119 def workflow_definitions_filter_counts(request):
0120     """AJAX endpoint for dynamic filter counts."""
0121     workflow_type_counts = WorkflowDefinition.objects.values('workflow_type').annotate(count=Count('id')).order_by('workflow_type')
0122     created_by_counts = WorkflowDefinition.objects.values('created_by').annotate(count=Count('id')).order_by('created_by')
0123 
0124     return JsonResponse({
0125         'workflow_type': list(workflow_type_counts),
0126         'created_by': list(created_by_counts),
0127     })
0128 
0129 
0130 @login_required
0131 def workflow_executions_list(request):
0132     """
0133     Professional workflow executions list view using server-side DataTables.
0134     """
0135     # Get filter parameters (for initial state)
0136     workflow = request.GET.get('workflow')
0137     status = request.GET.get('status')
0138     executed_by = request.GET.get('executed_by')
0139     namespace = request.GET.get('namespace')
0140 
0141     # Get unique values for filter links
0142     workflows = WorkflowExecution.objects.select_related('workflow_definition').values_list(
0143         'workflow_definition__workflow_name', flat=True
0144     ).distinct().order_by('workflow_definition__workflow_name')
0145 
0146     statuses = WorkflowExecution.objects.values_list('status', flat=True).distinct().order_by('status')
0147     executed_bys = WorkflowExecution.objects.values_list('executed_by', flat=True).distinct().order_by('executed_by')
0148     namespaces = WorkflowExecution.objects.exclude(namespace__isnull=True).exclude(namespace='').values_list(
0149         'namespace', flat=True
0150     ).distinct().order_by('namespace')
0151 
0152     columns = [
0153         {'name': 'execution_id', 'title': 'Execution ID', 'orderable': True},
0154         {'name': 'workflow', 'title': 'Workflow', 'orderable': True},
0155         {'name': 'namespace', 'title': 'Namespace', 'orderable': True},
0156         {'name': 'status', 'title': 'Status', 'orderable': True},
0157         {'name': 'stf_count', 'title': 'STFs', 'orderable': False},
0158         {'name': 'executed_by', 'title': 'Executed By', 'orderable': True},
0159         {'name': 'start_time', 'title': 'Started', 'orderable': True},
0160         {'name': 'duration', 'title': 'Duration', 'orderable': True},
0161         {'name': 'actions', 'title': 'Actions', 'orderable': False},
0162     ]
0163 
0164     filter_fields = [
0165         {'name': 'status', 'label': 'Status', 'type': 'select'},
0166         {'name': 'executed_by', 'label': 'Executed By', 'type': 'select'},
0167     ]
0168 
0169     context = {
0170         'table_title': 'Workflow Executions',
0171         'table_description': 'Monitor workflow execution instances and their status.',
0172         'ajax_url': reverse('monitor_app:workflow_executions_datatable_ajax'),
0173         'filter_counts_url': reverse('monitor_app:workflow_executions_filter_counts'),
0174         'columns': columns,
0175         'filter_fields': filter_fields,
0176         'workflows': list(workflows),
0177         'statuses': list(statuses),
0178         'executed_bys': list(executed_bys),
0179         'namespaces': list(namespaces),
0180         'selected_workflow': workflow,
0181         'selected_status': status,
0182         'selected_executed_by': executed_by,
0183         'selected_namespace': namespace,
0184     }
0185     return render(request, 'monitor_app/workflow_executions_list.html', context)
0186 
0187 
0188 def workflow_executions_datatable_ajax(request):
0189     """AJAX endpoint for server-side DataTables processing of workflow executions."""
0190     from .utils import DataTablesProcessor, format_datetime, format_duration
0191     from .workflow_models import WorkflowMessage
0192 
0193     columns = ['execution_id', 'workflow', 'namespace', 'status', 'stf_count', 'executed_by', 'start_time', 'duration', 'actions']
0194     dt = DataTablesProcessor(request, columns, default_order_column=6, default_order_direction='desc')
0195 
0196     # Build queryset
0197     queryset = WorkflowExecution.objects.select_related('workflow_definition')
0198 
0199     # Apply filters
0200     workflow = request.GET.get('workflow')
0201     if workflow:
0202         queryset = queryset.filter(workflow_definition__workflow_name=workflow)
0203 
0204     status = request.GET.get('status')
0205     if status:
0206         queryset = queryset.filter(status=status)
0207 
0208     executed_by = request.GET.get('executed_by')
0209     if executed_by:
0210         queryset = queryset.filter(executed_by=executed_by)
0211 
0212     namespace = request.GET.get('namespace')
0213     if namespace:
0214         queryset = queryset.filter(namespace=namespace)
0215 
0216     # Get counts and apply search/pagination
0217     records_total = WorkflowExecution.objects.count()
0218     search_fields = ['execution_id', 'workflow_definition__workflow_name', 'status', 'executed_by']
0219     queryset = dt.apply_search(queryset, search_fields)
0220     records_filtered = queryset.count()
0221 
0222     queryset = queryset.order_by(dt.get_order_by())
0223     executions = dt.apply_pagination(queryset)
0224 
0225     # Format data for DataTables
0226     data = []
0227     for execution in executions:
0228         # Calculate duration
0229         if execution.end_time:
0230             duration = execution.end_time - execution.start_time
0231             duration_str = format_duration(duration)
0232         elif execution.status == 'running':
0233             duration = timezone.now() - execution.start_time
0234             duration_str = format_duration(duration, is_ongoing=True)
0235         else:
0236             duration_str = '-'
0237 
0238         # Count actual STF files via run_ids associated with this execution
0239         run_ids = WorkflowMessage.objects.filter(
0240             execution_id=execution.execution_id,
0241             run_id__isnull=False,
0242         ).values_list('run_id', flat=True).distinct()
0243         run_numbers = [int(r) for r in run_ids if r]
0244         stf_count = StfFile.objects.filter(run__run_number__in=run_numbers).count()
0245 
0246         # Format namespace as link
0247         if execution.namespace:
0248             namespace_link = f'<a href="{reverse("monitor_app:namespace_detail", args=[execution.namespace])}">{execution.namespace}</a>'
0249         else:
0250             namespace_link = ''
0251 
0252         data.append([
0253             f'<a href="{reverse("monitor_app:workflow_execution_detail", args=[execution.execution_id])}" class="text-decoration-none">{execution.execution_id}</a>',
0254             f"{execution.workflow_definition.workflow_name} v{execution.workflow_definition.version}",
0255             namespace_link,
0256             execution.status,
0257             str(stf_count),
0258             execution.executed_by,
0259             format_datetime(execution.start_time),
0260             duration_str,
0261             f'<a href="{reverse("monitor_app:workflow_execution_detail", args=[execution.execution_id])}" class="btn btn-sm btn-outline-primary">View</a>'
0262         ])
0263 
0264     return dt.create_response(data, records_total, records_filtered)
0265 
0266 
0267 def workflow_executions_filter_counts(request):
0268     """AJAX endpoint for dynamic filter counts."""
0269     status_counts = WorkflowExecution.objects.values('status').annotate(count=Count('id')).order_by('status')
0270     executed_by_counts = WorkflowExecution.objects.values('executed_by').annotate(count=Count('id')).order_by('executed_by')
0271 
0272     return JsonResponse({
0273         'status': list(status_counts),
0274         'executed_by': list(executed_by_counts),
0275     })
0276 
0277 
0278 @login_required
0279 def namespaces_list(request):
0280     """
0281     Namespace list view using server-side DataTables.
0282     Primary source is the Namespace model with activity counts.
0283     """
0284     columns = [
0285         {'name': 'name', 'title': 'Namespace', 'orderable': True},
0286         {'name': 'owner', 'title': 'Owner', 'orderable': True},
0287         {'name': 'description', 'title': 'Description', 'orderable': False},
0288         {'name': 'agent_count', 'title': 'Agents', 'orderable': True},
0289         {'name': 'execution_count', 'title': 'Executions', 'orderable': True},
0290         {'name': 'message_count', 'title': 'Messages', 'orderable': True},
0291         {'name': 'updated_at', 'title': 'Modified', 'orderable': True},
0292     ]
0293 
0294     context = {
0295         'table_title': 'Namespaces',
0296         'table_description': 'View registered namespaces and their associated agents, executions, and messages.',
0297         'ajax_url': reverse('monitor_app:namespaces_datatable_ajax'),
0298         'columns': columns,
0299     }
0300     return render(request, 'monitor_app/namespaces_list.html', context)
0301 
0302 
0303 def namespaces_datatable_ajax(request):
0304     """AJAX endpoint for server-side DataTables processing of namespaces."""
0305     from django.db.models import Count, Q
0306     from .utils import DataTablesProcessor
0307     from .models import SystemAgent
0308     from .workflow_models import WorkflowMessage, Namespace
0309 
0310     columns = ['name', 'owner', 'description', 'agent_count', 'execution_count', 'message_count', 'updated_at']
0311     dt = DataTablesProcessor(request, columns, default_order_column=0, default_order_direction='asc')
0312 
0313     # Query Namespace model
0314     queryset = Namespace.objects.all()
0315     records_total = queryset.count()
0316 
0317     # Apply search filter
0318     if dt.search_value:
0319         queryset = queryset.filter(
0320             Q(name__icontains=dt.search_value) |
0321             Q(owner__icontains=dt.search_value) |
0322             Q(description__icontains=dt.search_value)
0323         )
0324     records_filtered = queryset.count()
0325 
0326     # Apply ordering (only for Namespace model fields, not counts)
0327     order_column = dt.order_column
0328     if order_column in ['name', 'owner', 'description', 'updated_at']:
0329         order_by = dt.get_order_by()
0330         queryset = queryset.order_by(order_by)
0331     else:
0332         queryset = queryset.order_by('name')
0333 
0334     # Apply pagination
0335     queryset = queryset[dt.start:dt.start + dt.length]
0336 
0337     # Get counts in 3 aggregate queries - efficient dict lookups
0338     agent_counts = dict(
0339         SystemAgent.objects.exclude(namespace__isnull=True).exclude(namespace='')
0340         .values('namespace').annotate(c=Count('id')).values_list('namespace', 'c')
0341     )
0342     execution_counts = dict(
0343         WorkflowExecution.objects.exclude(namespace__isnull=True).exclude(namespace='')
0344         .values('namespace').annotate(c=Count('execution_id')).values_list('namespace', 'c')
0345     )
0346     message_counts = dict(
0347         WorkflowMessage.objects.exclude(namespace__isnull=True).exclude(namespace='')
0348         .values('namespace').annotate(c=Count('pk')).values_list('namespace', 'c')
0349     )
0350 
0351     # Build data
0352     data = []
0353     for ns in queryset:
0354         description = ns.description if ns.description else '-'
0355         updated_at = ns.updated_at.strftime('%Y-%m-%d %H:%M')
0356 
0357         data.append([
0358             f'<a href="{reverse("monitor_app:namespace_detail", args=[ns.name])}">{ns.name}</a>',
0359             ns.owner,
0360             description,
0361             str(agent_counts.get(ns.name, 0)),
0362             str(execution_counts.get(ns.name, 0)),
0363             str(message_counts.get(ns.name, 0)),
0364             updated_at,
0365         ])
0366 
0367     return dt.create_response(data, records_total, records_filtered)
0368 
0369 
0370 @login_required
0371 def workflow_definition_detail(request, workflow_name, version):
0372     """Detail view for a specific workflow definition."""
0373     definition = get_object_or_404(WorkflowDefinition, workflow_name=workflow_name, version=version)
0374 
0375     # Get execution count for summary
0376     total_executions = definition.executions.count()
0377 
0378     context = {
0379         'definition': definition,
0380         'total_executions': total_executions,
0381     }
0382     return render(request, 'monitor_app/workflow_definition_detail.html', context)
0383 
0384 
0385 @login_required
0386 def workflow_execution_detail(request, execution_id):
0387     """Detail view for a specific workflow execution."""
0388     execution = get_object_or_404(WorkflowExecution, execution_id=execution_id)
0389 
0390     # Calculate duration if completed
0391     duration_text = None
0392     if execution.end_time and execution.start_time:
0393         delta = execution.end_time - execution.start_time
0394         total_seconds = delta.total_seconds()
0395 
0396         minutes = int(total_seconds // 60)
0397         seconds = total_seconds % 60
0398 
0399         if minutes > 0:
0400             duration_text = f"{minutes} minutes, {seconds:.2f} seconds"
0401         else:
0402             duration_text = f"{seconds:.2f} seconds"
0403 
0404     context = {
0405         'execution': execution,
0406         'duration_text': duration_text,
0407     }
0408     return render(request, 'monitor_app/workflow_execution_detail.html', context)