Skip to content

Commit

Permalink
Show current quota usage on Identity View (#367)
Browse files Browse the repository at this point in the history
* feat: progress bar and hardcoded usage values

* feat: make API return actual quota usage

* fix: tests missing new DI factory

* fix: bad quota tag

* refactor: rename var

* refactor: remove needless line

* refactor: move metricCalculatorFactoryfake creation to CreateHandler

* refactor: simplify Handler with GetIdentityResponse.Create

* refactor: move extraction of quota attributes to QuotaDTO ctor

* refactor: rename mock/stub. Use Dummy instead of Fake

* Trigger Build

* refactor: create overload for CreateHandler
This way we abstract the creation of optional dummies

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Dannyps and github-actions[bot] authored Nov 9, 2023
1 parent 55bf9a5 commit 2fae67e
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 45 deletions.
6 changes: 4 additions & 2 deletions AdminUi/src/AdminUi/ClientApp/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { MatIconModule } from "@angular/material/icon";
import { MatInputModule } from "@angular/material/input";
import { MatListModule } from "@angular/material/list";
import { MatPaginatorModule } from "@angular/material/paginator";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatSelectModule } from "@angular/material/select";
import { MatSidenavModule } from "@angular/material/sidenav";
Expand All @@ -46,6 +47,7 @@ import { IdentityListComponent } from "./components/quotas/identity/identity-lis
import { TierEditComponent } from "./components/quotas/tier/tier-edit/tier-edit.component";
import { TierListComponent } from "./components/quotas/tier/tier-list/tier-list.component";
import { ConfirmationDialogComponent } from "./components/shared/confirmation-dialog/confirmation-dialog.component";
import { IdentitiesOverviewComponent } from "./components/shared/identities-overview/identities-overview.component";
import { LoginComponent } from "./components/shared/login/login.component";
import { SidebarComponent } from "./components/sidebar/sidebar.component";
import { TopbarComponent } from "./components/topbar/topbar.component";
Expand All @@ -54,7 +56,6 @@ import { LoggerWriterService } from "./services/logger-writer-service/logger-wri
import { SidebarService } from "./services/sidebar-service/sidebar.service";
import { ApiKeyInterceptor } from "./shared/interceptors/api-key.interceptor";
import { XSRFInterceptor } from "./shared/interceptors/xsrf.interceptor";
import { IdentitiesOverviewComponent } from "./components/shared/identities-overview/identities-overview.component";

@NgModule({
declarations: [
Expand Down Expand Up @@ -125,7 +126,8 @@ import { IdentitiesOverviewComponent } from "./components/shared/identities-over
LayoutModule,
MatDialogModule,
MatSelectModule,
MatChipsModule
MatChipsModule,
MatProgressBarModule
],
providers: [
SidebarService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,12 @@
.auto-height {
height: auto;
}

.progressWrapper {
display: flex;
align-items: center;
}

.progressWrapper > span {
min-width: 2.5rem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,12 @@ <h2 class="header-title">{{ header }}</h2>
</td>
</ng-container>
<ng-container matColumnDef="max">
<th mat-header-cell *matHeaderCellDef>Max</th>
<td mat-cell *matCellDef="let Quota" data-label="Max:">
{{ Quota.max }}
<th mat-header-cell *matHeaderCellDef>Used/Max</th>
<td mat-cell *matCellDef="let Quota" data-label="Used/Max:">
<div class="progressWrapper">
<span>{{ Quota.usage }}/{{ Quota.max }}</span>
<mat-progress-bar mode="determinate" [value]="(Quota.usage / Quota.max) * 100"></mat-progress-bar>
</div>
</td>
</ng-container>
<ng-container matColumnDef="period">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface Quota {
source: "Tier" | "Individual";
metric: Metric;
max: number;
usage: number;
period: string;
disabled: boolean;
deleteable: boolean;
Expand Down
12 changes: 7 additions & 5 deletions Modules/Quotas/src/Quotas.Application/DTOs/QuotaDTO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
namespace Backbone.Modules.Quotas.Application.DTOs;
public class QuotaDTO
{
public QuotaDTO(string id, QuotaSource source, MetricDTO metric, int max, string period)
public QuotaDTO(Quota quota, MetricDTO metric, uint usage)
{
Id = id;
Source = source;
Id = quota.Id;
Source = quota is TierQuota ? QuotaSource.Tier : QuotaSource.Individual;
Metric = metric;
Max = max;
Period = period;
Max = quota.Max;
Period = quota.Period.ToString();
Usage = usage;
}

public string Id { get; set; }
public QuotaSource Source { get; set; }
public MetricDTO Metric { get; set; }
public int Max { get; set; }
public uint Usage { get; set; }
public string Period { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,40 @@
using Backbone.Modules.Quotas.Application.DTOs;
using Backbone.Modules.Quotas.Domain.Aggregates.Identities;
using Backbone.Modules.Quotas.Domain.Aggregates.Metrics;
using Backbone.Modules.Quotas.Domain.Metrics;

namespace Backbone.Modules.Quotas.Application.Identities.Queries.GetIdentity;
public class GetIdentityResponse
{
public GetIdentityResponse(string identityAddress, IEnumerable<TierQuota> tierQuotas, IEnumerable<IndividualQuota> individualQuotas, IEnumerable<Metric> metrics)
public string Address { get; set; }
public IEnumerable<QuotaDTO> Quotas { get; set; }

public static async Task<GetIdentityResponse> Create(MetricCalculatorFactory metricCalculatorFactory, string identityAddress, IEnumerable<TierQuota> tierQuotas, IEnumerable<IndividualQuota> individualQuotas, IEnumerable<Metric> metrics, CancellationToken cancellationToken)
{
var quotasList = new List<QuotaDTO>();
quotasList.AddRange(individualQuotas.Select(q =>
new QuotaDTO(
q.Id,
QuotaSource.Individual,
new MetricDTO(metrics.First(m => m.Key == q.MetricKey)),
q.Max,
q.Period.ToString()
)
));
quotasList.AddRange(tierQuotas.Select(q =>
new QuotaDTO(
q.Id,
QuotaSource.Tier,
new MetricDTO(metrics.First(m => m.Key == q.MetricKey)),
q.Max,
q.Period.ToString()
)
));

Address = identityAddress;
Quotas = quotasList;
var allQuotas = (individualQuotas as IEnumerable<Quota>).Union(tierQuotas);

foreach (var quota in allQuotas)
{
var usage = await CalculateUsage(metricCalculatorFactory, quota, identityAddress, cancellationToken);
quotasList.Add(new QuotaDTO(
quota,
new MetricDTO(metrics.First(m => m.Key == quota.MetricKey)),
usage
));
}

return new GetIdentityResponse()
{
Address = identityAddress,
Quotas = quotasList
};
}

public string Address { get; set; }
public IEnumerable<QuotaDTO> Quotas { get; set; }
private static async Task<uint> CalculateUsage(MetricCalculatorFactory metricCalculatorFactory, Quota quota, string identityAddress, CancellationToken cancellationToken)
{
var calculator = metricCalculatorFactory.CreateFor(quota.MetricKey);
return await calculator.CalculateUsage(quota.Period.CalculateBegin(), quota.Period.CalculateEnd(), identityAddress, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
using Backbone.BuildingBlocks.Application.Abstractions.Exceptions;
using Backbone.Modules.Quotas.Application.Infrastructure.Persistence.Repository;
using Backbone.Modules.Quotas.Domain.Aggregates.Identities;
using Backbone.Modules.Quotas.Domain.Metrics;
using MediatR;

namespace Backbone.Modules.Quotas.Application.Identities.Queries.GetIdentity;
public class Handler : IRequestHandler<GetIdentityQuery, GetIdentityResponse>
{
private readonly IIdentitiesRepository _identitiesRepository;
private readonly IMetricsRepository _metricsRepository;
private readonly MetricCalculatorFactory _metricCalculatorFactory;

public Handler(IIdentitiesRepository identitiesRepository, IMetricsRepository metricsRepository)
public Handler(IIdentitiesRepository identitiesRepository, IMetricsRepository metricsRepository, MetricCalculatorFactory metricCalculatorFactory)
{
_identitiesRepository = identitiesRepository;
_metricsRepository = metricsRepository;
_metricCalculatorFactory = metricCalculatorFactory;
}

public async Task<GetIdentityResponse> Handle(GetIdentityQuery request, CancellationToken cancellationToken)
Expand All @@ -21,7 +24,6 @@ public async Task<GetIdentityResponse> Handle(GetIdentityQuery request, Cancella

var metricsKeys = identity.TierQuotas.Select(q => q.MetricKey).Union(identity.IndividualQuotas.Select(q => q.MetricKey));
var metrics = await _metricsRepository.FindAllWithKeys(metricsKeys, cancellationToken);

return new GetIdentityResponse(identity.Address, identity.TierQuotas, identity.IndividualQuotas, metrics);
return await GetIdentityResponse.Create(_metricCalculatorFactory, identity.Address, identity.TierQuotas, identity.IndividualQuotas, metrics, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Backbone.Modules.Quotas.Domain.Aggregates.Identities;
using Backbone.Modules.Quotas.Domain.Aggregates.Metrics;
using Backbone.Modules.Quotas.Domain.Aggregates.Tiers;
using Backbone.Modules.Quotas.Domain.Metrics;
using Backbone.UnitTestTools.Extensions;
using FakeItEasy;
using FluentAssertions;
Expand All @@ -29,10 +30,10 @@ public async void Gets_identity_quotas_by_address()

var stubMetricsRepository = new FindAllWithKeysMetricsStubRepository(new List<Metric> { metric });

var identitiesRepository = A.Fake<IIdentitiesRepository>();
A.CallTo(() => identitiesRepository.Find(A<string>._, A<CancellationToken>._, A<bool>._)).Returns(identity);
var stubIdentitiesRepository = A.Fake<IIdentitiesRepository>();
A.CallTo(() => stubIdentitiesRepository.Find(A<string>._, A<CancellationToken>._, A<bool>._)).Returns(identity);

var handler = CreateHandler(identitiesRepository, stubMetricsRepository);
var handler = CreateHandler(stubIdentitiesRepository, stubMetricsRepository);

// Act
var result = await handler.Handle(new GetIdentityQuery(identity.Address), CancellationToken.None);
Expand All @@ -58,11 +59,10 @@ public async void Gets_identity_quotas_by_address()
public void Fails_when_no_identity_found()
{
// Arrange
var metricsRepository = A.Fake<IMetricsRepository>();
var identitiesRepository = A.Fake<IIdentitiesRepository>();
A.CallTo(() => identitiesRepository.Find(A<string>._, A<CancellationToken>._, A<bool>._)).Returns((Identity)null);
var stubIdentitiesRepository = A.Fake<IIdentitiesRepository>();
A.CallTo(() => stubIdentitiesRepository.Find(A<string>._, A<CancellationToken>._, A<bool>._)).Returns((Identity)null);

var handler = CreateHandler(identitiesRepository, metricsRepository);
var handler = CreateHandler(stubIdentitiesRepository);

// Act
Func<Task> acting = async () => await handler.Handle(new GetIdentityQuery("some-inexistent-identity-address"), CancellationToken.None);
Expand All @@ -73,8 +73,15 @@ public void Fails_when_no_identity_found()
exception.Code.Should().Be("error.platform.recordNotFound");
}

private Handler CreateHandler(IIdentitiesRepository identitiesRepository, IMetricsRepository metricsRepository)
private static Handler CreateHandler(IIdentitiesRepository identitiesRepository)
{
return new Handler(identitiesRepository, metricsRepository);
var dummyMetricsRepository = A.Dummy<IMetricsRepository>();
return CreateHandler(identitiesRepository, dummyMetricsRepository);
}

private static Handler CreateHandler(IIdentitiesRepository identitiesRepository, IMetricsRepository metricsRepository)
{
var dummyMetricCalculatorFactory = A.Dummy<MetricCalculatorFactory>();
return new Handler(identitiesRepository, metricsRepository, dummyMetricCalculatorFactory);
}
}

0 comments on commit 2fae67e

Please sign in to comment.