feature: Server error provider added, client notification service added

This commit is contained in:
Mikhail Shabanov committed 2021-05-16 22:04:42 +02:00
1 parent 3c67595d94
commit b928e0fec9
14 files changed
+199 -10

No files matched your search

@@ -13,6 +13,7 @@ namespace WotBlitzStatisticsPro.Blazor.Tests
{ {
protected Mock<IMediator> MediatorMock; protected Mock<IMediator> MediatorMock;
protected Mock<ISearchDialogService> SearchServiceMock; protected Mock<ISearchDialogService> SearchServiceMock;
protected Mock<INotificationsService> NotificationsServiceMock;
protected MockNavigationManager NavigationManagerMock; protected MockNavigationManager NavigationManagerMock;
[SetUp] [SetUp]
@@ -21,13 +22,16 @@ namespace WotBlitzStatisticsPro.Blazor.Tests
MediatorMock = new(); MediatorMock = new();
SearchServiceMock = new(); SearchServiceMock = new();
NavigationManagerMock = new(); NavigationManagerMock = new();
NotificationsServiceMock = new ();
TestContext = new Bunit.TestContext(); TestContext = new Bunit.TestContext();
TestContext.Services.AddSingleton(MediatorMock.Object); TestContext.Services.AddSingleton(MediatorMock.Object);
TestContext.Services.AddLocalization(); TestContext.Services.AddLocalization();
TestContext.Services.AddSingleton<DialogService>(); TestContext.Services.AddSingleton<DialogService>();
TestContext.Services.AddSingleton<NotificationService>();
TestContext.Services.AddSingleton(SearchServiceMock.Object); TestContext.Services.AddSingleton(SearchServiceMock.Object);
TestContext.Services.AddSingleton(NotificationsServiceMock.Object);
TestContext.Services.AddSingleton<NavigationManager>(NavigationManagerMock); TestContext.Services.AddSingleton<NavigationManager>(NavigationManagerMock);
@@ -0,0 +1,9 @@
namespace WotBlitzStatisticsPro.Blazor.Model
{
public enum NotificationType
{
Info,
Warning,
Error
}
}
@@ -1,7 +1,7 @@
@using WotBlitzStatisticsPro.Blazor.Model @using WotBlitzStatisticsPro.Blazor.Model
@inherits SearchDialogTypeBase @inherits SearchDialogTypeBase
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4 col-4">
<RadzenDropDown @bind-Value=@CurrentRealmType <RadzenDropDown @bind-Value=@CurrentRealmType
Data=@Realms Data=@Realms
ValueProperty="RealmType" ValueProperty="RealmType"
@@ -11,7 +11,7 @@
</Template> </Template>
</RadzenDropDown> </RadzenDropDown>
</div> </div>
<div class="col-md-8"> <div class="col-md-8 col-8">
<RadzenTextBox Placeholder=@($"{Localizer["Search"]}...") Change=@OnSearchTextChange Disabled="@ComponentBusy" Style="width: 150px;"/> <RadzenTextBox Placeholder=@($"{Localizer["Search"]}...") Change=@OnSearchTextChange Disabled="@ComponentBusy" Style="width: 150px;"/>
</div> </div>
</div> </div>
@@ -21,7 +21,7 @@
<RadzenListBox @bind-Value=@CurrentValue <RadzenListBox @bind-Value=@CurrentValue
Data=@FilteredList Data=@FilteredList
ValueProperty="ItemId" ValueProperty="ItemId"
Style="height:335px; width: 100%; margin-top: 10px;" Style="height:330px; width: 100%; margin-top: 10px;"
Visible="@(!ComponentBusy)"> Visible="@(!ComponentBusy)">
<Template> <Template>
<!--ToDo: Create new component that will show nick[clan] {WinRate with color}, {battles count with translatable word "battles"}. And another component for Clans--> <!--ToDo: Create new component that will show nick[clan] {WinRate with color}, {battles count with translatable word "battles"}. And another component for Clans-->
@@ -6,7 +6,6 @@ using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Radzen; using Radzen;
using WotBlitzStatisticsPro.Blazor.GraphQl; using WotBlitzStatisticsPro.Blazor.GraphQl;
//using WotBlitzStatisticsPro.Blazor.GraphQl;
using WotBlitzStatisticsPro.Blazor.Messages; using WotBlitzStatisticsPro.Blazor.Messages;
using WotBlitzStatisticsPro.Blazor.Model; using WotBlitzStatisticsPro.Blazor.Model;
using WotBlitzStatisticsPro.Blazor.Services; using WotBlitzStatisticsPro.Blazor.Services;
@@ -63,7 +62,6 @@ namespace WotBlitzStatisticsPro.Blazor.Pages
ComponentBusy = true; ComponentBusy = true;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
// ToDo: Move this call to a service
var accounts = var accounts =
await GraphQlBackendService.FindPlayers(value, CurrentRealmType); await GraphQlBackendService.FindPlayers(value, CurrentRealmType);
+2
View File
@@ -24,12 +24,14 @@ namespace WotBlitzStatisticsPro.Blazor
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<DialogService>(); builder.Services.AddScoped<DialogService>();
builder.Services.AddScoped<NotificationService>();
builder.Services.AddScoped<ISearchDialogService, SearchDialogService>(); builder.Services.AddScoped<ISearchDialogService, SearchDialogService>();
builder.Services.AddMediatR(typeof(Program)); builder.Services.AddMediatR(typeof(Program));
builder.Services.AddSingleton<NavigationMessagesInterceptor>(); builder.Services.AddSingleton<NavigationMessagesInterceptor>();
builder.Services.AddScoped<ILocalStorageService, LocalStorageService>(); builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
builder.Services.AddSingleton<INotificationsService, NotificationsService>();
// StrawberryShake GraphQL Client // StrawberryShake GraphQL Client
builder.Services builder.Services
@@ -1,6 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using StrawberryShake;
using WotBlitzStatisticsPro.Blazor.GraphQl; using WotBlitzStatisticsPro.Blazor.GraphQl;
namespace WotBlitzStatisticsPro.Blazor.Services namespace WotBlitzStatisticsPro.Blazor.Services
@@ -8,10 +10,14 @@ namespace WotBlitzStatisticsPro.Blazor.Services
public class GraphQlBackendService: IGraphQlBackendService public class GraphQlBackendService: IGraphQlBackendService
{ {
private readonly WotBlitzStatisticsProClient _client; private readonly WotBlitzStatisticsProClient _client;
private readonly INotificationsService _notificationsService;
public GraphQlBackendService(WotBlitzStatisticsProClient client) public GraphQlBackendService(
WotBlitzStatisticsProClient client,
INotificationsService notificationsService)
{ {
_client = client; _client = client;
_notificationsService = notificationsService;
} }
public async Task<IReadOnlyList<IFindPlayers_Players>?> FindPlayers(string accountNick, RealmType realmType) public async Task<IReadOnlyList<IFindPlayers_Players>?> FindPlayers(string accountNick, RealmType realmType)
@@ -19,6 +25,7 @@ namespace WotBlitzStatisticsPro.Blazor.Services
var accounts = var accounts =
await _client.FindPlayers.ExecuteAsync(accountNick, realmType, GetLanguage()); await _client.FindPlayers.ExecuteAsync(accountNick, realmType, GetLanguage());
CheckErrors(accounts.Errors);
return accounts.Data?.Players; return accounts.Data?.Players;
} }
@@ -27,6 +34,7 @@ namespace WotBlitzStatisticsPro.Blazor.Services
var clans = var clans =
await _client.FindClans.ExecuteAsync(clanNameOrTag, realmType, GetLanguage()); await _client.FindClans.ExecuteAsync(clanNameOrTag, realmType, GetLanguage());
CheckErrors(clans.Errors);
return clans.Data?.Clans; return clans.Data?.Clans;
} }
@@ -45,5 +53,39 @@ namespace WotBlitzStatisticsPro.Blazor.Services
} }
} }
private void CheckErrors(IReadOnlyCollection<IClientError> errors)
{
var messages = new List<string>();
if (errors.Count > 0)
{
foreach (var error in errors)
{
if (error.Extensions != null)
{
foreach (var errorExt in error.Extensions)
{
var serializedError = errorExt.Value;
if (serializedError is string realError)
{
var jObject = JObject.Parse(realError);
var firstErrorInfo = jObject["errors"]?[0];
var message = firstErrorInfo?["extensions"]?["message"];
if (message != null)
{
messages.Add(message.ToString());
}
}
}
}
}
}
if (messages.Count > 0)
{
_notificationsService.ReportError("Backend error",string.Join(";", messages));
}
}
} }
} }
@@ -0,0 +1,19 @@
using System;
using WotBlitzStatisticsPro.Blazor.Model;
namespace WotBlitzStatisticsPro.Blazor.Services
{
public interface INotificationsService
{
NotificationType Type { get; }
string Summary { get; }
string Message { get; }
event Action MessageArrived;
void ReportInfo(string summary, string message);
void ReportWarning(string summary, string message);
void ReportError(string summary, string message);
}
}
@@ -0,0 +1,42 @@
using System;
using WotBlitzStatisticsPro.Blazor.Model;
namespace WotBlitzStatisticsPro.Blazor.Services
{
public class NotificationsService: INotificationsService
{
// ToDo: Add JSInterop console.log
public NotificationType Type { get; private set; } = NotificationType.Info;
public string Message { get; private set; } = string.Empty;
public string Summary { get; private set; }
public event Action MessageArrived;
public void ReportInfo(string summary, string message)
{
Type = NotificationType.Info;
Message = message;
Summary = summary;
MessageArrived?.Invoke();
}
public void ReportWarning(string summary, string message)
{
Type = NotificationType.Warning;
Message = message;
Summary = summary;
MessageArrived?.Invoke();
}
public void ReportError(string summary, string message)
{
Type = NotificationType.Error;
Message = message;
Summary = summary;
MessageArrived?.Invoke();
}
}
}
@@ -1,6 +1,7 @@
@inherits MainLayoutBase @inherits MainLayoutBase
<RadzenDialog /> <RadzenDialog />
<RadzenNotification />
<RadzenHeader> <RadzenHeader>
<ChildContent> <ChildContent>
@@ -1,18 +1,27 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Radzen; using Radzen;
using Radzen.Blazor; using Radzen.Blazor;
using WotBlitzStatisticsPro.Blazor.Messages; using WotBlitzStatisticsPro.Blazor.Messages;
using WotBlitzStatisticsPro.Blazor.Model;
using WotBlitzStatisticsPro.Blazor.Services;
namespace WotBlitzStatisticsPro.Blazor.Shared namespace WotBlitzStatisticsPro.Blazor.Shared
{ {
public class MainLayoutBase: LayoutComponentBase public class MainLayoutBase: LayoutComponentBase, IDisposable
{ {
[Inject] [Inject]
private IMediator Mediator { get; set; } private IMediator Mediator { get; set; }
[Inject]
private INotificationsService NotificationsService { get; set; }
[Inject]
NotificationService RadzenNotificationService { get; set; }
public RadzenSidebar Sidebar0; public RadzenSidebar Sidebar0;
public RadzenBody Body0; public RadzenBody Body0;
public bool SidebarExpanded { get; set; } = false; public bool SidebarExpanded { get; set; } = false;
@@ -53,6 +62,36 @@ namespace WotBlitzStatisticsPro.Blazor.Shared
// } // }
//} //}
NotificationsService.MessageArrived += NotificationsService_MessageArrived;
}
private void NotificationsService_MessageArrived()
{
NotificationSeverity severity;
switch (NotificationsService.Type)
{
case NotificationType.Info:
severity = NotificationSeverity.Info;
break;
case NotificationType.Warning:
severity = NotificationSeverity.Warning;
break;
case NotificationType.Error:
severity = NotificationSeverity.Error;
break;
default:
severity = NotificationSeverity.Success;
break;
}
var message = new NotificationMessage
{
Severity = severity,
Summary = NotificationsService.Summary,
Detail = NotificationsService.Message,
Duration = 40000
};
RadzenNotificationService.Notify(message);
} }
public void InvertSideBar() public void InvertSideBar()
@@ -68,5 +107,10 @@ namespace WotBlitzStatisticsPro.Blazor.Shared
CurrentCultureInfo = CultureInfos[item.Value]; CurrentCultureInfo = CultureInfos[item.Value];
} }
} }
public void Dispose()
{
NotificationsService.MessageArrived -= NotificationsService_MessageArrived;
}
} }
} }
@@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="5.0.5" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="5.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Radzen.Blazor" Version="3.2.0" /> <PackageReference Include="Radzen.Blazor" Version="3.2.0" />
<PackageReference Include="StrawberryShake.CodeGeneration.CSharp.Analyzers" Version="11.2.2" /> <PackageReference Include="StrawberryShake.CodeGeneration.CSharp.Analyzers" Version="11.2.2" />
<PackageReference Include="StrawberryShake.Transport.Http" Version="11.2.2" /> <PackageReference Include="StrawberryShake.Transport.Http" Version="11.2.2" />
@@ -0,0 +1,22 @@
using HotChocolate;
using Microsoft.Extensions.Logging;
namespace WotBlitzStatisticsPro.GraphQl
{
public class GraphQlErrorFilter: IErrorFilter
{
private readonly ILogger<GraphQlErrorFilter> _logger;
public GraphQlErrorFilter(ILogger<GraphQlErrorFilter> logger)
{
_logger = logger;
}
public IError OnError(IError error)
{
_logger.LogError(error.Exception, $"Operation {error.Path}");
return error;
}
}
}
+6 -1
View File
@@ -2,6 +2,7 @@ using System;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using HotChocolate;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@@ -11,6 +12,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using WotBlitzStatisticsPro.Common; using WotBlitzStatisticsPro.Common;
using WotBlitzStatisticsPro.Common.Model; using WotBlitzStatisticsPro.Common.Model;
@@ -77,7 +79,6 @@ namespace WotBlitzStatisticsPro.GraphQl
}; };
}); });
// Add GraphQL Services // Add GraphQL Services
services.AddGraphQLServer() services.AddGraphQLServer()
.AddQueryType(d => d.Name("Query")) .AddQueryType(d => d.Name("Query"))
@@ -107,6 +108,10 @@ namespace WotBlitzStatisticsPro.GraphQl
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
}) })
.ModifyRequestOptions(opt => opt.IncludeExceptionDetails = true)
.AddErrorFilter(sp =>
new GraphQlErrorFilter
(sp.GetApplicationService<ILogger<GraphQlErrorFilter>>()))
; ;
// Blazor // Blazor
@@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.4" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.10.0" /> <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.10.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0" />
</ItemGroup> </ItemGroup>