/// <summary> /// Instantiates a background operation task queue directive that /// may include a wrapped internal directive. /// </summary> /// <param name="backgroundOperationName">The name of the background operation on which this task is run.</param> /// <param name="internalDirective">The directive - if any - to pass to the job.</param> public BackgroundOperationTaskDirective ( string backgroundOperationName, TaskDirective internalDirective ) { this.BackgroundOperationName = backgroundOperationName; this.InternalDirective = internalDirective; }
/// <summary> /// Cancels any future executions of tasks of type <paramref name="taskType"/> on queue <paramref name="queueId"/>. /// If <paramref name="scheduleFor"/> has a value then a new execution of the task is scheduled for this date/time. /// </summary> /// <param name="queueId">The queue that the task should be rescheduled on.</param> /// <param name="taskType">The task type to be rescheduled.</param> /// <param name="innerDirective">The inner directive to be passed to the rescheduled task.</param> /// <param name="vault">The vault reference to use for the operation.</param> /// <param name="scheduleFor">The date/time to schedule a new execution for. If <see langword="null"/>, does not schedule a future execution.</param> /// <remarks>Adds an item to the scheduling queue, so that only one server performs this operation.</remarks> public virtual void RescheduleTask(string queueId, string taskType, TaskDirective innerDirective = null, Vault vault = null, DateTime?scheduleFor = null) => this.AddTask ( vault ?? this.VaultApplication.PermanentVault, this.VaultApplication.GetSchedulerQueueID(), this.VaultApplication.GetRescheduleTaskType(), new RescheduleProcessorTaskDirective() { QueueID = queueId, TaskType = taskType, NextExecution = scheduleFor, InnerDirective = innerDirective } );
/// <summary> /// Creates a new background operation and starts it. /// The background operation runs the given method according to the <paramref name="schedule"/>. /// </summary> /// <param name="name">The name of the operation.</param> /// <param name="schedule">The schedule that defines when the operation should run.</param> /// <param name="method">The method to invoke at given intervals.</param> /// <param name="directive">The directive to pass to the job.</param> /// <returns>A started background operation.</returns> public TaskQueueBackgroundOperation <TSecureConfiguration> StartScheduledBackgroundOperation ( string name, Schedule schedule, Action <TaskProcessorJobEx <BackgroundOperationTaskDirective, TSecureConfiguration>, TaskDirective> method, TaskDirective directive = null ) { return(this.StartScheduledBackgroundOperation <TaskDirective> ( name, schedule, method, directive )); }
/// <summary> /// Creates a new background operation and starts it. The background operation runs the given method at given intervals. /// </summary> /// <param name="name">The name of the operation.</param> /// <param name="interval">The target interval between method calls. If the method call takes longer than the interval, the method will be invoked immediately after the previous method call returns.</param> /// <param name="method">The method to invoke at given intervals.</param> /// <param name="directive">The directive to pass to the job.</param> /// <param name="options">The options for the display of the background operation in the dashboard.</param> /// <returns>A started background operation.</returns> public TaskQueueBackgroundOperation <TSecureConfiguration> StartRecurringBackgroundOperation ( string name, TimeSpan interval, Action <TaskProcessorJobEx <BackgroundOperationTaskDirective, TSecureConfiguration>, TaskDirective> method, TaskDirective directive = null ) { return(this.StartRecurringBackgroundOperation <TaskDirective> ( name, interval, method, directive )); }
/// <summary> /// Runs the operation at once or immediately after the current run is finished. /// </summary> /// <param name="backgroundOperationName">The name of the background operation that should be invoked when this job is run.</param> /// <param name="runAt">If specified, schedules an execution at the provided time. Otherwise schedules a call immediately.</param> /// <param name="directive">The directive - if any - to pass to the job.</param> /// <param name="vault">The vault reference to add the task. Set to a transactional vault to only run the task if the transaction completes.</param> /// <remarks>Does not remove any scheduled executions. Use <see cref="StopRunningAtIntervals"/>.</remarks> public void RunOnce ( string backgroundOperationName, DateTime?runAt = null, TaskDirective directive = null, Vault vault = null ) { // Use the other overload. this.RunOnce <TaskDirective> ( backgroundOperationName, runAt, directive, vault ); }
/// <summary> /// Creates a <see cref="DashboardTable"/> containing information about the executions /// detailed in <paramref name="applicationTasks"/>. /// </summary> /// <param name="applicationTasks">The previous executions.</param> /// <returns>The table.</returns> public static IDashboardContent AsDashboardContent <TDirective> ( this IEnumerable <TaskInfo <TDirective> > applicationTasks, int maximumRowsToShow = 40 ) where TDirective : TaskDirective { // Sanity. if (null == applicationTasks || false == applicationTasks.Any()) { return(null); } var list = applicationTasks .OrderByDescending(e => e.LatestActivity) .ToList(); // Create the table and header row. DashboardTable table = new DashboardTable(); { var header = table.AddRow(DashboardTableRowType.Header); header.AddCells ( new DashboardCustomContent("Task"), new DashboardCustomContent("Scheduled"), new DashboardCustomContent("Duration"), new DashboardCustomContent("Details") ); } List <TaskInfo <TDirective> > executionsToShow; bool isFiltered = false; if (list.Count <= maximumRowsToShow) { executionsToShow = list; } else { isFiltered = true; // Show the latest 20 errors, then the rest filled with non-errors. executionsToShow = new List <TaskInfo <TDirective> > ( list .Where(e => e.State == MFTaskState.MFTaskStateFailed) .Take(20) .Union(list.Where(e => e.State != MFTaskState.MFTaskStateFailed).Take(maximumRowsToShow - 20)) ); } // Add a row for each execution to show. foreach (var execution in executionsToShow) { TaskDirective internalDirective = execution.Directive; { if (internalDirective is BackgroundOperationTaskDirective bgtd) { internalDirective = bgtd.GetParsedInternalDirective(); } } var directive = internalDirective as ITaskDirectiveWithDisplayName; var displayName = string.IsNullOrWhiteSpace(directive?.DisplayName) ? execution.TaskId : directive.DisplayName; var activation = execution.ActivationTime; TaskInformation taskInfo = null == execution.Status?.Data ? new TaskInformation() : new TaskInformation(execution.Status.Data); // Create the content for the scheduled column (including icon). var taskInfoCell = new DashboardCustomContentEx(System.Security.SecurityElement.Escape(displayName)); var scheduledCell = new DashboardCustomContentEx ( activation.ToTimeOffset ( // If we are waiting for it to start then highlight that. execution.State == MFilesAPI.MFTaskState.MFTaskStateWaiting ? FormattingExtensionMethods.DateTimeRepresentationOf.NextRun : FormattingExtensionMethods.DateTimeRepresentationOf.LastRun ) ); // Copy data from the execution if needed. taskInfo.Started = taskInfo.Started ?? execution.ActivationTime; taskInfo.LastActivity = taskInfo.LastActivity ?? execution.Status?.EndedAt ?? execution.Status?.LastUpdatedAt ?? DateTime.UtcNow; taskInfo.StatusDetails = taskInfo.StatusDetails ?? execution.Status?.Details; taskInfo.PercentageComplete = taskInfo.PercentageComplete ?? execution.Status?.PercentComplete; if (taskInfo.CurrentTaskState != execution.State) { taskInfo.CurrentTaskState = execution.State; } var removeLineBreaks = false; // By default show the full text as sent. if (taskInfo.CurrentTaskState == MFTaskState.MFTaskStateFailed) { taskInfo.StatusDetails = execution.Status?.ErrorMessage ?? taskInfo.StatusDetails; removeLineBreaks = true; // Exceptions are LONG, so format them. } // Add a row for this execution. var row = table.AddRow(); // Set the row title. var rowTitle = ""; switch (execution.State) { case MFilesAPI.MFTaskState.MFTaskStateWaiting: taskInfoCell.Icon = "Resources/Waiting.png"; rowTitle = $"Waiting. Will start at approximately {activation.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")}."; break; case MFilesAPI.MFTaskState.MFTaskStateInProgress: rowTitle = "Running."; if ((taskInfo?.Started.HasValue) ?? false) { rowTitle += $" Started at approximately {taskInfo.Started.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")} server-time (taken {taskInfo.GetElapsedTime().ToDisplayString()} so far)."; } taskInfoCell.Icon = "Resources/Running.png"; break; case MFilesAPI.MFTaskState.MFTaskStateFailed: rowTitle = "Failed."; if ((taskInfo?.Started.HasValue) ?? false) { rowTitle += $" Started at approximately {taskInfo.Started.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")} server-time (took {taskInfo.GetElapsedTime().ToDisplayString()})."; } taskInfoCell.Icon = "Resources/Failed.png"; break; case MFilesAPI.MFTaskState.MFTaskStateCompleted: rowTitle = "Completed."; if ((taskInfo?.Started.HasValue) ?? false) { rowTitle += $" Started at approximately {taskInfo.Started.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")} server-time (took {taskInfo.GetElapsedTime().ToDisplayString()})."; } taskInfoCell.Icon = "Resources/Completed.png"; break; default: break; } row.Attributes.Add("title", rowTitle); // Add the cells to the row. row.AddCells ( taskInfoCell, scheduledCell, new DashboardCustomContent(execution.State == MFilesAPI.MFTaskState.MFTaskStateWaiting ? "" : taskInfo?.GetElapsedTime().ToDisplayString()), taskInfo?.AsDashboardContent(removeLineBreaks) ); // First three cells should be as small as possible. row.Cells[0].Styles.AddOrUpdate("width", "1%"); row.Cells[0].Styles.AddOrUpdate("white-space", "nowrap"); row.Cells[1].Styles.AddOrUpdate("width", "1%"); row.Cells[1].Styles.AddOrUpdate("white-space", "nowrap"); row.Cells[2].Styles.AddOrUpdate("width", "1%"); row.Cells[2].Styles.AddOrUpdate("white-space", "nowrap"); // Last cell should have as much space as possible. row.Cells[3].Styles.AddOrUpdate("width", "100%"); } // Create an overview of the statuses. var data = list.GroupBy(e => e.State).ToDictionary(e => e.Key, e => e.Count()); var overview = new DashboardTable(); { // Remove all styles - we only are using this for layout. overview.Styles.Clear(); overview.Styles.AddOrUpdate("width", "100%"); // Add a single row. var row = overview.AddRow(); // The first cell contains text if this table is filtered, or is empty otherwise. var cell1 = row.AddCell(); if (isFiltered) { cell1.InnerContent = new DashboardCustomContentEx($"<p style='font-size: 12px'><em>This table shows only {maximumRowsToShow} of {list.Count} tasks.</em></p>"); } // The second cell contains the totals. var cell2 = row.AddCell(new DashboardCustomContentEx ( "<span>Totals: </span>" + $"<span title='{(data.ContainsKey(MFTaskState.MFTaskStateWaiting) ? data[MFTaskState.MFTaskStateWaiting] : 0)} awaiting processing' style=\"display: inline-block; margin: 0px 2px; background-image: url({DashboardHelpersEx.ImageFileToDataUri("Resources/Waiting.png")}); background-repeat: no-repeat; background-position: 0 center; padding-left: 20px\">{(data.ContainsKey(MFTaskState.MFTaskStateWaiting) ? data[MFTaskState.MFTaskStateWaiting] : 0)}</span>" + $"<span title='{(data.ContainsKey(MFTaskState.MFTaskStateInProgress) ? data[MFTaskState.MFTaskStateInProgress] : 0)} running' style=\"display: inline-block; margin: 0px 2px; background-image: url({DashboardHelpersEx.ImageFileToDataUri("Resources/Running.png")}); background-repeat: no-repeat; background-position: 0 center; padding-left: 20px\">{(data.ContainsKey(MFTaskState.MFTaskStateInProgress) ? data[MFTaskState.MFTaskStateInProgress] : 0)}</span>" + $"<span title='{(data.ContainsKey(MFTaskState.MFTaskStateCompleted) ? data[MFTaskState.MFTaskStateCompleted] : 0)} completed' style=\"display: inline-block; margin: 0px 2px; background-image: url({DashboardHelpersEx.ImageFileToDataUri("Resources/Completed.png")}); background-repeat: no-repeat; background-position: 0 center; padding-left: 20px\">{(data.ContainsKey(MFTaskState.MFTaskStateCompleted) ? data[MFTaskState.MFTaskStateCompleted] : 0)}</span>" + $"<span title='{(data.ContainsKey(MFTaskState.MFTaskStateFailed) ? data[MFTaskState.MFTaskStateFailed] : 0)} failed' style=\"display: inline-block; margin: 0px 2px; background-image: url({DashboardHelpersEx.ImageFileToDataUri("Resources/Failed.png")}); background-repeat: no-repeat; background-position: 0 center; padding-left: 20px\">{(data.ContainsKey(MFTaskState.MFTaskStateFailed) ? data[MFTaskState.MFTaskStateFailed] : 0)}</span>" )); cell2.Styles.AddOrUpdate("text-align", "right"); } // Return the content. return(new DashboardContentCollection() { table, overview }); }
/// <summary> /// Returns the directive of the <paramref name="taskInfo"/> a parsed/populated instance. /// </summary> /// <param name="applicationTaskInfo">The task to retrieve data for.</param> /// <returns>The directive, or null if no directive is found.</returns> public static TaskDirective GetDirective(this ApplicationTaskInfo applicationTaskInfo) { return(TaskDirective.Parse <BackgroundOperationTaskDirective>(applicationTaskInfo.TaskData)?.GetParsedInternalDirective()); }