tasker-engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +440 -0
- data/Rakefile +10 -0
- data/app/controllers/tasker/analytics_controller.rb +179 -0
- data/app/controllers/tasker/application_controller.rb +45 -0
- data/app/controllers/tasker/graphql_controller.rb +193 -0
- data/app/controllers/tasker/handlers_controller.rb +217 -0
- data/app/controllers/tasker/health_controller.rb +229 -0
- data/app/controllers/tasker/metrics_controller.rb +111 -0
- data/app/controllers/tasker/page_sort.rb +97 -0
- data/app/controllers/tasker/tasks_controller.rb +123 -0
- data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
- data/app/graphql/examples/all_tasks.graphql +22 -0
- data/app/graphql/examples/pending_tasks.graphql +23 -0
- data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
- data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
- data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
- data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
- data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
- data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
- data/app/graphql/tasker/graph_ql_types.rb +42 -0
- data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
- data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
- data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
- data/app/graphql/tasker/mutations/create_task.rb +52 -0
- data/app/graphql/tasker/mutations/update_step.rb +36 -0
- data/app/graphql/tasker/mutations/update_task.rb +41 -0
- data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
- data/app/graphql/tasker/queries/all_tasks.rb +23 -0
- data/app/graphql/tasker/queries/base_query.rb +9 -0
- data/app/graphql/tasker/queries/helpers.rb +16 -0
- data/app/graphql/tasker/queries/one_step.rb +24 -0
- data/app/graphql/tasker/queries/one_task.rb +18 -0
- data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
- data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
- data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
- data/app/jobs/tasker/application_job.rb +8 -0
- data/app/jobs/tasker/metrics_export_job.rb +252 -0
- data/app/jobs/tasker/task_runner_job.rb +224 -0
- data/app/models/tasker/annotation_type.rb +26 -0
- data/app/models/tasker/application_record.rb +70 -0
- data/app/models/tasker/dependent_system.rb +26 -0
- data/app/models/tasker/dependent_system_object_map.rb +64 -0
- data/app/models/tasker/named_step.rb +41 -0
- data/app/models/tasker/named_task.rb +121 -0
- data/app/models/tasker/named_tasks_named_step.rb +82 -0
- data/app/models/tasker/step_dag_relationship.rb +65 -0
- data/app/models/tasker/step_readiness_status.rb +59 -0
- data/app/models/tasker/task.rb +414 -0
- data/app/models/tasker/task_annotation.rb +36 -0
- data/app/models/tasker/task_execution_context.rb +29 -0
- data/app/models/tasker/task_namespace.rb +41 -0
- data/app/models/tasker/task_transition.rb +235 -0
- data/app/models/tasker/workflow_step.rb +461 -0
- data/app/models/tasker/workflow_step_edge.rb +95 -0
- data/app/models/tasker/workflow_step_transition.rb +434 -0
- data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
- data/app/serializers/tasker/handler_serializer.rb +109 -0
- data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
- data/app/serializers/tasker/task_serializer.rb +168 -0
- data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
- data/app/services/tasker/analytics_service.rb +409 -0
- data/config/initializers/dry_struct.rb +11 -0
- data/config/initializers/statesman.rb +6 -0
- data/config/initializers/tasker_orchestration.rb +17 -0
- data/config/initializers/time_formats.rb +4 -0
- data/config/routes.rb +34 -0
- data/config/tasker/subscriptions/example_integrations.yml +67 -0
- data/config/tasker/system_events.yml +305 -0
- data/db/functions/calculate_dependency_levels_v01.sql +45 -0
- data/db/functions/get_analytics_metrics_v01.sql +137 -0
- data/db/functions/get_slowest_steps_v01.sql +82 -0
- data/db/functions/get_slowest_tasks_v01.sql +96 -0
- data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
- data/db/functions/get_step_readiness_status_single_and_batch_v02.sql +223 -0
- data/db/functions/get_step_readiness_status_v01.sql +139 -0
- data/db/functions/get_system_health_counts_v01.sql +108 -0
- data/db/functions/get_task_execution_context_v01.sql +108 -0
- data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
- data/db/init/schema.sql +2254 -0
- data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
- data/db/migrate/20250710110830_step_readiness_sql_functions_v02.rb +39 -0
- data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
- data/docs/APPLICATION_GENERATOR.md +384 -0
- data/docs/AUTH.md +1741 -0
- data/docs/CIRCUIT_BREAKER.md +224 -0
- data/docs/DEVELOPER_GUIDE.md +2664 -0
- data/docs/EVENT_SYSTEM.md +637 -0
- data/docs/EXECUTION_CONFIGURATION.md +341 -0
- data/docs/FLOW_CHART.md +149 -0
- data/docs/HEALTH.md +542 -0
- data/docs/METRICS.md +731 -0
- data/docs/OPTIMIZATION_PLAN.md +1479 -0
- data/docs/OVERVIEW.md +548 -0
- data/docs/QUICK_START.md +270 -0
- data/docs/REGISTRY_SYSTEMS.md +373 -0
- data/docs/REST_API.md +632 -0
- data/docs/REVERSIONING.md +404 -0
- data/docs/ROADMAP.md +221 -0
- data/docs/SQL_FUNCTIONS.md +1408 -0
- data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
- data/docs/TELEMETRY.md +795 -0
- data/docs/TROUBLESHOOTING.md +756 -0
- data/docs/TaskHandlerGenerator.html +255 -0
- data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
- data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
- data/docs/Tasker/Analysis.html +117 -0
- data/docs/Tasker/AnalyticsController.html +450 -0
- data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
- data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
- data/docs/Tasker/AnalyticsService.html +2221 -0
- data/docs/Tasker/AnnotationType.html +137 -0
- data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
- data/docs/Tasker/ApplicationController.html +147 -0
- data/docs/Tasker/ApplicationJob.html +128 -0
- data/docs/Tasker/ApplicationRecord.html +378 -0
- data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
- data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
- data/docs/Tasker/Authentication/Coordinator.html +242 -0
- data/docs/Tasker/Authentication/Interface.html +560 -0
- data/docs/Tasker/Authentication/InterfaceError.html +124 -0
- data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
- data/docs/Tasker/Authentication.html +119 -0
- data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
- data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
- data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
- data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
- data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +360 -0
- data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
- data/docs/Tasker/Authorization/ResourceRegistry.html +875 -0
- data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
- data/docs/Tasker/Authorization.html +582 -0
- data/docs/Tasker/CacheCapabilities.html +167 -0
- data/docs/Tasker/CacheStrategy.html +1297 -0
- data/docs/Tasker/Concerns/Authenticatable.html +116 -0
- data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
- data/docs/Tasker/Concerns/Authorizable.html +816 -0
- data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
- data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
- data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
- data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
- data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
- data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
- data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
- data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
- data/docs/Tasker/Concerns.html +117 -0
- data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
- data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
- data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
- data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
- data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
- data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
- data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
- data/docs/Tasker/Configuration.html +1669 -0
- data/docs/Tasker/ConfigurationError.html +143 -0
- data/docs/Tasker/ConfiguredTask.html +514 -0
- data/docs/Tasker/Constants/EventDefinitions.html +590 -0
- data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
- data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
- data/docs/Tasker/Constants/RegistryEvents.html +285 -0
- data/docs/Tasker/Constants/StepEvents.html +177 -0
- data/docs/Tasker/Constants/TaskEvents.html +167 -0
- data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
- data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
- data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
- data/docs/Tasker/Constants/TaskExecution.html +126 -0
- data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
- data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
- data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
- data/docs/Tasker/Constants/TaskFinalization.html +126 -0
- data/docs/Tasker/Constants/TaskStatuses.html +223 -0
- data/docs/Tasker/Constants/TestEvents.html +163 -0
- data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
- data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
- data/docs/Tasker/Constants.html +561 -0
- data/docs/Tasker/DependentSystem.html +137 -0
- data/docs/Tasker/DependentSystemObjectMap.html +250 -0
- data/docs/Tasker/DetectorRegistry.html +598 -0
- data/docs/Tasker/Diagram/Edge.html +1191 -0
- data/docs/Tasker/Diagram/Flowchart.html +1539 -0
- data/docs/Tasker/Diagram/Node.html +1165 -0
- data/docs/Tasker/Diagram.html +117 -0
- data/docs/Tasker/Engine.html +215 -0
- data/docs/Tasker/Error.html +139 -0
- data/docs/Tasker/Events/Bus.html +1226 -0
- data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
- data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
- data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
- data/docs/Tasker/Events/Catalog.html +1291 -0
- data/docs/Tasker/Events/CustomRegistry.html +943 -0
- data/docs/Tasker/Events/DefinitionLoader.html +575 -0
- data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
- data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
- data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
- data/docs/Tasker/Events/Publisher.html +365 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
- data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
- data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
- data/docs/Tasker/Events/Subscribers.html +117 -0
- data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
- data/docs/Tasker/Events.html +294 -0
- data/docs/Tasker/EventsGenerator.html +459 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
- data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
- data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
- data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
- data/docs/Tasker/Functions.html +117 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
- data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
- data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
- data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
- data/docs/Tasker/Generators.html +117 -0
- data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
- data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
- data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
- data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
- data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
- data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
- data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
- data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
- data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
- data/docs/Tasker/GraphQLTypes.html +130 -0
- data/docs/Tasker/GraphqlController.html +251 -0
- data/docs/Tasker/HandlerFactory.html +1528 -0
- data/docs/Tasker/HandlerSerializer.html +682 -0
- data/docs/Tasker/HandlersController.html +574 -0
- data/docs/Tasker/HashIdentityStrategy.html +278 -0
- data/docs/Tasker/Health/ReadinessChecker.html +712 -0
- data/docs/Tasker/Health/StatusChecker.html +653 -0
- data/docs/Tasker/Health.html +117 -0
- data/docs/Tasker/HealthController.html +523 -0
- data/docs/Tasker/IdentityStrategy.html +276 -0
- data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
- data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events.html +204 -0
- data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
- data/docs/Tasker/LifecycleEvents.html +799 -0
- data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
- data/docs/Tasker/Logging.html +115 -0
- data/docs/Tasker/MetricsController.html +293 -0
- data/docs/Tasker/MetricsExportJob.html +414 -0
- data/docs/Tasker/Mutations/BaseMutation.html +128 -0
- data/docs/Tasker/Mutations/CancelStep.html +219 -0
- data/docs/Tasker/Mutations/CancelTask.html +221 -0
- data/docs/Tasker/Mutations/CreateTask.html +243 -0
- data/docs/Tasker/Mutations/UpdateStep.html +243 -0
- data/docs/Tasker/Mutations/UpdateTask.html +243 -0
- data/docs/Tasker/Mutations.html +117 -0
- data/docs/Tasker/NamedStep.html +216 -0
- data/docs/Tasker/NamedTask.html +910 -0
- data/docs/Tasker/NamedTasksNamedStep.html +435 -0
- data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
- data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
- data/docs/Tasker/Orchestration/Coordinator.html +641 -0
- data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
- data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
- data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
- data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
- data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
- data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
- data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
- data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
- data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
- data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
- data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
- data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
- data/docs/Tasker/Orchestration.html +130 -0
- data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
- data/docs/Tasker/PageSort.html +247 -0
- data/docs/Tasker/PermanentError.html +518 -0
- data/docs/Tasker/ProceduralError.html +147 -0
- data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
- data/docs/Tasker/Queries/AllTasks.html +221 -0
- data/docs/Tasker/Queries/BaseQuery.html +128 -0
- data/docs/Tasker/Queries/Helpers.html +187 -0
- data/docs/Tasker/Queries/OneStep.html +225 -0
- data/docs/Tasker/Queries/OneTask.html +217 -0
- data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
- data/docs/Tasker/Queries/TasksByStatus.html +233 -0
- data/docs/Tasker/Queries.html +119 -0
- data/docs/Tasker/Railtie.html +124 -0
- data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
- data/docs/Tasker/Registry/EventPublisher.html +667 -0
- data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
- data/docs/Tasker/Registry/RegistrationError.html +132 -0
- data/docs/Tasker/Registry/RegistryError.html +139 -0
- data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
- data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
- data/docs/Tasker/Registry/ValidationError.html +132 -0
- data/docs/Tasker/Registry.html +119 -0
- data/docs/Tasker/RetryableError.html +515 -0
- data/docs/Tasker/StateMachine/Compatibility.html +282 -0
- data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
- data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
- data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
- data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
- data/docs/Tasker/StateMachine.html +602 -0
- data/docs/Tasker/StepDagRelationship.html +657 -0
- data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
- data/docs/Tasker/StepHandler/Api.html +884 -0
- data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
- data/docs/Tasker/StepHandler/Base.html +970 -0
- data/docs/Tasker/StepHandler.html +119 -0
- data/docs/Tasker/StepReadinessStatus.html +836 -0
- data/docs/Tasker/Task.html +2478 -0
- data/docs/Tasker/TaskAnnotation.html +137 -0
- data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
- data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
- data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
- data/docs/Tasker/TaskBuilder.html +764 -0
- data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
- data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
- data/docs/Tasker/TaskDiagram.html +548 -0
- data/docs/Tasker/TaskDiagramsController.html +240 -0
- data/docs/Tasker/TaskExecutionContext.html +469 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
- data/docs/Tasker/TaskHandler/ClassMethods.html +395 -0
- data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
- data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
- data/docs/Tasker/TaskHandler.html +271 -0
- data/docs/Tasker/TaskNamespace.html +312 -0
- data/docs/Tasker/TaskRunnerJob.html +406 -0
- data/docs/Tasker/TaskSerializer.html +474 -0
- data/docs/Tasker/TaskTransition.html +1517 -0
- data/docs/Tasker/TaskWorkflowSummary.html +988 -0
- data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
- data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema.html +384 -0
- data/docs/Tasker/TasksController.html +595 -0
- data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
- data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
- data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
- data/docs/Tasker/Telemetry/Events.html +115 -0
- data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
- data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
- data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
- data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
- data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
- data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
- data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
- data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
- data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
- data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
- data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
- data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
- data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
- data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
- data/docs/Tasker/Telemetry/Plugins.html +117 -0
- data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
- data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
- data/docs/Tasker/Telemetry.html +130 -0
- data/docs/Tasker/Types/AuthConfig.html +886 -0
- data/docs/Tasker/Types/BackoffConfig.html +1063 -0
- data/docs/Tasker/Types/BaseConfig.html +227 -0
- data/docs/Tasker/Types/CacheConfig.html +1731 -0
- data/docs/Tasker/Types/DatabaseConfig.html +388 -0
- data/docs/Tasker/Types/DependencyGraph.html +526 -0
- data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
- data/docs/Tasker/Types/EngineConfig.html +1181 -0
- data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
- data/docs/Tasker/Types/GraphEdge.html +517 -0
- data/docs/Tasker/Types/GraphMetadata.html +781 -0
- data/docs/Tasker/Types/GraphNode.html +694 -0
- data/docs/Tasker/Types/HealthConfig.html +784 -0
- data/docs/Tasker/Types/StepSequence.html +353 -0
- data/docs/Tasker/Types/StepTemplate.html +1193 -0
- data/docs/Tasker/Types/TaskRequest.html +1179 -0
- data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
- data/docs/Tasker/Types.html +154 -0
- data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
- data/docs/Tasker/WorkflowStep.html +2724 -0
- data/docs/Tasker/WorkflowStepEdge.html +306 -0
- data/docs/Tasker/WorkflowStepSerializer.html +305 -0
- data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
- data/docs/Tasker/WorkflowStepTransition.html +2201 -0
- data/docs/Tasker/WorkflowStepsController.html +462 -0
- data/docs/Tasker.html +468 -0
- data/docs/VISION.md +584 -0
- data/docs/WHY.md +21 -0
- data/docs/_index.html +2319 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/events/migration_plan_outcomes.md +80 -0
- data/docs/file.README.html +537 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +537 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +8854 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/generators/tasker/authenticator_generator.rb +301 -0
- data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
- data/lib/generators/tasker/events_generator.rb +91 -0
- data/lib/generators/tasker/subscriber_generator.rb +107 -0
- data/lib/generators/tasker/task_handler_generator.rb +138 -0
- data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
- data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
- data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +82 -0
- data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +136 -0
- data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
- data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
- data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
- data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
- data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
- data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
- data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
- data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
- data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
- data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
- data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
- data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
- data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
- data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
- data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
- data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
- data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
- data/lib/generators/tasker/templates/task_handler.rb.erb +60 -0
- data/lib/generators/tasker/templates/task_handler_spec.rb.erb +165 -0
- data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
- data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
- data/lib/tasker/authentication/coordinator.rb +78 -0
- data/lib/tasker/authentication/errors.rb +9 -0
- data/lib/tasker/authentication/interface.rb +36 -0
- data/lib/tasker/authentication/none_authenticator.rb +26 -0
- data/lib/tasker/authorization/base_coordinator.rb +112 -0
- data/lib/tasker/authorization/errors.rb +26 -0
- data/lib/tasker/authorization/resource_constants.rb +73 -0
- data/lib/tasker/authorization/resource_registry.rb +136 -0
- data/lib/tasker/authorization.rb +75 -0
- data/lib/tasker/cache_capabilities.rb +131 -0
- data/lib/tasker/cache_strategy.rb +469 -0
- data/lib/tasker/concerns/authenticatable.rb +41 -0
- data/lib/tasker/concerns/authorizable.rb +204 -0
- data/lib/tasker/concerns/controller_authorizable.rb +124 -0
- data/lib/tasker/concerns/event_publisher.rb +716 -0
- data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
- data/lib/tasker/concerns/state_machine_base.rb +218 -0
- data/lib/tasker/concerns/structured_logging.rb +387 -0
- data/lib/tasker/configuration.rb +325 -0
- data/lib/tasker/constants/event_definitions.rb +147 -0
- data/lib/tasker/constants/registry_events.rb +54 -0
- data/lib/tasker/constants.rb +417 -0
- data/lib/tasker/engine.rb +90 -0
- data/lib/tasker/errors.rb +90 -0
- data/lib/tasker/events/catalog.rb +432 -0
- data/lib/tasker/events/custom_registry.rb +175 -0
- data/lib/tasker/events/definition_loader.rb +199 -0
- data/lib/tasker/events/event_payload_builder.rb +461 -0
- data/lib/tasker/events/publisher.rb +149 -0
- data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
- data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
- data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
- data/lib/tasker/events/subscription_loader.rb +161 -0
- data/lib/tasker/events.rb +37 -0
- data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
- data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
- data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
- data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
- data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
- data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
- data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
- data/lib/tasker/functions/function_wrapper.rb +42 -0
- data/lib/tasker/functions.rb +12 -0
- data/lib/tasker/handler_factory.rb +327 -0
- data/lib/tasker/health/readiness_checker.rb +186 -0
- data/lib/tasker/health/status_checker.rb +203 -0
- data/lib/tasker/identity_strategy.rb +38 -0
- data/lib/tasker/logging/correlation_id_generator.rb +120 -0
- data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
- data/lib/tasker/orchestration/connection_builder.rb +122 -0
- data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
- data/lib/tasker/orchestration/coordinator.rb +119 -0
- data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
- data/lib/tasker/orchestration/plugin_integration.rb +124 -0
- data/lib/tasker/orchestration/response_processor.rb +168 -0
- data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
- data/lib/tasker/orchestration/step_executor.rb +941 -0
- data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
- data/lib/tasker/orchestration/task_finalizer.rb +564 -0
- data/lib/tasker/orchestration/task_initializer.rb +140 -0
- data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
- data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
- data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
- data/lib/tasker/orchestration.rb +45 -0
- data/lib/tasker/railtie.rb +9 -0
- data/lib/tasker/registry/base_registry.rb +177 -0
- data/lib/tasker/registry/event_publisher.rb +91 -0
- data/lib/tasker/registry/interface_validator.rb +140 -0
- data/lib/tasker/registry/statistics_collector.rb +381 -0
- data/lib/tasker/registry/subscriber_registry.rb +285 -0
- data/lib/tasker/registry.rb +22 -0
- data/lib/tasker/state_machine/step_state_machine.rb +508 -0
- data/lib/tasker/state_machine/task_state_machine.rb +192 -0
- data/lib/tasker/state_machine.rb +83 -0
- data/lib/tasker/step_handler/api.rb +410 -0
- data/lib/tasker/step_handler/base.rb +206 -0
- data/lib/tasker/task_builder.rb +432 -0
- data/lib/tasker/task_handler/class_methods.rb +327 -0
- data/lib/tasker/task_handler/instance_methods.rb +293 -0
- data/lib/tasker/task_handler/step_group.rb +182 -0
- data/lib/tasker/task_handler.rb +43 -0
- data/lib/tasker/telemetry/event_mapping.rb +126 -0
- data/lib/tasker/telemetry/event_router.rb +318 -0
- data/lib/tasker/telemetry/events/export_events.rb +38 -0
- data/lib/tasker/telemetry/export_coordinator.rb +497 -0
- data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
- data/lib/tasker/telemetry/log_backend.rb +224 -0
- data/lib/tasker/telemetry/metric_types.rb +416 -0
- data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
- data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
- data/lib/tasker/telemetry/plugin_registry.rb +333 -0
- data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
- data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
- data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
- data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
- data/lib/tasker/telemetry/trace_backend.rb +186 -0
- data/lib/tasker/telemetry.rb +59 -0
- data/lib/tasker/types/auth_config.rb +81 -0
- data/lib/tasker/types/backoff_config.rb +142 -0
- data/lib/tasker/types/cache_config.rb +257 -0
- data/lib/tasker/types/database_config.rb +39 -0
- data/lib/tasker/types/dependency_graph.rb +225 -0
- data/lib/tasker/types/dependency_graph_config.rb +149 -0
- data/lib/tasker/types/engine_config.rb +131 -0
- data/lib/tasker/types/execution_config.rb +289 -0
- data/lib/tasker/types/health_config.rb +84 -0
- data/lib/tasker/types/step_sequence.rb +24 -0
- data/lib/tasker/types/step_template.rb +63 -0
- data/lib/tasker/types/task_request.rb +60 -0
- data/lib/tasker/types/telemetry_config.rb +273 -0
- data/lib/tasker/types.rb +64 -0
- data/lib/tasker/version.rb +8 -0
- data/lib/tasker.rb +82 -0
- data/lib/tasks/tasker_tasks.rake +383 -0
- metadata +954 -0
@@ -0,0 +1,235 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tasker
|
4
|
+
# TaskTransition represents state transitions for Task entities using Statesman
|
5
|
+
#
|
6
|
+
# This model stores the audit trail of all task state changes, providing
|
7
|
+
# a complete history of task lifecycle events with metadata and timestamps.
|
8
|
+
class TaskTransition < ApplicationRecord
|
9
|
+
# NOTE: We don't include Statesman::Adapters::ActiveRecordTransition
|
10
|
+
# because we're using PostgreSQL JSONB column for metadata
|
11
|
+
|
12
|
+
# Associations
|
13
|
+
belongs_to :task, inverse_of: :task_transitions
|
14
|
+
|
15
|
+
# Validations
|
16
|
+
validates :to_state, inclusion: {
|
17
|
+
in: %w[pending in_progress complete error cancelled resolved_manually],
|
18
|
+
message: 'is not a valid task state'
|
19
|
+
}
|
20
|
+
|
21
|
+
# Validate that the task exists before creating transition
|
22
|
+
validate :task_must_exist
|
23
|
+
|
24
|
+
validates :sort_key, presence: true, uniqueness: { scope: :task_id }
|
25
|
+
# Custom validation for metadata that allows empty hash but not nil
|
26
|
+
validate :metadata_must_be_hash
|
27
|
+
|
28
|
+
# Ensure metadata is always a hash
|
29
|
+
after_initialize :ensure_metadata_hash
|
30
|
+
# Ensure metadata defaults to empty hash if not provided
|
31
|
+
before_validation :ensure_metadata_presence
|
32
|
+
|
33
|
+
# Scopes
|
34
|
+
scope :recent, -> { order(sort_key: :desc) }
|
35
|
+
scope :to_state, ->(state) { where(to_state: state) }
|
36
|
+
scope :with_metadata_key, ->(key) { where('metadata ? :key', key: key.to_s) }
|
37
|
+
|
38
|
+
# Class methods for querying transitions
|
39
|
+
class << self
|
40
|
+
# Find the most recent transition to a specific state
|
41
|
+
#
|
42
|
+
# @param state [String, Symbol] The state to find the most recent transition to
|
43
|
+
# @return [TaskTransition, nil] The most recent transition to the given state
|
44
|
+
def most_recent_to_state(state)
|
45
|
+
to_state(state.to_s).order(sort_key: :desc).first
|
46
|
+
end
|
47
|
+
|
48
|
+
# Find all transitions that occurred within a time range
|
49
|
+
#
|
50
|
+
# @param start_time [Time] The start of the time range
|
51
|
+
# @param end_time [Time] The end of the time range
|
52
|
+
# @return [ActiveRecord::Relation] Transitions within the time range
|
53
|
+
def in_time_range(start_time, end_time)
|
54
|
+
where(created_at: start_time..end_time)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Find transitions with specific metadata values
|
58
|
+
#
|
59
|
+
# @param key [String, Symbol] The metadata key to search for
|
60
|
+
# @param value [Object] The value to match
|
61
|
+
# @return [ActiveRecord::Relation] Transitions with matching metadata
|
62
|
+
def with_metadata_value(key, value)
|
63
|
+
where('metadata->:key = :value', key: key.to_s, value: value.to_json.delete('"'))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get transition statistics for analytics
|
67
|
+
#
|
68
|
+
# @return [Hash] Statistics about transitions
|
69
|
+
def statistics
|
70
|
+
{
|
71
|
+
total_transitions: count,
|
72
|
+
states: group(:to_state).count,
|
73
|
+
recent_activity: where(created_at: 24.hours.ago..Time.current).count,
|
74
|
+
average_time_between_transitions: average_time_between_transitions
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# Calculate average time between transitions
|
81
|
+
#
|
82
|
+
# @return [Float, nil] Average seconds between transitions
|
83
|
+
def average_time_between_transitions
|
84
|
+
transitions = order(:created_at).pluck(:created_at)
|
85
|
+
return nil if transitions.size < 2
|
86
|
+
|
87
|
+
total_time = 0
|
88
|
+
(1...transitions.size).each do |i|
|
89
|
+
total_time += (transitions[i] - transitions[i - 1])
|
90
|
+
end
|
91
|
+
|
92
|
+
total_time / (transitions.size - 1)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Instance methods
|
97
|
+
|
98
|
+
# Get the duration since the previous transition
|
99
|
+
#
|
100
|
+
# @return [Float, nil] Duration in seconds since previous transition
|
101
|
+
def duration_since_previous
|
102
|
+
previous_transition = self.class.where(task_id: task_id)
|
103
|
+
.where(sort_key: ...sort_key)
|
104
|
+
.order(sort_key: :desc)
|
105
|
+
.first
|
106
|
+
|
107
|
+
return nil unless previous_transition
|
108
|
+
|
109
|
+
created_at - previous_transition.created_at
|
110
|
+
end
|
111
|
+
|
112
|
+
# Check if this transition represents an error state
|
113
|
+
#
|
114
|
+
# @return [Boolean] True if transitioning to an error state
|
115
|
+
def error_transition?
|
116
|
+
to_state == 'error'
|
117
|
+
end
|
118
|
+
|
119
|
+
# Check if this transition represents completion
|
120
|
+
#
|
121
|
+
# @return [Boolean] True if transitioning to a completion state
|
122
|
+
def completion_transition?
|
123
|
+
%w[complete resolved_manually].include?(to_state)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Check if this transition represents cancellation
|
127
|
+
#
|
128
|
+
# @return [Boolean] True if transitioning to cancelled state
|
129
|
+
def cancellation_transition?
|
130
|
+
to_state == 'cancelled'
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get human-readable description of the transition
|
134
|
+
#
|
135
|
+
# @return [String] Description of what this transition represents
|
136
|
+
def description
|
137
|
+
case to_state
|
138
|
+
when 'pending'
|
139
|
+
'Task initialized and ready for processing'
|
140
|
+
when 'in_progress'
|
141
|
+
'Task execution started'
|
142
|
+
when 'complete'
|
143
|
+
'Task completed successfully'
|
144
|
+
when 'error'
|
145
|
+
'Task encountered an error'
|
146
|
+
when 'cancelled'
|
147
|
+
'Task was cancelled'
|
148
|
+
when 'resolved_manually'
|
149
|
+
'Task was manually resolved'
|
150
|
+
else
|
151
|
+
"Task transitioned to #{to_state}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Get formatted metadata for display
|
156
|
+
#
|
157
|
+
# @return [Hash] Formatted metadata with additional computed fields
|
158
|
+
def formatted_metadata
|
159
|
+
base_metadata = metadata.dup
|
160
|
+
|
161
|
+
# Add computed fields
|
162
|
+
base_metadata['duration_since_previous'] = duration_since_previous
|
163
|
+
base_metadata['transition_description'] = description
|
164
|
+
base_metadata['transition_timestamp'] = created_at.iso8601
|
165
|
+
|
166
|
+
base_metadata
|
167
|
+
end
|
168
|
+
|
169
|
+
# Check if transition has specific metadata
|
170
|
+
#
|
171
|
+
# @param key [String, Symbol] The metadata key to check for
|
172
|
+
# @return [Boolean] True if the metadata contains the key
|
173
|
+
def has_metadata?(key)
|
174
|
+
metadata.key?(key.to_s)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Get metadata value with default
|
178
|
+
#
|
179
|
+
# @param key [String, Symbol] The metadata key
|
180
|
+
# @param default [Object] Default value if key not found
|
181
|
+
# @return [Object] The metadata value or default
|
182
|
+
def get_metadata(key, default = nil)
|
183
|
+
metadata.fetch(key.to_s, default)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Set metadata value
|
187
|
+
#
|
188
|
+
# @param key [String, Symbol] The metadata key
|
189
|
+
# @param value [Object] The value to set
|
190
|
+
# @return [Object] The set value
|
191
|
+
def set_metadata(key, value)
|
192
|
+
self.metadata = metadata.merge(key.to_s => value)
|
193
|
+
value
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
# Ensure metadata is always initialized as a hash
|
199
|
+
#
|
200
|
+
# @return [void]
|
201
|
+
def ensure_metadata_hash
|
202
|
+
self.metadata ||= {}
|
203
|
+
end
|
204
|
+
|
205
|
+
# Ensure metadata is present for validation
|
206
|
+
#
|
207
|
+
# @return [void]
|
208
|
+
def ensure_metadata_presence
|
209
|
+
self.metadata = {} if metadata.blank?
|
210
|
+
end
|
211
|
+
|
212
|
+
# Custom validation for metadata
|
213
|
+
#
|
214
|
+
# @return [void]
|
215
|
+
def metadata_must_be_hash
|
216
|
+
if metadata.nil?
|
217
|
+
self.metadata = {}
|
218
|
+
elsif !metadata.is_a?(Hash)
|
219
|
+
errors.add(:metadata, 'must be a hash')
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Validate that the task exists
|
224
|
+
#
|
225
|
+
# @return [void]
|
226
|
+
def task_must_exist
|
227
|
+
return if task_id.blank?
|
228
|
+
|
229
|
+
errors.add(:task, 'must exist before creating transition') unless Tasker::Task.exists?(task_id: task_id)
|
230
|
+
rescue ActiveRecord::StatementInvalid => e
|
231
|
+
# Handle cases where the table might not exist (e.g., during migrations)
|
232
|
+
Rails.logger.warn { "Could not validate task existence: #{e.message}" }
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,461 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../../../lib/tasker/state_machine/step_state_machine'
|
5
|
+
|
6
|
+
module Tasker
|
7
|
+
class WorkflowStep < ApplicationRecord
|
8
|
+
PROVIDES_EDGE_NAME = 'provides'
|
9
|
+
|
10
|
+
self.primary_key = :workflow_step_id
|
11
|
+
belongs_to :task
|
12
|
+
belongs_to :named_step
|
13
|
+
# belongs_to :depends_on_step, class_name: 'WorkflowStep', optional: true
|
14
|
+
has_many :incoming_edges,
|
15
|
+
class_name: 'WorkflowStepEdge',
|
16
|
+
foreign_key: :to_step_id,
|
17
|
+
dependent: :destroy,
|
18
|
+
inverse_of: :to_step
|
19
|
+
has_many :outgoing_edges,
|
20
|
+
class_name: 'WorkflowStepEdge',
|
21
|
+
foreign_key:
|
22
|
+
:from_step_id,
|
23
|
+
dependent: :destroy,
|
24
|
+
inverse_of: :from_step
|
25
|
+
has_many :parents, through: :incoming_edges, source: :from_step
|
26
|
+
has_many :children, through: :outgoing_edges, source: :to_step
|
27
|
+
has_many :siblings, through: :outgoing_edges, source: :from_step
|
28
|
+
has_many :workflow_step_transitions, inverse_of: :workflow_step, dependent: :destroy
|
29
|
+
|
30
|
+
validates :named_step_id, uniqueness: { scope: :task_id, message: 'must be unique within the same task' }
|
31
|
+
validate :name_uniqueness_within_task
|
32
|
+
|
33
|
+
delegate :name, to: :named_step
|
34
|
+
|
35
|
+
has_one :step_dag_relationship, class_name: 'Tasker::StepDagRelationship', primary_key: :workflow_step_id
|
36
|
+
# NOTE: step_readiness_status is now accessed via function-based approach, not ActiveRecord association
|
37
|
+
|
38
|
+
# Optimized scopes for efficient querying using state machine transitions
|
39
|
+
scope :completed, lambda {
|
40
|
+
joins(:workflow_step_transitions)
|
41
|
+
.where(
|
42
|
+
workflow_step_transitions: {
|
43
|
+
most_recent: true,
|
44
|
+
to_state: [
|
45
|
+
Constants::WorkflowStepStatuses::COMPLETE,
|
46
|
+
Constants::WorkflowStepStatuses::RESOLVED_MANUALLY
|
47
|
+
]
|
48
|
+
}
|
49
|
+
)
|
50
|
+
}
|
51
|
+
|
52
|
+
scope :failed, lambda {
|
53
|
+
joins(:workflow_step_transitions)
|
54
|
+
.where(
|
55
|
+
workflow_step_transitions: {
|
56
|
+
most_recent: true,
|
57
|
+
to_state: Constants::WorkflowStepStatuses::ERROR
|
58
|
+
}
|
59
|
+
)
|
60
|
+
}
|
61
|
+
|
62
|
+
scope :pending, lambda {
|
63
|
+
# Include steps with no transitions (initial state) AND steps with pending/in_progress transitions
|
64
|
+
where.missing(:workflow_step_transitions)
|
65
|
+
.or(
|
66
|
+
joins(:workflow_step_transitions)
|
67
|
+
.where(
|
68
|
+
workflow_step_transitions: {
|
69
|
+
most_recent: true,
|
70
|
+
to_state: [
|
71
|
+
Constants::WorkflowStepStatuses::PENDING,
|
72
|
+
Constants::WorkflowStepStatuses::IN_PROGRESS
|
73
|
+
]
|
74
|
+
}
|
75
|
+
)
|
76
|
+
)
|
77
|
+
}
|
78
|
+
|
79
|
+
scope :for_task, lambda { |task|
|
80
|
+
where(task_id: task.task_id)
|
81
|
+
}
|
82
|
+
|
83
|
+
# Scopes workflow steps by their current state using state machine transitions
|
84
|
+
#
|
85
|
+
# @scope class
|
86
|
+
# @param state [String, nil] The state to filter by. If nil, returns all steps with current state information
|
87
|
+
# @return [ActiveRecord::Relation] Steps with current state, optionally filtered by specific state
|
88
|
+
scope :by_current_state, lambda { |state = nil|
|
89
|
+
relation = joins(<<-SQL.squish)
|
90
|
+
INNER JOIN (
|
91
|
+
SELECT DISTINCT ON (workflow_step_id) workflow_step_id, to_state
|
92
|
+
FROM tasker_workflow_step_transitions
|
93
|
+
WHERE most_recent = true
|
94
|
+
ORDER BY workflow_step_id, sort_key DESC
|
95
|
+
) current_transitions ON current_transitions.workflow_step_id = tasker_workflow_steps.workflow_step_id
|
96
|
+
SQL
|
97
|
+
|
98
|
+
if state.present?
|
99
|
+
relation.where(current_transitions: { to_state: state })
|
100
|
+
else
|
101
|
+
relation
|
102
|
+
end
|
103
|
+
}
|
104
|
+
|
105
|
+
# Scopes workflow steps completed since a specific time
|
106
|
+
#
|
107
|
+
# @scope class
|
108
|
+
# @param since_time [Time] The earliest completion time to include
|
109
|
+
# @return [ActiveRecord::Relation] Steps completed since the specified time
|
110
|
+
scope :completed_since, lambda { |since_time|
|
111
|
+
joins(:workflow_step_transitions)
|
112
|
+
.where('tasker_workflow_step_transitions.most_recent = ? AND tasker_workflow_step_transitions.to_state = ?', true, 'complete')
|
113
|
+
.where('tasker_workflow_step_transitions.created_at > ?', since_time)
|
114
|
+
}
|
115
|
+
|
116
|
+
# Scopes workflow steps that failed since a specific time
|
117
|
+
#
|
118
|
+
# @scope class
|
119
|
+
# @param since_time [Time] The earliest failure time to include
|
120
|
+
# @return [ActiveRecord::Relation] Steps that failed since the specified time
|
121
|
+
scope :failed_since, lambda { |since_time|
|
122
|
+
joins(:workflow_step_transitions)
|
123
|
+
.where('tasker_workflow_step_transitions.most_recent = ? AND tasker_workflow_step_transitions.to_state = ?', true, 'error')
|
124
|
+
.where('tasker_workflow_step_transitions.created_at > ?', since_time)
|
125
|
+
}
|
126
|
+
|
127
|
+
# Scopes workflow steps for tasks created since a specific time
|
128
|
+
#
|
129
|
+
# @scope class
|
130
|
+
# @param since_time [Time] The earliest task creation time to include
|
131
|
+
# @return [ActiveRecord::Relation] Steps for tasks created since the specified time
|
132
|
+
scope :for_tasks_since, lambda { |since_time|
|
133
|
+
joins(:task).where('tasker_tasks.created_at > ?', since_time)
|
134
|
+
}
|
135
|
+
|
136
|
+
# Efficient method to get task completion statistics using ActiveRecord scopes
|
137
|
+
# This avoids the N+1 query problem while working with the state machine system
|
138
|
+
#
|
139
|
+
# @param task [Task] The task to analyze
|
140
|
+
# @return [Hash] Hash with completion statistics and latest completion time
|
141
|
+
def self.task_completion_stats(task)
|
142
|
+
# Use efficient ActiveRecord queries with the state machine
|
143
|
+
task_steps = for_task(task)
|
144
|
+
|
145
|
+
# Get completion statistics with optimized queries
|
146
|
+
total_steps = task_steps.count
|
147
|
+
completed_steps = task_steps.completed
|
148
|
+
failed_steps = task_steps.failed
|
149
|
+
|
150
|
+
# Calculate counts
|
151
|
+
completed_count = completed_steps.count
|
152
|
+
failed_count = failed_steps.count
|
153
|
+
|
154
|
+
# For pending count, calculate as total minus completed and failed
|
155
|
+
# This handles the case where new steps don't have transitions yet
|
156
|
+
pending_count = total_steps - completed_count - failed_count
|
157
|
+
|
158
|
+
# Get latest completion time from completed steps
|
159
|
+
latest_completion_time = completed_steps.maximum(:processed_at)
|
160
|
+
|
161
|
+
{
|
162
|
+
total_steps: total_steps,
|
163
|
+
completed_steps: completed_count,
|
164
|
+
failed_steps: failed_count,
|
165
|
+
pending_steps: pending_count,
|
166
|
+
latest_completion_time: latest_completion_time,
|
167
|
+
all_complete: completed_count == total_steps && total_steps.positive?
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
# State machine integration
|
172
|
+
def state_machine
|
173
|
+
@state_machine ||= Tasker::StateMachine::StepStateMachine.new(
|
174
|
+
self,
|
175
|
+
transition_class: Tasker::WorkflowStepTransition,
|
176
|
+
association_name: :workflow_step_transitions
|
177
|
+
)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Status is now entirely managed by the state machine
|
181
|
+
def status
|
182
|
+
if new_record?
|
183
|
+
# For new records, return the initial state
|
184
|
+
Tasker::Constants::WorkflowStepStatuses::PENDING
|
185
|
+
else
|
186
|
+
# For persisted records, use state machine
|
187
|
+
state_machine.current_state
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Finds a WorkflowStep with the given name by traversing the DAG efficiently
|
192
|
+
# @param steps [Array<WorkflowStep>] Collection of steps to search through
|
193
|
+
# @param name [String] Name of the step to find
|
194
|
+
# @return [WorkflowStep, nil] The first matching step found or nil if none exists
|
195
|
+
def self.find_step_by_name(steps, name)
|
196
|
+
StepFinder.find_by_name(steps, name)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Service class to find steps by name
|
200
|
+
# Reduces complexity by organizing step search logic
|
201
|
+
class StepFinder
|
202
|
+
class << self
|
203
|
+
# Find step by name in provided collection or task hierarchy
|
204
|
+
#
|
205
|
+
# @param steps [Array<WorkflowStep>] Collection of steps to search through
|
206
|
+
# @param name [String] Name of the step to find
|
207
|
+
# @return [WorkflowStep, nil] The first matching step found or nil if none exists
|
208
|
+
def find_by_name(steps, name)
|
209
|
+
return nil if steps.empty? || name.nil?
|
210
|
+
|
211
|
+
# First check direct match in provided steps
|
212
|
+
direct_match = find_direct_match(steps, name)
|
213
|
+
return direct_match if direct_match
|
214
|
+
|
215
|
+
# Fall back to task-wide search using DAG relationships
|
216
|
+
find_in_task_hierarchy(steps, name)
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
# Find direct match in provided steps
|
222
|
+
#
|
223
|
+
# @param steps [Array<WorkflowStep>] Collection of steps
|
224
|
+
# @param name [String] Name to search for
|
225
|
+
# @return [WorkflowStep, nil] Matching step or nil
|
226
|
+
def find_direct_match(steps, name)
|
227
|
+
steps.find { |step| step.name == name }
|
228
|
+
end
|
229
|
+
|
230
|
+
# Find step in task hierarchy using efficient DAG traversal
|
231
|
+
#
|
232
|
+
# @param steps [Array<WorkflowStep>] Collection of steps to get task context
|
233
|
+
# @param name [String] Name to search for
|
234
|
+
# @return [WorkflowStep, nil] Matching step or nil
|
235
|
+
def find_in_task_hierarchy(steps, name)
|
236
|
+
task_ids = extract_task_ids(steps)
|
237
|
+
|
238
|
+
task_ids.each do |task_id|
|
239
|
+
found_step = find_in_single_task(task_id, name)
|
240
|
+
return found_step if found_step
|
241
|
+
end
|
242
|
+
|
243
|
+
nil
|
244
|
+
end
|
245
|
+
|
246
|
+
# Extract unique task IDs from steps
|
247
|
+
#
|
248
|
+
# @param steps [Array<WorkflowStep>] Collection of steps
|
249
|
+
# @return [Array<Integer>] Unique task IDs
|
250
|
+
def extract_task_ids(steps)
|
251
|
+
steps.map(&:task_id).uniq
|
252
|
+
end
|
253
|
+
|
254
|
+
# Find step by name in a single task
|
255
|
+
#
|
256
|
+
# @param task_id [Integer] Task ID to search in
|
257
|
+
# @param name [String] Name to search for
|
258
|
+
# @return [WorkflowStep, nil] Matching step or nil
|
259
|
+
def find_in_single_task(task_id, name)
|
260
|
+
# Get all workflow steps for this task with their DAG relationships
|
261
|
+
all_task_steps = WorkflowStep.joins(:named_step)
|
262
|
+
.includes(:step_dag_relationship)
|
263
|
+
.where(task_id: task_id)
|
264
|
+
|
265
|
+
# Find step by name using simple lookup instead of recursive traversal
|
266
|
+
all_task_steps.joins(:named_step)
|
267
|
+
.find_by(named_steps: { name: name })
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def self.get_steps_for_task(task, templates)
|
273
|
+
named_steps = NamedStep.create_named_steps_from_templates(templates)
|
274
|
+
steps =
|
275
|
+
templates.map do |template|
|
276
|
+
named_step = named_steps.find { |ns| template.name == ns.name }
|
277
|
+
NamedTasksNamedStep.associate_named_step_with_named_task(task.named_task, template, named_step)
|
278
|
+
step = where(task_id: task.task_id, named_step_id: named_step.named_step_id).first
|
279
|
+
step ||= build_default_step!(task, template, named_step)
|
280
|
+
step
|
281
|
+
end
|
282
|
+
set_up_dependent_steps(steps, templates)
|
283
|
+
end
|
284
|
+
|
285
|
+
def self.set_up_dependent_steps(steps, templates)
|
286
|
+
templates.each do |template|
|
287
|
+
next if template.all_dependencies.empty?
|
288
|
+
|
289
|
+
dependent_step = steps.find { |step| step.name == template.name }
|
290
|
+
template.all_dependencies.each do |dependency|
|
291
|
+
provider_step = steps.find { |step| step.name == dependency }
|
292
|
+
unless provider_step.outgoing_edges.exists?(to_step: dependent_step)
|
293
|
+
provider_step.add_provides_edge!(dependent_step)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
steps
|
298
|
+
end
|
299
|
+
|
300
|
+
def self.build_default_step!(task, template, named_step)
|
301
|
+
# Create the step first without status
|
302
|
+
step_attributes = {
|
303
|
+
task_id: task.task_id,
|
304
|
+
named_step_id: named_step.named_step_id,
|
305
|
+
retryable: template.default_retryable,
|
306
|
+
retry_limit: template.default_retry_limit,
|
307
|
+
skippable: template.skippable,
|
308
|
+
in_process: false,
|
309
|
+
inputs: task.context,
|
310
|
+
processed: false,
|
311
|
+
attempts: 0,
|
312
|
+
results: {}
|
313
|
+
}
|
314
|
+
|
315
|
+
step = new(step_attributes)
|
316
|
+
step.save!
|
317
|
+
|
318
|
+
# REMOVED: Automatic state machine initialization to prevent duplicate key violations
|
319
|
+
# The state machine will initialize naturally when accessed, and factories may
|
320
|
+
# have already created transitions through their own setup
|
321
|
+
# step.state_machine.initialize_state_machine!
|
322
|
+
|
323
|
+
step
|
324
|
+
end
|
325
|
+
|
326
|
+
def self.get_viable_steps(task, sequence)
|
327
|
+
# Get step IDs from sequence
|
328
|
+
step_ids = sequence.steps.map(&:workflow_step_id)
|
329
|
+
|
330
|
+
# Use function-based approach for high-performance readiness checking
|
331
|
+
ready_statuses = StepReadinessStatus.for_task(task.task_id, step_ids)
|
332
|
+
ready_step_ids = ready_statuses.select(&:ready_for_execution).map(&:workflow_step_id)
|
333
|
+
|
334
|
+
# Return WorkflowStep objects for ready steps
|
335
|
+
WorkflowStep.where(workflow_step_id: ready_step_ids)
|
336
|
+
.includes(:named_step)
|
337
|
+
end
|
338
|
+
|
339
|
+
def add_provides_edge!(to_step)
|
340
|
+
outgoing_edges.create!(to_step: to_step, name: PROVIDES_EDGE_NAME)
|
341
|
+
end
|
342
|
+
|
343
|
+
# Helper method to get step readiness status using function-based approach
|
344
|
+
def step_readiness_status
|
345
|
+
@step_readiness_status ||= StepReadinessStatus.for_task(task_id, [workflow_step_id]).first
|
346
|
+
end
|
347
|
+
|
348
|
+
def complete?
|
349
|
+
# Use function-based approach for consistent state checking
|
350
|
+
step_readiness_status&.current_state&.in?([
|
351
|
+
Constants::WorkflowStepStatuses::COMPLETE,
|
352
|
+
Constants::WorkflowStepStatuses::RESOLVED_MANUALLY
|
353
|
+
]) || false
|
354
|
+
end
|
355
|
+
|
356
|
+
def in_progress?
|
357
|
+
# Use function-based approach for consistent state checking
|
358
|
+
step_readiness_status&.current_state == Constants::WorkflowStepStatuses::IN_PROGRESS
|
359
|
+
end
|
360
|
+
|
361
|
+
def pending?
|
362
|
+
# Use function-based approach for consistent state checking
|
363
|
+
step_readiness_status&.current_state == Constants::WorkflowStepStatuses::PENDING
|
364
|
+
end
|
365
|
+
|
366
|
+
def in_error?
|
367
|
+
# Use function-based approach for consistent state checking
|
368
|
+
step_readiness_status&.current_state == Constants::WorkflowStepStatuses::ERROR
|
369
|
+
end
|
370
|
+
|
371
|
+
def cancelled?
|
372
|
+
# Use function-based approach for consistent state checking
|
373
|
+
step_readiness_status&.current_state == Constants::WorkflowStepStatuses::CANCELLED
|
374
|
+
end
|
375
|
+
|
376
|
+
def ready_status?
|
377
|
+
# Use function-based approach for efficient ready status checking
|
378
|
+
Constants::UNREADY_WORKFLOW_STEP_STATUSES.exclude?(
|
379
|
+
step_readiness_status&.current_state || Constants::WorkflowStepStatuses::PENDING
|
380
|
+
)
|
381
|
+
end
|
382
|
+
|
383
|
+
def ready?
|
384
|
+
# Use function-based approach's comprehensive readiness calculation
|
385
|
+
step_readiness_status&.ready_for_execution || false
|
386
|
+
end
|
387
|
+
|
388
|
+
# Function-based predicate methods
|
389
|
+
def dependencies_satisfied?
|
390
|
+
# Use function-based approach's pre-calculated dependency analysis
|
391
|
+
step_readiness_status&.dependencies_satisfied || false
|
392
|
+
end
|
393
|
+
|
394
|
+
def retry_eligible?
|
395
|
+
# Use function-based approach's retry/backoff calculation
|
396
|
+
step_readiness_status&.retry_eligible || false
|
397
|
+
end
|
398
|
+
|
399
|
+
def has_retry_attempts?
|
400
|
+
# Check if step has made retry attempts
|
401
|
+
(step_readiness_status&.attempts || 0).positive?
|
402
|
+
end
|
403
|
+
|
404
|
+
def retry_exhausted?
|
405
|
+
# Check if step has exhausted retry attempts
|
406
|
+
return false unless step_readiness_status
|
407
|
+
|
408
|
+
attempts = step_readiness_status.attempts || 0
|
409
|
+
retry_limit = step_readiness_status.retry_limit || 3
|
410
|
+
attempts >= retry_limit
|
411
|
+
end
|
412
|
+
|
413
|
+
def waiting_for_backoff?
|
414
|
+
# Check if step is waiting for backoff period to expire
|
415
|
+
return false unless step_readiness_status&.next_retry_at
|
416
|
+
|
417
|
+
step_readiness_status.next_retry_at > Time.current
|
418
|
+
end
|
419
|
+
|
420
|
+
def can_retry_now?
|
421
|
+
# Comprehensive check if step can be retried right now
|
422
|
+
return false unless in_error?
|
423
|
+
return false unless retry_eligible?
|
424
|
+
return false if waiting_for_backoff?
|
425
|
+
|
426
|
+
true
|
427
|
+
end
|
428
|
+
|
429
|
+
def root_step?
|
430
|
+
# Check if this is a root step (no dependencies)
|
431
|
+
(step_readiness_status&.total_parents || 0).zero?
|
432
|
+
end
|
433
|
+
|
434
|
+
def leaf_step?
|
435
|
+
# Check if this is a leaf step using DAG relationship view
|
436
|
+
step_dag_relationship&.child_count&.zero?
|
437
|
+
end
|
438
|
+
|
439
|
+
def reload
|
440
|
+
# Override reload to ensure step readiness status is refreshed
|
441
|
+
super.tap do
|
442
|
+
@step_readiness_status = nil # Reset cached readiness status
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
private
|
447
|
+
|
448
|
+
# Custom validation to ensure step names are unique within a task
|
449
|
+
def name_uniqueness_within_task
|
450
|
+
return unless named_step && task
|
451
|
+
|
452
|
+
# Find all steps within the same task that have the same name
|
453
|
+
matching_steps = self.class.where(task_id: task_id)
|
454
|
+
.joins(:named_step)
|
455
|
+
.where(named_step: { name: name })
|
456
|
+
.where.not(workflow_step_id: workflow_step_id) # Exclude self when updating
|
457
|
+
|
458
|
+
errors.add(:base, "Step name '#{name}' must be unique within the same task") if matching_steps.exists?
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|