Skip to content

Commit

Permalink
DateTime Ranges on Charts
Browse files Browse the repository at this point in the history
Also fix a lot of stuff.

Add an endpoint that gets the Min StartDatetime for all aggregated
measurements in a room.
  • Loading branch information
mads256h committed Dec 4, 2023
1 parent 70d181c commit 2bcd319
Show file tree
Hide file tree
Showing 13 changed files with 274 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Text.Json.Serialization;

namespace CentralHub.Api.Model.Responses.AggregatedMeasurements;
namespace CentralHub.Api.Model.Responses.Measurements;

public sealed class GetAggregatedMeasurementsResponse
{
[JsonConstructor]
public GetAggregatedMeasurementsResponse(bool success, IReadOnlyCollection<AggregatedMeasurements>? aggregatedMeasurements)
public GetAggregatedMeasurementsResponse(bool success, IReadOnlyCollection<AggregatedMeasurements.AggregatedMeasurements>? aggregatedMeasurements)
{
Success = success;
AggregatedMeasurements = aggregatedMeasurements;
Expand All @@ -14,14 +14,14 @@ public GetAggregatedMeasurementsResponse(bool success, IReadOnlyCollection<Aggre
public bool Success { get; }

// Null when unsuccessful
public IReadOnlyCollection<AggregatedMeasurements>? AggregatedMeasurements { get; }
public IReadOnlyCollection<AggregatedMeasurements.AggregatedMeasurements>? AggregatedMeasurements { get; }

public static GetAggregatedMeasurementsResponse CreateUnsuccessful()
{
return new GetAggregatedMeasurementsResponse(false, null);
}

public static GetAggregatedMeasurementsResponse CreateSuccessful(IReadOnlyCollection<AggregatedMeasurements> aggregatedMeasurements)
public static GetAggregatedMeasurementsResponse CreateSuccessful(IReadOnlyCollection<AggregatedMeasurements.AggregatedMeasurements> aggregatedMeasurements)
{
return new GetAggregatedMeasurementsResponse(true, aggregatedMeasurements);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Text.Json.Serialization;

namespace CentralHub.Api.Model.Responses.Measurements;

[method: JsonConstructor]
public sealed class GetFirstAggregatedMeasurementsDateTimeResponse(bool success, DateTime? firstDateTime)
{
public bool Success { get; } = success;
public DateTime? FirstDateTime { get; } = firstDateTime;

public static GetFirstAggregatedMeasurementsDateTimeResponse CreateUnsuccessful()
{
return new GetFirstAggregatedMeasurementsDateTimeResponse(false, null);
}

public static GetFirstAggregatedMeasurementsDateTimeResponse CreateSuccessful(DateTime firstDateTime)
{
return new GetFirstAggregatedMeasurementsDateTimeResponse(true, firstDateTime);
}
}
109 changes: 64 additions & 45 deletions CentralHub.Api.Tests/MockAggregatedMeasurementRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,87 +2,106 @@
using CentralHub.Api.Dtos;
using CentralHub.Api.Model;
using CentralHub.Api.Services;
using CentralHub.Api.Threading;

namespace CentralHub.Api.Tests;

internal sealed class MockAggregatedMeasurementRepository : IMeasurementRepository
{
private static readonly Dictionary<int, List<MeasurementGroup>> _recentMeasurementGroups = new Dictionary<int, List<MeasurementGroup>>();
private static readonly CancellableMutex<Dictionary<int, List<MeasurementGroup>>> RecentMeasurementGroupsMutex =
new CancellableMutex<Dictionary<int, List<MeasurementGroup>>>(new Dictionary<int, List<MeasurementGroup>>());

private static readonly List<AggregatedMeasurementDto> _AggregatedMeasurements = new List<AggregatedMeasurementDto>();
private int _nextId = 0;

public Task<int> AddAggregatedMeasurementAsync(AggregatedMeasurementDto aggregatedDto, CancellationToken cancellationToken)
private static readonly CancellableMutex<AggregatedMeasurementsStuff> AggregatedMeasurementsMutex =
new CancellableMutex<AggregatedMeasurementsStuff>(new AggregatedMeasurementsStuff());

private sealed class AggregatedMeasurementsStuff
{
public List<AggregatedMeasurementDto> AggregatedMeasurements { get; } = new List<AggregatedMeasurementDto>();

public int NextId { get; set; } = 0;
}

public async Task<int> AddAggregatedMeasurementAsync(AggregatedMeasurementDto aggregatedDto, CancellationToken cancellationToken)
{
lock (_AggregatedMeasurements)
return await AggregatedMeasurementsMutex.Lock(stuff =>
{
_AggregatedMeasurements.Add(aggregatedDto);
aggregatedDto.AggregatedMeasurementDtoId = _nextId;
_nextId++;
}
return new ValueTask<int>(aggregatedDto.RoomDtoId).AsTask();
stuff.AggregatedMeasurements.Add(aggregatedDto);
aggregatedDto.AggregatedMeasurementDtoId = stuff.NextId;
stuff.NextId++;

return aggregatedDto.AggregatedMeasurementDtoId;
}, cancellationToken);
}

public Task RemoveAggregatedMeasurementAsync(AggregatedMeasurementDto aggregatedDto, CancellationToken cancellationToken)
public async Task RemoveAggregatedMeasurementAsync(AggregatedMeasurementDto aggregatedDto, CancellationToken cancellationToken)
{
lock (_AggregatedMeasurements)
await AggregatedMeasurementsMutex.Lock(stuff =>
{
_AggregatedMeasurements.Remove(aggregatedDto);
}
return Task.CompletedTask;
stuff.AggregatedMeasurements.Remove(aggregatedDto);
}, cancellationToken);
}

public Task<IEnumerable<AggregatedMeasurementDto>> GetAggregatedMeasurementsAsync(int roomId, CancellationToken cancellationToken)
public async Task<IEnumerable<AggregatedMeasurementDto>> GetAggregatedMeasurementsAsync(int roomId, CancellationToken cancellationToken)
{
lock (_AggregatedMeasurements)
return await AggregatedMeasurementsMutex.Lock(stuff =>
{
return new ValueTask<IEnumerable<AggregatedMeasurementDto>>(
_AggregatedMeasurements
return stuff.AggregatedMeasurements
.Where(m => m.RoomDtoId == roomId)
.ToImmutableArray()).AsTask();
}
.ToImmutableArray();
}, cancellationToken);
}

public Task<IEnumerable<AggregatedMeasurementDto>> GetAggregatedMeasurementsAsync(int roomId, DateTime timeStart, DateTime timeEnd, CancellationToken cancellationToken)
public async Task<IEnumerable<AggregatedMeasurementDto>> GetAggregatedMeasurementsAsync(int roomId, DateTime startTime, DateTime endTime, CancellationToken cancellationToken)
{
lock (_AggregatedMeasurements)
return await AggregatedMeasurementsMutex.Lock(stuff =>
{
return new ValueTask<IEnumerable<AggregatedMeasurementDto>>(
_AggregatedMeasurements
.Where(m => m.RoomDtoId == roomId && m.StartTime >= timeStart && m.EndTime <= timeEnd)
.ToImmutableArray()).AsTask();
}
return stuff.AggregatedMeasurements
.Where(m => m.RoomDtoId == roomId && m.StartTime >= startTime && m.EndTime <= endTime)
.ToImmutableArray();
}, cancellationToken);
}

public async Task<IReadOnlyDictionary<int, IReadOnlyList<MeasurementGroup>>> GetRoomMeasurementGroupsAsync(CancellationToken cancellationToken)
{
return await Task.Run(() =>
return await RecentMeasurementGroupsMutex.Lock(recentMeasurementGroups =>
{
lock (_recentMeasurementGroups)
var recentTrackerMeasurementGroups = recentMeasurementGroups
.ToDictionary(k => k.Key, v => (IReadOnlyList<MeasurementGroup>)v.Value.ToImmutableArray());
recentMeasurementGroups.Clear();
return recentTrackerMeasurementGroups;
}, cancellationToken);
}

public async Task AddMeasurementsAsync(int id, IReadOnlyCollection<Measurement> measurements, CancellationToken cancellationToken)
{
await RecentMeasurementGroupsMutex.Lock(recentMeasurementGroups =>
{
if (recentMeasurementGroups.TryGetValue(id, out var value))
{
var recentTrackerMeasurementGroups = _recentMeasurementGroups
.ToDictionary(k => k.Key, v => (IReadOnlyList<MeasurementGroup>)v.Value.ToImmutableArray());
_recentMeasurementGroups.Clear();
return recentTrackerMeasurementGroups;
value.Add(new MeasurementGroup(measurements.ToImmutableArray()));
}
else
{
recentMeasurementGroups.Add(id,
new List<MeasurementGroup>() { new MeasurementGroup(measurements.ToImmutableArray()) });
}

}, cancellationToken);
}

public Task AddMeasurementsAsync(int id, IReadOnlyCollection<Measurement> measurements, CancellationToken cancellationToken)
public async Task<DateTime?> GetFirstAggregatedMeasurementsDateTimeAsync(int roomId, CancellationToken cancellationToken)
{
return Task.Run(() =>
return await AggregatedMeasurementsMutex.Lock(stuff =>
{
lock (_recentMeasurementGroups)
var aggregatedMeasurements = stuff.AggregatedMeasurements.Where(m => m.RoomDtoId == roomId)
.ToImmutableArray();
if (!aggregatedMeasurements.Any())
{
if (_recentMeasurementGroups.TryGetValue(id, out var value))
{
value.Add(new MeasurementGroup(measurements.ToList()));
}
else
{
_recentMeasurementGroups.Add(id, new List<MeasurementGroup>() { new MeasurementGroup(measurements.ToList()) });
}
return (DateTime?)null;
}

return aggregatedMeasurements.Min(m => m.StartTime);
}, cancellationToken);
}
}
26 changes: 21 additions & 5 deletions CentralHub.Api/Controllers/MeasurementController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CentralHub.Api.Model;
using CentralHub.Api.Model.Requests.Localization;
using CentralHub.Api.Model.Responses.AggregatedMeasurements;
using CentralHub.Api.Model.Responses.Measurements;
using CentralHub.Api.Services;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -13,17 +14,17 @@ public sealed class MeasurementController : ControllerBase
{
private readonly ILogger<MeasurementController> _logger;

private readonly IMeasurementRepository _aggregatorRepository;
private readonly IMeasurementRepository _measurementRepository;

private readonly ITrackerRepository _trackerRepository;

public MeasurementController(
ILogger<MeasurementController> logger,
IMeasurementRepository aggregatorRepository,
IMeasurementRepository measurementRepository,
ITrackerRepository trackerRepository)
{
_logger = logger;
_aggregatorRepository = aggregatorRepository;
_measurementRepository = measurementRepository;
_trackerRepository = trackerRepository;
}

Expand All @@ -40,7 +41,7 @@ public async Task<AddMeasurementsResponse> AddMeasurements(AddMeasurementsReques
return AddMeasurementsResponse.CreateUnsuccessful();
}

await _aggregatorRepository.AddMeasurementsAsync(
await _measurementRepository.AddMeasurementsAsync(
tracker.RoomDtoId,
addMeasurementsRequest.Measurements
.Where(m => !registeredTrackers.Any(t => t.WifiMacAddress == m.MacAddress || t.BluetoothMacAddress == m.MacAddress))
Expand All @@ -53,7 +54,7 @@ await _aggregatorRepository.AddMeasurementsAsync(
[HttpGet("all")]
public async Task<GetAggregatedMeasurementsResponse> GetAggregateMeasurements(int roomId, CancellationToken token)
{
var aggregatedMeasurements = await _aggregatorRepository.GetAggregatedMeasurementsAsync(roomId, token);
var aggregatedMeasurements = await _measurementRepository.GetAggregatedMeasurementsAsync(roomId, token);

if (aggregatedMeasurements == null)
{
Expand Down Expand Up @@ -93,4 +94,19 @@ public async Task<GetAggregatedMeasurementsResponse> GetAggregateMeasurements(in
.Where(am => am.StartTime >= timeStart && am.EndTime <= timeEnd)
.ToImmutableArray());
}

[HttpGet("first")]
public async Task<GetFirstAggregatedMeasurementsDateTimeResponse> GetFirstAggregatedMeasurementDateTime(int roomId,
CancellationToken cancellationToken)
{
var possibleFirstDateTime =
await _measurementRepository.GetFirstAggregatedMeasurementsDateTimeAsync(roomId, cancellationToken);

if (possibleFirstDateTime == null)
{
return GetFirstAggregatedMeasurementsDateTimeResponse.CreateUnsuccessful();
}

return GetFirstAggregatedMeasurementsDateTimeResponse.CreateSuccessful(possibleFirstDateTime.Value);
}
}
1 change: 0 additions & 1 deletion CentralHub.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.ComponentModel;
using App.ScopedService;
using CentralHub.Api.DbContexts;
using CentralHub.Api.Services;
using Microsoft.EntityFrameworkCore;
Expand Down
42 changes: 0 additions & 42 deletions CentralHub.Api/ScopedBackgroundService.cs

This file was deleted.

4 changes: 3 additions & 1 deletion CentralHub.Api/Services/IMeasurementRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ public interface IMeasurementRepository

Task<IEnumerable<AggregatedMeasurementDto>> GetAggregatedMeasurementsAsync(int roomId, CancellationToken cancellationToken);

Task<IEnumerable<AggregatedMeasurementDto>> GetAggregatedMeasurementsAsync(int roomId, DateTime timeStart, DateTime timeEnd, CancellationToken cancellationToken);
Task<IEnumerable<AggregatedMeasurementDto>> GetAggregatedMeasurementsAsync(int roomId, DateTime startTime, DateTime endTime, CancellationToken cancellationToken);

Task<IReadOnlyDictionary<int, IReadOnlyList<MeasurementGroup>>> GetRoomMeasurementGroupsAsync(CancellationToken cancellationToken);

Task AddMeasurementsAsync(int roomId, IReadOnlyCollection<Measurement> measurements, CancellationToken cancellationToken);

Task<DateTime?> GetFirstAggregatedMeasurementsDateTimeAsync(int roomId, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace App.ScopedService;
namespace CentralHub.Api.Services;

public interface IScopedProcessingService
{
Expand Down
1 change: 0 additions & 1 deletion CentralHub.Api/Services/LocalizationService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.Immutable;
using System.Linq;
using App.ScopedService;
using CentralHub.Api.Dtos;
using CentralHub.Api.Model;
using CentralHub.Api.Model.Responses.Room;
Expand Down
Loading

0 comments on commit 2bcd319

Please sign in to comment.