< Summary

Information
Class: Elsa.Dashboard.Api.Services.DefaultDashboardProvider
Assembly: Elsa.Dashboard.Api
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Dashboard.Api/Services/DefaultDashboardProvider.cs
Line coverage
82%
Covered lines: 111
Uncovered lines: 24
Coverable lines: 135
Total lines: 246
Line coverage: 82.2%
Branch coverage
70%
Covered branches: 21
Total branches: 30
Branch coverage: 70%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetOverviewAsync()100%66100%
GetWorkflowTrendsAsync()100%11100%
GetNeedsAttentionAsync()100%11100%
GetRecentActivityAsync()100%11100%
GetWorkflowHotspotsAsync()100%210%
get_OrderedContributors()100%11100%
CreateContext(...)100%11100%
CollectAsync()100%44100%
CollectManyAsync()100%44100%
ExecuteContributorAsync()100%11100%
MergeRuntime(...)100%44100%
MergeWorkflowMetrics(...)100%11100%
MergeDiagnostics(...)100%44100%
AverageDuration(...)50%22100%
NormalizeHotspotMetric(...)0%7280%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Dashboard.Api/Services/DefaultDashboardProvider.cs

#LineLine coverage
 1using Elsa.Dashboard.Abstractions.Contracts;
 2using Elsa.Dashboard.Abstractions.Models;
 3using Microsoft.Extensions.Hosting;
 4
 5namespace Elsa.Dashboard.Api.Services;
 6
 107public class DefaultDashboardProvider(
 108    IEnumerable<IDashboardContributor> contributors,
 109    DashboardRangeResolver rangeResolver,
 1010    IHostEnvironment environment) : IDashboardProvider
 11{
 12    public async Task<DashboardOverview> GetOverviewAsync(DashboardQuery query, CancellationToken cancellationToken = de
 13    {
 414        var range = rangeResolver.Resolve(query.Range);
 415        var context = CreateContext(range, query.IncludeSystem, cancellationToken);
 416        var contributions = new List<DashboardOverviewContribution>();
 17
 1718        foreach (var contributor in OrderedContributors)
 19        {
 1020            var contribution = await ExecuteContributorAsync(contributor, x => x.GetOverviewAsync(context).AsTask(), can
 421            if (contribution != null)
 322                contributions.Add(contribution);
 23        }
 24
 325        return new()
 326        {
 327            BackendName = environment.ApplicationName,
 328            EnvironmentName = environment.EnvironmentName,
 329            Runtime = MergeRuntime(contributions),
 330            WorkflowInstances = MergeWorkflowMetrics(contributions),
 331            Diagnostics = MergeDiagnostics(contributions),
 32            Metrics = contributions.SelectMany(x => x.Metrics).OrderBy(x => x.Order).ThenBy(x => x.Id, StringComparer.Or
 33            Panels = contributions.SelectMany(x => x.Panels).OrderBy(x => x.Order).ThenBy(x => x.Id, StringComparer.Ordi
 334            AppliedRange = range.Key,
 335            From = range.From,
 336            To = range.To
 337        };
 338    }
 39
 40    public async Task<DashboardTrendResponse> GetWorkflowTrendsAsync(DashboardTrendRequest request, CancellationToken ca
 41    {
 142        var range = rangeResolver.Resolve(request.Range);
 143        var granularity = rangeResolver.ResolveGranularity(request.Granularity, range.Key);
 144        var context = new DashboardTrendContext(range, granularity, request.IncludeSystem, cancellationToken, Environmen
 345        var responses = await CollectAsync(contributor => contributor.GetWorkflowTrendsAsync(context).AsTask(), cancella
 146        var buckets = responses
 47            .SelectMany(x => x.Buckets)
 48            .GroupBy(x => new { x.From, x.To })
 49            .Select(x => new DashboardTrendBucket
 50            {
 51                From = x.Key.From,
 52                To = x.Key.To,
 53                CreatedOrStarted = x.Sum(y => y.CreatedOrStarted),
 54                Finished = x.Sum(y => y.Finished),
 55                Faulted = x.Sum(y => y.Faulted),
 56                Suspended = x.Sum(y => y.Suspended),
 57                IncidentBearing = x.Sum(y => y.IncidentBearing)
 58            })
 59            .OrderBy(x => x.From)
 160            .ToList();
 61
 162        return new()
 163        {
 164            Buckets = buckets,
 165            AppliedRange = range.Key,
 166            Granularity = granularity,
 167            From = range.From,
 168            To = range.To
 169        };
 170    }
 71
 72    public async Task<DashboardNeedsAttentionResponse> GetNeedsAttentionAsync(DashboardQuery query, int take, Cancellati
 73    {
 274        var range = rangeResolver.Resolve(query.Range);
 275        var context = CreateContext(range, query.IncludeSystem, cancellationToken);
 676        var findings = await CollectManyAsync(contributor => contributor.GetFindingsAsync(context).AsTask(), cancellatio
 77
 278        return new()
 279        {
 280            Findings = findings
 81                .OrderBy(x => x.Priority)
 82                .ThenBy(x => x.Id, StringComparer.Ordinal)
 283                .Take(Math.Clamp(take, 1, 50))
 284                .ToList(),
 285            AppliedRange = range.Key
 286        };
 287    }
 88
 89    public async Task<DashboardRecentActivityResponse> GetRecentActivityAsync(DashboardQuery query, int take, Cancellati
 90    {
 191        var range = rangeResolver.Resolve(query.Range);
 192        var context = new DashboardListContext(range, Math.Clamp(take, 1, 100), query.IncludeSystem, cancellationToken, 
 393        var responses = await CollectAsync(contributor => contributor.GetRecentActivityAsync(context).AsTask(), cancella
 194        var items = responses
 95            .SelectMany(x => x.Items)
 96            .OrderByDescending(x => x.UpdatedAt ?? x.FinishedAt ?? x.CreatedAt)
 97            .ThenBy(x => x.InstanceId, StringComparer.Ordinal)
 198            .Take(context.Take)
 199            .ToList();
 100
 1101        return new()
 1102        {
 1103            Items = items,
 1104            AppliedRange = range.Key,
 1105            From = range.From,
 1106            To = range.To
 1107        };
 1108    }
 109
 110    public async Task<DashboardWorkflowHotspotsResponse> GetWorkflowHotspotsAsync(DashboardWorkflowHotspotsRequest reque
 111    {
 0112        var range = rangeResolver.Resolve(request.Range);
 0113        var metric = NormalizeHotspotMetric(request.Metric);
 0114        var take = Math.Clamp(request.Take, 1, 50);
 0115        var context = new DashboardHotspotsContext(range, metric, take, request.IncludeSystem, cancellationToken, Enviro
 0116        var responses = await CollectAsync(contributor => contributor.GetWorkflowHotspotsAsync(context).AsTask(), cancel
 0117        var items = responses
 118            .SelectMany(x => x.Items)
 119            .GroupBy(x => x.DefinitionId)
 120            .Select(x => new DashboardHotspot
 121            {
 122                DefinitionId = x.Key,
 123                WorkflowName = x.Select(y => y.WorkflowName).FirstOrDefault(y => !string.IsNullOrWhiteSpace(y)),
 124                Value = x.Sum(y => y.Value),
 125                AverageDuration = AverageDuration(x.Select(y => y.AverageDuration))
 126            })
 127            .OrderByDescending(x => x.Value)
 128            .ThenBy(x => x.WorkflowName, StringComparer.Ordinal)
 0129            .Take(take)
 0130            .ToList();
 131
 0132        return new()
 0133        {
 0134            Items = items,
 0135            AppliedRange = range.Key,
 0136            Metric = metric,
 0137            From = range.From,
 0138            To = range.To
 0139        };
 0140    }
 141
 142    private IReadOnlyCollection<IDashboardContributor> OrderedContributors =>
 8143        contributors
 12144            .OrderBy(x => x.Order)
 12145            .ThenBy(x => x.Id, StringComparer.Ordinal)
 8146            .ToList();
 147
 148    private DashboardContext CreateContext(DashboardRange range, bool includeSystem, CancellationToken cancellationToken
 6149        new(range, includeSystem, cancellationToken, EnvironmentName: environment.EnvironmentName);
 150
 151    private async Task<IReadOnlyCollection<T>> CollectAsync<T>(
 152        Func<IDashboardContributor, Task<T?>> action,
 153        CancellationToken cancellationToken)
 154        where T : class
 155    {
 2156        var results = new List<T>();
 12157        foreach (var contributor in OrderedContributors)
 158        {
 4159            var result = await ExecuteContributorAsync(contributor, action, cancellationToken);
 4160            if (result != null)
 4161                results.Add(result);
 162        }
 163
 2164        return results;
 2165    }
 166
 167    private async Task<IReadOnlyCollection<T>> CollectManyAsync<T>(
 168        Func<IDashboardContributor, Task<IReadOnlyCollection<T>>> action,
 169        CancellationToken cancellationToken)
 170    {
 2171        var results = new List<T>();
 12172        foreach (var contributor in OrderedContributors)
 173        {
 4174            var result = await ExecuteContributorAsync(contributor, action, cancellationToken);
 4175            if (result != null)
 3176                results.AddRange(result);
 177        }
 178
 2179        return results;
 2180    }
 181
 182    private static async Task<T?> ExecuteContributorAsync<T>(
 183        IDashboardContributor contributor,
 184        Func<IDashboardContributor, Task<T>> action,
 185        CancellationToken cancellationToken)
 186    {
 187        try
 188        {
 13189            return await action(contributor);
 190        }
 1191        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 192        {
 1193            throw;
 194        }
 2195        catch
 196        {
 2197            return default;
 198        }
 12199    }
 200
 201    private static DashboardRuntimeStatus MergeRuntime(IEnumerable<DashboardOverviewContribution> contributions) =>
 3202        contributions
 2203            .Select(x => x.Runtime)
 2204            .FirstOrDefault(x => x != null && x.Status != DashboardRuntimeStatusKeys.Unavailable)
 3205        ?? new();
 206
 207    private static DashboardWorkflowInstanceMetrics MergeWorkflowMetrics(IEnumerable<DashboardOverviewContribution> cont
 208    {
 6209        var metrics = contributions.Select(x => x.WorkflowInstances).OfType<DashboardWorkflowInstanceMetrics>().ToList()
 3210        return new()
 3211        {
 2212            Running = metrics.Sum(x => x.Running),
 2213            Completed = metrics.Sum(x => x.Completed),
 2214            Faulted = metrics.Sum(x => x.Faulted),
 2215            Suspended = metrics.Sum(x => x.Suspended),
 2216            Interrupted = metrics.Sum(x => x.Interrupted),
 2217            IncidentBearing = metrics.Sum(x => x.IncidentBearing),
 2218            AverageDuration = AverageDuration(metrics.Select(x => x.AverageDuration))
 3219        };
 220    }
 221
 222    private static DashboardDiagnosticsSummary MergeDiagnostics(IEnumerable<DashboardOverviewContribution> contributions
 223    {
 6224        var summaries = contributions.Select(x => x.Diagnostics).OfType<DashboardDiagnosticsSummary>().ToList();
 3225        return new()
 3226        {
 2227            StructuredLogs = summaries.Select(x => x.StructuredLogs).FirstOrDefault(x => x.Capability.Status != Dashboar
 2228            ConsoleLogs = summaries.Select(x => x.ConsoleLogs).FirstOrDefault(x => x.Capability.Status != DashboardCapab
 3229        };
 230    }
 231
 232    private static TimeSpan? AverageDuration(IEnumerable<TimeSpan?> durations)
 233    {
 3234        var values = durations.OfType<TimeSpan>().Where(x => x >= TimeSpan.Zero).ToList();
 3235        return values.Count == 0 ? null : TimeSpan.FromTicks(Convert.ToInt64(values.Average(x => x.Ticks)));
 236    }
 237
 238    private static string NormalizeHotspotMetric(string? metric) =>
 0239        metric?.Trim().ToLowerInvariant() switch
 0240        {
 0241            "executions" => DashboardHotspotMetric.Executions,
 0242            "incidents" => DashboardHotspotMetric.Incidents,
 0243            "duration" => DashboardHotspotMetric.Duration,
 0244            _ => DashboardHotspotMetric.Faults
 0245        };
 246}