using System.Runtime.CompilerServices; using System.Text.Json.Serialization; using Elsa.Common.Services; using Elsa.Expressions.Models; using Elsa.Workflows.Core; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Models; using Microsoft.Extensions.Logging; namespace Elsa.Scheduling.Activities; /// /// Triggers the workflow at a specific future timestamp. /// [Activity("Elsa", "Scheduling", "Trigger execution at a specific time in the future.")] public class StartAt : Trigger { private const string InputKey = "ExecuteAt"; /// [JsonConstructor] public StartAt([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) { } /// public StartAt(Input dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => DateTime = dateTime; /// public StartAt(Func dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input(dateTime), source, line) { } /// public StartAt( Func> dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input(dateTime), source, line) { } /// public StartAt(Func> dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input(dateTime), source, line) { } /// public StartAt(Func dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(new Input(dateTime), source, line) { } /// public StartAt(DateTimeOffset dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => DateTime = new Input(dateTime); /// public StartAt(Variable dateTime, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : this(source, line) => DateTime = new Input(dateTime); /// /// The timestamp at which the workflow should be triggered. /// [Input] public Input DateTime { get; set; } = default!; /// protected override object GetTriggerPayload(TriggerIndexingContext context) { var executeAt = context.ExpressionExecutionContext.Get(DateTime); return new StartAtPayload(executeAt); } /// protected override void Execute(ActivityExecutionContext context) { // If external input was received, it means this activity got triggered and does not need to create a bookmark. if (context.TryGetInput(InputKey, out _)) return; // No external input received, so create a bookmark. var executeAt = context.ExpressionExecutionContext.Get(DateTime); var clock = context.ExpressionExecutionContext.GetRequiredService(); var now = clock.UtcNow; var logger = context.GetRequiredService>(); if (executeAt <= now) { logger.LogDebug("Scheduled trigger time lies in the past ('{Delta}'). Skipping scheduling", now - executeAt); context.JournalData.Add("Executed At", now); return; } var payload = new StartAtPayload(executeAt); context.CreateBookmark(payload); } /// /// Creates a new activity set to trigger at the specified timestamp. /// public static StartAt From(DateTimeOffset value) => new(value); } internal record StartAtPayload(DateTimeOffset ExecuteAt);