-
Notifications
You must be signed in to change notification settings - Fork 572
UN-3011 [FEAT] Limit maximum file execution count with file history viewer #1676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
muhammad-ali-e
wants to merge
14
commits into
main
Choose a base branch
from
feat/UN-3011-FEAT_limit_maximum_file_execution_count_with_file_history_viewer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
127f129
UN-3011 [FEAT] Limit maximum file execution count with file history v…
muhammad-ali-e 2b27112
added file path search and addressed coderabbit comments
muhammad-ali-e b23b448
removed cache clear now. will address it later
muhammad-ali-e a6886f5
Merge branch 'main' into feat/UN-3011-FEAT_limit_maximum_file_executi…
muhammad-ali-e d14aa46
single to bulk delete
muhammad-ali-e 6c17c00
Merge branch 'feat/UN-3011-FEAT_limit_maximum_file_execution_count_wi…
muhammad-ali-e 1716258
minor changes in validations
muhammad-ali-e cfdafef
addressed commenets
muhammad-ali-e 4edfcda
addressed commenets
muhammad-ali-e 4f30f4b
Merge branch 'main' into feat/UN-3011-FEAT_limit_maximum_file_executi…
muhammad-ali-e 9006b78
addressed commenets
muhammad-ali-e 002c140
Merge branch 'feat/UN-3011-FEAT_limit_maximum_file_execution_count_wi…
muhammad-ali-e 2891ffa
removed unwanted console
muhammad-ali-e 7e22f99
addressed ux design suggestions
muhammad-ali-e File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
backend/workflow_manager/workflow_v2/file_history_views.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import logging | ||
|
|
||
| from django.conf import settings | ||
| from django.shortcuts import get_object_or_404 | ||
| from rest_framework import status, viewsets | ||
| from rest_framework.decorators import action | ||
| from rest_framework.permissions import IsAuthenticated | ||
| from rest_framework.response import Response | ||
| from utils.pagination import CustomPagination | ||
|
|
||
| from workflow_manager.workflow_v2.models.file_history import FileHistory | ||
| from workflow_manager.workflow_v2.models.workflow import Workflow | ||
| from workflow_manager.workflow_v2.permissions import IsWorkflowOwnerOrShared | ||
| from workflow_manager.workflow_v2.serializers import FileHistorySerializer | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class FileHistoryViewSet(viewsets.ReadOnlyModelViewSet): | ||
| """ViewSet for file history operations with filtering support.""" | ||
|
|
||
| serializer_class = FileHistorySerializer | ||
| lookup_field = "id" | ||
| permission_classes = [IsAuthenticated, IsWorkflowOwnerOrShared] | ||
| pagination_class = CustomPagination | ||
|
|
||
| def get_queryset(self): | ||
| """Get file histories for workflow with filters.""" | ||
| # Reuse cached workflow from permission check | ||
| if hasattr(self.request, "_workflow_cache"): | ||
| workflow = self.request._workflow_cache | ||
| else: | ||
| # Fallback if permission didn't run (shouldn't happen) | ||
| workflow_id = self.kwargs.get("workflow_id") | ||
| workflow = get_object_or_404(Workflow, id=workflow_id) | ||
|
|
||
| queryset = FileHistory.objects.filter(workflow=workflow) | ||
|
|
||
| # Apply simple filters from query parameters | ||
| status_param = self.request.query_params.get("status") | ||
| if status_param: | ||
| status_list = [s.strip() for s in status_param.split(",")] | ||
| queryset = queryset.filter(status__in=status_list) | ||
|
|
||
| exec_min = self.request.query_params.get("execution_count_min") | ||
| if exec_min: | ||
| queryset = queryset.filter(execution_count__gte=int(exec_min)) | ||
|
|
||
| exec_max = self.request.query_params.get("execution_count_max") | ||
| if exec_max: | ||
| queryset = queryset.filter(execution_count__lte=int(exec_max)) | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return queryset.order_by("-created_at") | ||
|
|
||
| def destroy(self, request, workflow_id=None, id=None): | ||
| """Delete single file history by ID.""" | ||
| file_history = self.get_object() | ||
| file_history_id = file_history.id | ||
|
|
||
| # Clear Redis cache for this file before deletion | ||
| self._clear_cache_for_file(workflow_id, file_history) | ||
|
|
||
| file_history.delete() | ||
|
|
||
| logger.info(f"Deleted file history {file_history_id} for workflow {workflow_id}") | ||
|
|
||
| return Response( | ||
| {"message": "File history deleted successfully"}, status=status.HTTP_200_OK | ||
| ) | ||
|
|
||
| @action(detail=False, methods=["post"]) | ||
| def clear(self, request, workflow_id=None): | ||
| """Clear file histories with filters (direct delete).""" | ||
| workflow = get_object_or_404(Workflow, id=workflow_id) | ||
| queryset = FileHistory.objects.filter(workflow=workflow) | ||
|
|
||
| # Apply filters from request body | ||
| status_list = request.data.get("status", []) | ||
| if status_list: | ||
| queryset = queryset.filter(status__in=status_list) | ||
|
|
||
| exec_min = request.data.get("execution_count_min") | ||
| if exec_min: | ||
| queryset = queryset.filter(execution_count__gte=exec_min) | ||
|
|
||
| exec_max = request.data.get("execution_count_max") | ||
| if exec_max: | ||
| queryset = queryset.filter(execution_count__lte=exec_max) | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # Delete directly (no dry run complexity) | ||
| deleted_count, _ = queryset.delete() | ||
|
|
||
| # Clear Redis cache pattern for workflow | ||
| self._clear_workflow_cache(workflow_id) | ||
|
|
||
| logger.info( | ||
| f"Cleared {deleted_count} file history records for workflow {workflow_id}" | ||
| ) | ||
|
|
||
| return Response( | ||
| { | ||
| "deleted_count": deleted_count, | ||
| "message": f"{deleted_count} file history records deleted", | ||
| } | ||
| ) | ||
|
|
||
| def _clear_cache_for_file(self, workflow_id, file_history): | ||
| """Clear Redis cache for specific file.""" | ||
| try: | ||
| from workflow_manager.workflow_v2.execution.active_file_manager import ( | ||
| ActiveFileManager, | ||
| ) | ||
|
|
||
| cache_key = ActiveFileManager._create_cache_key( | ||
| workflow_id, file_history.provider_file_uuid, file_history.file_path | ||
| ) | ||
| DB = settings.FILE_ACTIVE_CACHE_REDIS_DB | ||
| from utils.cache import CacheService | ||
|
|
||
| CacheService.delete_key(cache_key, db=DB) | ||
| logger.debug(f"Cleared cache for file: {cache_key}") | ||
| except Exception as e: | ||
| logger.warning(f"Failed to clear cache for file: {e}") | ||
muhammad-ali-e marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| def _clear_workflow_cache(self, workflow_id): | ||
| """Clear all Redis cache for workflow.""" | ||
| try: | ||
| pattern = f"file_active:{workflow_id}:*" | ||
| DB = settings.FILE_ACTIVE_CACHE_REDIS_DB | ||
| from utils.cache import CacheService | ||
|
|
||
| CacheService.clear_cache_optimized(pattern, db=DB) | ||
| logger.debug(f"Cleared cache pattern: {pattern}") | ||
| except Exception as e: | ||
| logger.warning(f"Failed to clear workflow cache: {e}") | ||
42 changes: 42 additions & 0 deletions
42
backend/workflow_manager/workflow_v2/migrations/0018_filehistory_execution_count_and_more.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # Generated by Django 4.2.1 on 2025-11-21 12:18 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("workflow_v2", "0017_workflow_shared_to_org_workflow_shared_users"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="filehistory", | ||
| name="execution_count", | ||
| field=models.IntegerField( | ||
| db_comment="Number of times this file has been processed", default=1 | ||
| ), | ||
| ), | ||
| migrations.AddField( | ||
| model_name="workflow", | ||
| name="max_file_execution_count", | ||
| field=models.IntegerField( | ||
| blank=True, | ||
| db_comment="Maximum times a file can be executed. null=use org/global default. Only enforced for ETL/TASK workflows.", | ||
| null=True, | ||
| ), | ||
| ), | ||
| migrations.AddIndex( | ||
| model_name="filehistory", | ||
| index=models.Index( | ||
| fields=["workflow", "status"], | ||
| name="idx_fh_wf_status", | ||
| ), | ||
| ), | ||
| migrations.AddIndex( | ||
| model_name="filehistory", | ||
| index=models.Index( | ||
| fields=["workflow", "execution_count"], | ||
| name="idx_fh_wf_exec_cnt", | ||
| ), | ||
| ), | ||
| ] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.