unstract icon indicating copy to clipboard operation
unstract copied to clipboard

UN-3011 [FEAT] Limit maximum file execution count with file history viewer

Open muhammad-ali-e opened this issue 1 month ago • 3 comments

What

  • Add global maximum execution count limit (default: 3) to prevent infinite processing loops
  • Add file history management UI with filtering, viewing, and bulk delete operations

Why

  • Users need visibility into which files have been processed and how many times
  • Users need to prevent infinite processing loops by enforcing maximum execution limits
  • Users need to clean up file history to manage storage and maintain data hygiene
  • Users need granular control over file history with filtering by status and execution count

How

Backend (Django):

  • Created FileHistoryViewSet with read-only endpoints for listing, retrieving, deleting individual and bulk clearing file history records
  • Created IsWorkflowOwnerOrShared permission class to ensure only workflow owners and shared users can access file history
  • Added database migration (0018) to add execution_count field to FileHistory model and max_file_execution_count field to Workflow model (prepared for future workflow-level limits)
  • Enhanced FileHistory model with execution count tracking and limit checking logic
  • Added global default maximum execution count (currently set to 3, configurable via settings)
  • Enhanced Workflow model with get_max_execution_count() method supporting configuration hierarchy (prepared for future workflow > org > global expansion)
  • Updated FileHistorySerializer to include computed fields for max execution count and limit exceeded status
  • Added new URL routes under /workflows/<id>/file-histories/ for CRUD operations
  • Updated internal views to support file history operations

Frontend (React):

  • Created FileHistoryModal component with Ant Design table, pagination, and multi-select row selection
  • Added filter panel supporting status filter (ERROR, COMPLETED) and execution count range filters (min/max)
  • Added action buttons for delete selected, clear all matching filters, and refresh data
  • Integrated modal into Pipelines component with "View File History" menu item
  • Added API service methods in workflow-service.js for file history operations

Workers:

  • Updated filter pipeline to check execution count limits before processing
  • Updated workflow execution service to increment execution count and handle limit exceeded scenarios
  • Updated core data models to support execution count tracking

Future Expansion:

  • Database schema includes max_file_execution_count field on Workflow model for future workflow-level limit configuration
  • Architecture supports extending limits to organization level and workflow level in future releases

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

  • No breaking changes. This PR is backward compatible because:
    • Database migration adds new fields with default values (execution_count defaults to 0, max_file_execution_count is nullable)
    • Existing file history records are preserved and will work with the new UI
    • The feature is additive - existing workflows continue to function without needing configuration changes
    • Global default limit (3) is enforced for all workflows, preventing infinite loops while allowing reasonable retries
    • Permission checks use existing organization-scoped access patterns
    • Workers gracefully handle missing execution count data

Database Migrations

  • Migration 0018: backend/workflow_manager/workflow_v2/migrations/0018_filehistory_execution_count_and_more.py
    • Adds execution_count field to FileHistory model (IntegerField, default=0)
    • Adds max_file_execution_count field to Workflow model (IntegerField, nullable, prepared for future workflow-level configuration)
    • Adds database indexes on FileHistory.status and FileHistory.execution_count for efficient filtering

Env Config

  • No new environment variables required
  • Global default max execution count is currently hardcoded to 3 (can be made configurable in future releases)

Relevant Docs

  • File history management API endpoints documentation should be added to API reference
  • User guide for file history viewer should be added to product documentation
  • Document global execution limit (default: 3) and future expansion to org/workflow levels

Related Issues or PRs

  • Jira ticket: UN-3011

Dependencies Versions

  • No new dependencies added
  • Uses existing Ant Design components (Table, Modal, Button, Select, InputNumber)
  • Uses existing Django REST Framework for API endpoints

Notes on Testing

Backend Testing:

  • Test file history list endpoint with pagination
  • Test filtering by status (ERROR, COMPLETED) and execution count range
  • Test delete single file history record
  • Test bulk clear with filters
  • Test permission checks for workflow owner, shared users, and non-authorized users
  • Test execution count increment logic in workers
  • Test global max execution count limit enforcement (default: 3)
  • Test Redis cache clearing on file history deletion
  • Verify workflow-level max_file_execution_count field exists but is not enforced yet (prepared for future)

Frontend Testing:

  • Test file history modal opens from pipelines action menu
  • Test table displays file path, status, execution count, last run, error, and actions
  • Test pagination (10 items per page)
  • Test multi-select row selection
  • Test delete selected files action
  • Test clear all action with confirmation dialog
  • Test filter panel (status dropdown, min/max execution count inputs)
  • Test copy-to-clipboard for file paths and error messages
  • Test refresh data action
  • Test responsive design on mobile, tablet, and desktop

Integration Testing:

  • Test end-to-end workflow: file upload → processing → execution count increment → view in file history
  • Test limit enforcement: file reaches max execution count (3) → processing stops → status shown in UI
  • Test bulk delete: select multiple files → delete → verify removed from database and cache
  • Test behavior when file hits global limit: verify error message, verify file not reprocessed

Screenshots

(Screenshots to be added by QA team during testing)

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

muhammad-ali-e avatar Nov 24 '25 06:11 muhammad-ali-e

Summary by CodeRabbit

Release Notes

  • New Features
    • Added file history management interface with status filtering, execution count ranges, and file path search capabilities
    • Introduced configurable execution count limits to control how many times files can be reprocessed
    • Added bulk delete and clear operations for managing file history records
    • New "View File History" action accessible from pipeline and deployment views
    • Added copy-to-clipboard functionality for file paths and error messages

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds execution-count tracking and per-workflow execution limits; atomic file-history create/update with race handling; new FileHistory API endpoints and UI for listing/clearing histories; permission checks; worker changes to honor limits and only reuse COMPLETED cached results; frontend clipboard helper and modal UI.

Changes

Cohort / File(s) Summary
Configuration & Env
backend/backend/settings/base.py, backend/sample.env
Add MAX_FILE_EXECUTION_COUNT setting (env var, default 3).
Models & Migration
backend/workflow_manager/workflow_v2/models/file_history.py, backend/workflow_manager/workflow_v2/models/workflow.py, backend/workflow_manager/workflow_v2/migrations/0018_filehistory_execution_count_and_more.py
Add execution_count to FileHistory; add max_file_execution_count to Workflow and ExecutionAction; add indexes and trigram extension; add __str__, has_exceeded_limit, and get_max_execution_count helpers.
FileHistory Helper
backend/workflow_manager/workflow_v2/file_history_helper.py
Add atomic increment helper using F(), safe-string helpers, race-condition handling on IntegrityError; create_file_history creates or atomically increments and returns FileHistory with execution_count.
APIs & Internal Views
backend/workflow_manager/workflow_v2/file_history_views.py, backend/workflow_manager/workflow_v2/urls/workflow.py, backend/workflow_manager/internal_views.py, backend/workflow_manager/workflow_v2/views.py
New FileHistoryViewSet (list/retrieve/destroy/clear) with filters and nested routes; include execution_count and max_execution_count in internal/batch responses; serializer reuse optimization in batch lookup.
Permissions & Serialization
backend/workflow_manager/workflow_v2/permissions.py, backend/workflow_manager/workflow_v2/serializers.py
New IsWorkflowOwnerOrShared permission (caches workflow on request); FileHistorySerializer adds max_execution_count and has_exceeded_limit fields.
Workers & Processing
workers/shared/processing/filter_pipeline.py, workers/shared/workflow/execution/service.py, workers/api-deployment/tasks.py, workers/shared/processing/files/processor.py
Enforce execution-count-based skips (use has_exceeded_limit when available), simplify status-based acceptance, only reuse cached results when status is COMPLETED, and propagate status/error into file-history creation and API cached responses.
Frontend UI & Services
frontend/src/components/pipelines-or-deployments/file-history-modal/FileHistoryModal.jsx, frontend/src/components/pipelines-or-deployments/file-history-modal/FileHistoryModal.css, frontend/src/components/pipelines-or-deployments/pipelines/Pipelines.jsx, frontend/src/components/agency/agency/Agency.jsx, frontend/src/components/workflows/workflow/workflow-service.js
Add File History modal UI (filters, pagination, copy, single/bulk delete, clear) and CSS; integrate modal into Pipelines and Agency; add service methods getFileHistories, deleteFileHistory, bulkClearFileHistories, bulkDeleteFileHistoriesByIds.
Client Data Model & Helpers
unstract/core/src/unstract/core/data_models.py, frontend/src/helpers/GetStaticData.js
Wire execution_count into client FileHistoryData; add copyToClipboard(text) helper.

Sequence Diagram(s)

sequenceDiagram
    participant FE as Frontend (Modal)
    participant API as Django API
    participant Helper as FileHistoryHelper
    participant DB as Postgres

    FE->>API: GET /workflow/{id}/file-histories/ (list) or POST clear/delete
    API->>DB: fetch FileHistory (+ workflow.max_file_execution_count)
    DB-->>API: return records (status, execution_count, max_file_execution_count)
    API-->>FE: list response (includes execution_count, max_execution_count, has_exceeded_limit)
    
    FE->>API: POST /workflow/{id}/file-histories/ (create/update) or DELETE single
    API->>Helper: create_file_history(workflow, file_hash, status, result, metadata, error, is_api)
    Helper->>DB: SELECT FileHistory WHERE workflow & file_hash
    alt exists
        Helper->>DB: UPDATE execution_count = F('execution_count') + 1, set status/result/error
        DB-->>Helper: updated record
    else not exists
        Helper->>DB: INSERT FileHistory (execution_count=1)
        alt IntegrityError (race)
            Helper->>DB: SELECT & UPDATE execution_count = F('execution_count') + 1
            DB-->>Helper: updated record
        end
    end
    Helper-->>API: return FileHistory (includes execution_count)
    API-->>FE: Response {file_history_id, execution_count, ...}
sequenceDiagram
    participant Worker as Worker
    participant Pipeline as filter_pipeline
    participant API as Django API
    participant DB as Postgres

    Worker->>API: GET file-history for file (hash)
    API->>DB: fetch FileHistory + Workflow.max_file_execution_count
    DB-->>API: return data (status, execution_count, max_file_execution_count, has_exceeded_limit)
    API-->>Pipeline: return history data
    alt has_exceeded_limit == true OR (has_exceeded_limit is None AND execution_count >= max_file_execution_count)
        Pipeline-->>Worker: SKIP (limit reached)
    else status allows reprocess
        Pipeline-->>Worker: ACCEPT (process)
    else
        Pipeline->>Pipeline: compare history_path vs current_path
        alt same path
            Pipeline-->>Worker: SKIP
        else
            Pipeline-->>Worker: ACCEPT
        end
    end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • backend/workflow_manager/workflow_v2/file_history_helper.py — atomic increments, IntegrityError recovery, freshness of returned instance.
    • workers/shared/processing/filter_pipeline.py — new SKIP/ACCEPT logic and backward-compat handling when has_exceeded_limit is None.
    • workers/shared/processing/files/processor.py & workers/api-deployment/tasks.py — gating cached-result usage on COMPLETED and correct propagation of status/error to API responses.
    • backend/workflow_manager/workflow_v2/file_history_views.py — bulk clear validation (IDs limit) and filter-based deletion semantics.
    • frontend/src/components/.../FileHistoryModal.jsx — filter apply/reset UX, bulk action counts/limits, and API error handling.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically summarizes the main feature: introducing a maximum file execution count limit with a file history viewer UI.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required template sections: What, Why, How, database migrations, env config, related issues, testing notes, and screenshots.
Docstring Coverage ✅ Passed Docstring coverage is 84.85% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment
  • [ ] Commit unit tests in branch feat/UN-3011-FEAT_limit_maximum_file_execution_count_with_file_history_viewer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Nov 24 '25 06:11 coderabbitai[bot]

@muhammad-ali-e make sure to raise a docs PR as well for this

@muhammad-ali-e make sure to raise a docs PR as well for this

once it went to staging

muhammad-ali-e avatar Dec 01 '25 09:12 muhammad-ali-e

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 66 passed, 0 failed (66 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_time\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_delay\_would\_exceed\_max\_time}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_time}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{66}}$$ $$\textcolor{#23d18b}{\tt{66}}$$

github-actions[bot] avatar Dec 03 '25 14:12 github-actions[bot]