/// <summary>
        /// Creates either a <see cref="DashboardProgressBar"/> or <see cref="DashboardCustomContent"/>
        /// to render the current task information on a dashboard.
        /// </summary>
        /// <returns>The dashboard content.</returns>
        public IDashboardContent AsDashboardContent(bool removeLineBreaks)
        {
            // If we have a progress then do a pretty bar chart.
            if (null != this.PercentageComplete)
            {
                var progressBar = new DashboardProgressBar()
                {
                    PercentageComplete = this.PercentageComplete.Value,
                    Text      = this.StatusDetails,
                    TaskState = this.CurrentTaskState
                };
                return(progressBar);
            }
            else if (false == string.IsNullOrWhiteSpace(this.StatusDetails))
            {
                // Otherwise just show the text.
                var dashboardCustomContentEx = new DashboardCustomContentEx
                                               (
                    new DashboardCustomContent(System.Security.SecurityElement.Escape(this.StatusDetails))
                                               );
                if (removeLineBreaks)
                {
                    dashboardCustomContentEx.Styles.Add("white-space", "nowrap");
                    dashboardCustomContentEx.Styles.Add("overflow", "hidden");
                    dashboardCustomContentEx.Styles.Add("display", "inline-block");
                    dashboardCustomContentEx.Styles.Add("text-overflow", "ellipsis");
                }
                return(dashboardCustomContentEx);
            }

            // Return nothing.
            return(null);
        }
        /// <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
            });
        }