Skip to content

Commit 7981cd3

Browse files
committed
Implement dashboard log query for PostgreSQL provider.
Improved handling of data providers in `AggregateDataProvider.cs` with added guard clauses and a new `SelectedDataProvider` property. Updated `IDataProvider.cs` to include a method for fetching dashboard statistics asynchronously. Streamlined log data fetching in `ElasticSearchDbDataProvider.cs`, `MongoDbDataProvider.cs`, and `SqlServerDataProvider.cs` for better readability and maintainability. Introduced a new `DashboardModel` class in `LogStatisticModel.cs` for structured dashboard statistics. Added comprehensive tests in `DataProviderDashboardTests.cs` to ensure correct functionality. Refactored various test files for better organization and clarity.
1 parent 8b1977a commit 7981cd3

File tree

17 files changed

+744
-434
lines changed

17 files changed

+744
-434
lines changed
Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,79 @@
1-
using System;
1+
using Ardalis.GuardClauses;
2+
using Serilog.Ui.Core.Models;
3+
using System;
24
using System.Collections.Generic;
35
using System.Linq;
46
using System.Threading;
57
using System.Threading.Tasks;
6-
using Ardalis.GuardClauses;
7-
using Serilog.Ui.Core.Models;
88

9-
namespace Serilog.Ui.Core
9+
namespace Serilog.Ui.Core;
10+
11+
/// <summary>
12+
/// Aggregates multiple <see cref="IDataProvider" /> into one instance.
13+
/// </summary>
14+
public class AggregateDataProvider : IDataProvider
1015
{
16+
private readonly Dictionary<string, IDataProvider> _dataProviders = new();
17+
1118
/// <summary>
12-
/// Aggregates multiple <see cref="IDataProvider"/> into one instance.
19+
/// It creates an instance of <see cref="AggregateDataProvider" />.
1320
/// </summary>
14-
public class AggregateDataProvider : IDataProvider
21+
/// <param name="dataProviders">IEnumerable of providers.</param>
22+
/// <exception cref="ArgumentNullException">when <paramref name="dataProviders" /> is null</exception>
23+
/// <exception cref="ArgumentException">when <paramref name="dataProviders" /> is empty</exception>
24+
public AggregateDataProvider(IEnumerable<IDataProvider> dataProviders)
1525
{
16-
private readonly Dictionary<string, IDataProvider> _dataProviders = new();
26+
List<IDataProvider> providers = Guard.Against.NullOrEmpty(dataProviders).ToList();
1727

18-
/// <summary>
19-
/// It creates an instance of <see cref="AggregateDataProvider"/>.
20-
/// </summary>
21-
/// <param name="dataProviders">IEnumerable of providers.</param>
22-
/// <exception cref="ArgumentNullException">when <paramref name="dataProviders"/> is null</exception>
23-
/// <exception cref="ArgumentException">when <paramref name="dataProviders"/> is empty</exception>
24-
public AggregateDataProvider(IEnumerable<IDataProvider> dataProviders)
28+
foreach (List<IDataProvider>? grouped in providers.GroupBy(dp => dp.Name, p => p, (_, e) => e.ToList()))
2529
{
26-
var providers = Guard.Against.NullOrEmpty(dataProviders).ToList();
27-
28-
foreach (var grouped in providers.GroupBy(dp => dp.Name, p => p, (_, e) => e.ToList()))
29-
{
30-
var name = grouped[0].Name;
30+
string name = grouped[0].Name;
3131

32-
if (grouped.Count == 1)
32+
if (grouped.Count == 1)
33+
_dataProviders.Add(name, grouped[0]);
34+
else
35+
// When providers with the same name are registered, we ensure uniqueness by
36+
// generating a key I.e. ["MSSQL.dbo.logs", "MSSQL.dbo.logs"] =>
37+
// ["MSSQL.dbo.logs[0]", "MSSQL.dbo.logs[1]"]
38+
for (int i = 0; i < grouped.Count; i++)
3339
{
34-
_dataProviders.Add(name, grouped[0]);
40+
IDataProvider? dataProvider = grouped[i];
41+
_dataProviders.Add($"{name}[{i}]", dataProvider);
3542
}
36-
else
37-
{
38-
// When providers with the same name are registered, we ensure uniqueness by
39-
// generating a key I.e. ["MSSQL.dbo.logs", "MSSQL.dbo.logs"] =>
40-
// ["MSSQL.dbo.logs[0]", "MSSQL.dbo.logs[1]"]
41-
for (var i = 0; i < grouped.Count; i++)
42-
{
43-
var dataProvider = grouped[i];
44-
_dataProviders.Add($"{name}[{i}]", dataProvider);
45-
}
46-
}
47-
}
48-
49-
SelectedDataProvider = _dataProviders.First().Value;
5043
}
5144

52-
/// <summary>
53-
/// <inheritdoc cref="IDataProvider.Name"/>
54-
/// NOTE: We assume only one Aggregate provider, so the name is static.
55-
/// </summary>
56-
public string Name => nameof(AggregateDataProvider);
45+
SelectedDataProvider = _dataProviders.First().Value;
46+
}
47+
48+
/// <summary>
49+
/// If there is only one data provider, this is it.
50+
/// If there are multiple, this is the current data provider.
51+
/// </summary>
52+
private IDataProvider SelectedDataProvider { get; set; }
53+
54+
/// <summary>
55+
/// Existing data providers keys.
56+
/// </summary>
57+
public IEnumerable<string> Keys => _dataProviders.Keys;
5758

58-
/// <summary>
59-
/// If there is only one data provider, this is it.
60-
/// If there are multiple, this is the current data provider.
61-
/// </summary>
62-
private IDataProvider SelectedDataProvider { get; set; }
59+
/// <summary>
60+
/// <inheritdoc cref="IDataProvider.Name" />
61+
/// NOTE: We assume only one Aggregate provider, so the name is static.
62+
/// </summary>
63+
public string Name => nameof(AggregateDataProvider);
6364

64-
/// <summary>
65-
/// Switch active data provider by key.
66-
/// </summary>
67-
/// <param name="key">Data provider key</param>
68-
public void SwitchToProvider(string key) => SelectedDataProvider = _dataProviders[key];
65+
/// <inheritdoc />
66+
public Task<(IEnumerable<LogModel>, int)> FetchDataAsync(FetchLogsQuery queryParams,
67+
CancellationToken cancellationToken = default)
68+
=> SelectedDataProvider.FetchDataAsync(queryParams, cancellationToken);
6969

70-
/// <summary>
71-
/// Existing data providers keys.
72-
/// </summary>
73-
public IEnumerable<string> Keys => _dataProviders.Keys;
70+
/// <inheritdoc />
71+
public Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default)
72+
=> SelectedDataProvider.FetchDashboardAsync(cancellationToken);
7473

75-
/// <inheritdoc/>
76-
public Task<(IEnumerable<LogModel>, int)> FetchDataAsync(FetchLogsQuery queryParams, CancellationToken cancellationToken = default)
77-
=> SelectedDataProvider.FetchDataAsync(queryParams, cancellationToken);
78-
}
74+
/// <summary>
75+
/// Switch active data provider by key.
76+
/// </summary>
77+
/// <param name="key">Data provider key</param>
78+
public void SwitchToProvider(string key) => SelectedDataProvider = _dataProviders[key];
7979
}

src/Serilog.Ui.Core/IDataProvider.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,26 @@
33
using System.Threading.Tasks;
44
using Serilog.Ui.Core.Models;
55

6-
namespace Serilog.Ui.Core
6+
namespace Serilog.Ui.Core;
7+
8+
/// <summary>
9+
/// Data provider interface
10+
/// </summary>
11+
public interface IDataProvider
712
{
813
/// <summary>
9-
/// Data provider interface
14+
/// Name of the provider, used to identify this provider when using multiple.
15+
/// </summary>
16+
string Name { get; }
17+
18+
/// <summary>
19+
/// Fetches the log data asynchronous.
1020
/// </summary>
11-
public interface IDataProvider
12-
{
13-
/// <summary>
14-
/// Fetches the log data asynchronous.
15-
/// </summary>
16-
Task<(IEnumerable<LogModel> results, int total)> FetchDataAsync(FetchLogsQuery queryParams, CancellationToken cancellationToken = default);
21+
Task<(IEnumerable<LogModel> results, int total)> FetchDataAsync(FetchLogsQuery queryParams,
22+
CancellationToken cancellationToken = default);
1723

18-
/// <summary>
19-
/// Name of the provider, used to identify this provider when using multiple.
20-
/// </summary>
21-
string Name { get; }
22-
}
24+
/// <summary>
25+
/// Fetches dashboard statistics asynchronous.
26+
/// </summary>
27+
Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default);
2328
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Collections.Generic;
2+
3+
namespace Serilog.Ui.Core.Models;
4+
5+
/// <summary>
6+
/// Represents dashboard statistics for log data visualization.
7+
/// </summary>
8+
public class DashboardModel
9+
{
10+
/// <summary>
11+
/// Gets or sets the total count of logs.
12+
/// </summary>
13+
public int TotalLogs { get; set; }
14+
15+
/// <summary>
16+
/// Gets or sets the count of logs by level.
17+
/// </summary>
18+
public Dictionary<string, int> LogsByLevel { get; set; } = new();
19+
20+
/// <summary>
21+
/// Gets or sets the count of logs for today.
22+
/// </summary>
23+
public int TodayLogs { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the count of error logs for today.
27+
/// </summary>
28+
public int TodayErrorLogs { get; set; }
29+
}

src/Serilog.Ui.ElasticSearchProvider/ElasticSearchDbDataProvider.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
using System;
1+
using Nest;
2+
using Newtonsoft.Json;
3+
using Serilog.Ui.Core;
4+
using Serilog.Ui.Core.Models;
5+
using System;
26
using System.Collections.Generic;
37
using System.Linq;
48
using System.Reflection;
59
using System.Threading;
610
using System.Threading.Tasks;
7-
using Nest;
8-
using Newtonsoft.Json;
9-
using Serilog.Ui.Core;
10-
using Serilog.Ui.Core.Models;
1111
using static Serilog.Ui.Core.Models.SearchOptions;
1212

1313
namespace Serilog.Ui.ElasticSearchProvider;
@@ -24,35 +24,40 @@ public class ElasticSearchDbDataProvider(IElasticClient client, ElasticSearchDbO
2424

2525
private readonly ElasticSearchDbOptions _options = options ?? throw new ArgumentNullException(nameof(options));
2626

27-
public Task<(IEnumerable<LogModel>, int)> FetchDataAsync(FetchLogsQuery queryParams, CancellationToken cancellationToken = default)
28-
{
29-
return GetLogsAsync(queryParams, cancellationToken);
30-
}
27+
public Task<(IEnumerable<LogModel>, int)> FetchDataAsync(FetchLogsQuery queryParams,
28+
CancellationToken cancellationToken = default) => GetLogsAsync(queryParams, cancellationToken);
29+
30+
public Task<DashboardModel> FetchDashboardAsync(CancellationToken cancellationToken = default) =>
31+
throw new NotImplementedException();
3132

3233
public string Name => _options.ProviderName;
3334

34-
private async Task<(IEnumerable<LogModel>, int)> GetLogsAsync(FetchLogsQuery queryParams, CancellationToken cancellationToken = default)
35+
private async Task<(IEnumerable<LogModel>, int)> GetLogsAsync(FetchLogsQuery queryParams,
36+
CancellationToken cancellationToken = default)
3537
{
3638
// since serilog-sink does not have keyword indexes on level and message, we can only sort on @timestamp
3739
Func<SortDescriptor<ElasticSearchDbLogModel>, SortDescriptor<ElasticSearchDbLogModel>> sortDescriptor =
3840
queryParams.SortBy == SortDirection.Desc
3941
? descriptor => descriptor.Descending(TimeStampPropertyName)
4042
: descriptor => descriptor.Ascending(TimeStampPropertyName);
41-
var rowNoStart = queryParams.Page * queryParams.Count;
42-
var descriptor = new SearchDescriptor<ElasticSearchDbLogModel>()
43+
int rowNoStart = queryParams.Page * queryParams.Count;
44+
SearchDescriptor<ElasticSearchDbLogModel>? descriptor = new SearchDescriptor<ElasticSearchDbLogModel>()
4345
.Index(_options.IndexName)
4446
.Query(q =>
45-
+q.Match(m => m.Field(f => f.Level).Query(queryParams.Level)) &&
46-
+q.DateRange(dr => dr.Field(f => f.Timestamp).GreaterThanOrEquals(queryParams.StartDate).LessThanOrEquals(queryParams.EndDate)) &&
47-
+q.Match(m => m.Field(f => f.Message).Query(queryParams.SearchCriteria)) ||
47+
(+q.Match(m => m.Field(f => f.Level).Query(queryParams.Level)) &&
48+
+q.DateRange(dr =>
49+
dr.Field(f => f.Timestamp).GreaterThanOrEquals(queryParams.StartDate)
50+
.LessThanOrEquals(queryParams.EndDate)) &&
51+
+q.Match(m => m.Field(f => f.Message).Query(queryParams.SearchCriteria))) ||
4852
+q.Match(m => m.Field(f => f.Exceptions).Query(queryParams.SearchCriteria)))
4953
.Sort(sortDescriptor)
5054
.Skip(rowNoStart)
5155
.Size(queryParams.Count);
5256

53-
var result = await _client.SearchAsync<ElasticSearchDbLogModel>(descriptor, cancellationToken);
57+
ISearchResponse<ElasticSearchDbLogModel>? result =
58+
await _client.SearchAsync<ElasticSearchDbLogModel>(descriptor, cancellationToken);
5459

55-
int.TryParse(result?.Total.ToString(), out var total);
60+
int.TryParse(result?.Total.ToString(), out int total);
5661

5762
return (result?.Documents.Select((x, index) => x.ToLogModel(rowNoStart, index)).ToList() ?? [], total);
5863
}

0 commit comments

Comments
 (0)