From bdd78ddc163f941cadf9dd7d33235b91718d1331 Mon Sep 17 00:00:00 2001 From: "amir.gholami" Date: Tue, 4 Jun 2024 16:43:40 +0330 Subject: [PATCH 1/2] Get Reports for Organization details feature in Aggregator #143 --- .../Contracts/Dtos/Tasks/GetTaskDto.cs | 2 + .../Contracts/Protos/base.proto | 10 + .../Protos/get_organizationReport_by_id.proto | 13 ++ .../Configuration/GrpcExtensions.cs | 26 +++ .../Configuration/HostingExtensions.cs | 2 + .../GetOrganizationReportByIdGrpcEndpoint.cs | 26 +++ .../GetOrganizationReportByIdHandler.cs | 207 ++++++++++++++++++ .../GetOrganizationReportByIdRequest.cs | 15 ++ .../GetOrganizationReportByIdRestEndpoint.cs | 31 +++ .../Infrastructure/Mapper/MappingProfile.cs | 2 + .../Owners.Read.Api/Owners.Read.Api.csproj | 4 + .../Api/Owners.Read.Api/appsettings.json | 5 + .../GetOrganizationReportByIdTests.cs | 61 ++++++ .../Owners.Read.Tests.Integration.csproj | 1 + .../UserPanel/Aggregator/Aggregator.csproj | 17 +- .../Configuration/GrpcExtensions.cs | 6 + .../GetOrganizationsByOwnerIdHandler.cs | 22 +- .../Infrastructure/Mapper/MappingProfile.cs | 2 + src/7-Docker/Infrastructure.yml | 2 +- 19 files changed, 442 insertions(+), 12 deletions(-) create mode 100644 src/1-BuildingBlocks/Contracts/Protos/get_organizationReport_by_id.proto create mode 100644 src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdGrpcEndpoint.cs create mode 100644 src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdHandler.cs create mode 100644 src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdRequest.cs create mode 100644 src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdRestEndpoint.cs create mode 100644 src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Features/Organizations/GetOrganizationReportByIdTests.cs diff --git a/src/1-BuildingBlocks/Contracts/Dtos/Tasks/GetTaskDto.cs b/src/1-BuildingBlocks/Contracts/Dtos/Tasks/GetTaskDto.cs index 2e1128041..4c95bc7a4 100644 --- a/src/1-BuildingBlocks/Contracts/Dtos/Tasks/GetTaskDto.cs +++ b/src/1-BuildingBlocks/Contracts/Dtos/Tasks/GetTaskDto.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using TaskoMask.BuildingBlocks.Contracts.Dtos.Common; +using TaskoMask.BuildingBlocks.Contracts.Enums; using TaskoMask.BuildingBlocks.Contracts.Resources; namespace TaskoMask.BuildingBlocks.Contracts.Dtos.Tasks; @@ -8,6 +9,7 @@ public class GetTaskDto : TaskBaseDto { [Display(Name = nameof(ContractsMetadata.OrganizationName), ResourceType = typeof(ContractsMetadata))] public string CardName { get; set; } + public BoardCardType CardType { get; set; } public string BoardId { get; set; } diff --git a/src/1-BuildingBlocks/Contracts/Protos/base.proto b/src/1-BuildingBlocks/Contracts/Protos/base.proto index 90e434254..fd690b3b1 100644 --- a/src/1-BuildingBlocks/Contracts/Protos/base.proto +++ b/src/1-BuildingBlocks/Contracts/Protos/base.proto @@ -65,4 +65,14 @@ message GetCardGrpcResponse { string ownerId = 8; } +message GetOrganizationReportGrpcResponse { + CreationTimeGrpcResponse creationTime=1; + string projectsCount = 2; + string boardsCount = 3; + string toDoTasksCount = 4; + string doingTasksCount = 5; + string doneTasksCount = 6; + string backlogTasksCount = 7; + string id = 8; +} diff --git a/src/1-BuildingBlocks/Contracts/Protos/get_organizationReport_by_id.proto b/src/1-BuildingBlocks/Contracts/Protos/get_organizationReport_by_id.proto new file mode 100644 index 000000000..f5c42ae54 --- /dev/null +++ b/src/1-BuildingBlocks/Contracts/Protos/get_organizationReport_by_id.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package TaskoMask.BuildingBlocks.Contracts.Protos; + +import "base.proto"; + +service GetOrganizationReportIdGrpcService { + rpc Handle (GetOrganizationReportIdGrpcRequest) returns (GetOrganizationReportGrpcResponse); +} + +message GetOrganizationReportIdGrpcRequest { + string organizationId = 1; +} \ No newline at end of file diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Configuration/GrpcExtensions.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Configuration/GrpcExtensions.cs index 12cc88a9d..13d32382c 100644 --- a/src/2-Services/Owners/Api/Owners.Read.Api/Configuration/GrpcExtensions.cs +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Configuration/GrpcExtensions.cs @@ -1,5 +1,10 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using TaskoMask.BuildingBlocks.Contracts.Protos; +using TaskoMask.Services.Owners.Read.Api.Features.Organizations.GetOrganizationReportById; using TaskoMask.Services.Owners.Read.Api.Features.Organizations.GetOrganizationsByOwnerId; using TaskoMask.Services.Owners.Read.Api.Features.Projects.GetProjectById; using TaskoMask.Services.Owners.Read.Api.Features.Projects.GetProjectsByOrganizationId; @@ -16,5 +21,26 @@ public static void MapGrpcServices(this IEndpointRouteBuilder endpoints) endpoints.MapGrpcService(); endpoints.MapGrpcService(); endpoints.MapGrpcService(); + endpoints.MapGrpcService(); + } + + + public static void AddGrpcClients(this IServiceCollection services, IConfiguration configuration) + { + services.AddGrpcClient(options => + { + options.Address = new Uri(configuration["Url:Board-Read-Service"]); + }); + + services.AddGrpcClient(options => + { + options.Address = new Uri(configuration["Url:Board-Read-Service"]); + }); + + services.AddGrpcClient(options => + { + options.Address = new Uri(configuration["Url:Task-Read-Service"]); + }); + } } diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Configuration/HostingExtensions.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Configuration/HostingExtensions.cs index 9a7e75f69..d6406308d 100644 --- a/src/2-Services/Owners/Api/Owners.Read.Api/Configuration/HostingExtensions.cs +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Configuration/HostingExtensions.cs @@ -24,6 +24,8 @@ public static WebApplication ConfigureServices(this WebApplicationBuilder builde builder.Services.AddGrpcPreConfigured(); + builder.Services.AddGrpcClients(builder.Configuration); + return builder.Build(); } diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdGrpcEndpoint.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdGrpcEndpoint.cs new file mode 100644 index 000000000..fe328cbe0 --- /dev/null +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdGrpcEndpoint.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using Grpc.Core; +using System.Threading.Tasks; +using TaskoMask.BuildingBlocks.Application.Bus; +using TaskoMask.BuildingBlocks.Contracts.Protos; + +namespace TaskoMask.Services.Owners.Read.Api.Features.Organizations.GetOrganizationReportById; + + +public class GetOrganizationReportByIdGrpcEndpoint : GetOrganizationReportIdGrpcService.GetOrganizationReportIdGrpcServiceBase +{ + private readonly IInMemoryBus _inMemoryBus; + private readonly IMapper _mapper; + + public GetOrganizationReportByIdGrpcEndpoint(IInMemoryBus inMemoryBus, IMapper mapper) + { + _inMemoryBus = inMemoryBus; + _mapper = mapper; + } + + public override async Task Handle(GetOrganizationReportIdGrpcRequest request, ServerCallContext context) + { + var report = await _inMemoryBus.SendQuery(new GetOrganizationReportByIdRequest(request.OrganizationId)); + return _mapper.Map(report.Value); + } +} diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdHandler.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdHandler.cs new file mode 100644 index 000000000..8715f86ff --- /dev/null +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdHandler.cs @@ -0,0 +1,207 @@ +using AutoMapper; +using Grpc.Core; +using MediatR; +using MongoDB.Driver; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using TaskoMask.BuildingBlocks.Application.Exceptions; +using TaskoMask.BuildingBlocks.Application.Queries; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Boards; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Cards; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Organizations; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Tasks; +using TaskoMask.BuildingBlocks.Contracts.Enums; +using TaskoMask.BuildingBlocks.Contracts.Protos; +using TaskoMask.BuildingBlocks.Contracts.Resources; +using TaskoMask.BuildingBlocks.Contracts.ViewModels; +using TaskoMask.BuildingBlocks.Domain.Resources; +using TaskoMask.Services.Owners.Read.Api.Infrastructure.DbContext; +using static TaskoMask.BuildingBlocks.Contracts.Protos.GetBoardsByOrganizationIdGrpcService; +using static TaskoMask.BuildingBlocks.Contracts.Protos.GetCardsByBoardIdGrpcService; +using static TaskoMask.BuildingBlocks.Contracts.Protos.GetTasksByCardIdGrpcService; + + +namespace TaskoMask.Services.Owners.Read.Api.Features.Organizations.GetOrganizationReportById; + +public class GetOrganizationReportByIdHandler : BaseQueryHandler, IRequestHandler +{ + #region Fields + + private readonly OwnerReadDbContext _ownerReadDbContext; + private readonly GetBoardsByOrganizationIdGrpcServiceClient _getBoardsByOrganizationIdGrpcServiceClient; + private readonly GetCardsByBoardIdGrpcServiceClient _getCardsByBoardIdGrpcServiceClient; + private readonly GetTasksByCardIdGrpcServiceClient _getTasksByCardIdGrpcServiceClient; + #endregion + + #region Ctors + + public GetOrganizationReportByIdHandler(OwnerReadDbContext ownerReadDbContext, + GetBoardsByOrganizationIdGrpcServiceClient getBoardsByOrganizationIdGrpcServiceClient, + GetCardsByBoardIdGrpcServiceClient getCardsByBoardIdGrpcServiceClient, + GetTasksByCardIdGrpcServiceClient getTasksByCardIdGrpcServiceClient, + IMapper mapper) + : base(mapper) + { + _ownerReadDbContext = ownerReadDbContext; + _getBoardsByOrganizationIdGrpcServiceClient = getBoardsByOrganizationIdGrpcServiceClient; + _getCardsByBoardIdGrpcServiceClient = getCardsByBoardIdGrpcServiceClient; + _getTasksByCardIdGrpcServiceClient = getTasksByCardIdGrpcServiceClient; + } + + #endregion + + #region Handlers + + + + /// + /// + /// + public async Task Handle(GetOrganizationReportByIdRequest request, CancellationToken cancellationToken) + { + var organizationReportDto = new OrganizationReportDto(); + var organization = await _ownerReadDbContext.Organizations + .Find(e => e.Id == request.Id) + .FirstOrDefaultAsync(cancellationToken: cancellationToken); + + if (organization == null) + throw new ApplicationException(ContractsMessages.Data_Not_exist, DomainMetadata.Organization); + + #region Project Reports + var organizationProjects = await _ownerReadDbContext.Projects.Find(e => e.Id == organization.Id).ToListAsync(cancellationToken); + organizationReportDto.ProjectsCount = organizationProjects?.Count ?? 0; + #endregion + + #region Board Reports + var boards = new List(); + var boardsGrpcCall = _getBoardsByOrganizationIdGrpcServiceClient.Handle( + new GetBoardsByOrganizationIdGrpcRequest { OrganizationId = organization.Id } + ); + + await foreach (var response in boardsGrpcCall.ResponseStream.ReadAllAsync()) + boards.Add(_mapper.Map(response)); + + organizationReportDto.BoardsCount = boards?.Count ?? 0; + #endregion + + #region Task Reports + long toDoTasksCount = 0; + long doingTasksCount = 0; + long doneTasksCount = 0; + long backlogTasksCount = 0; + + var allBoardTasks = await GetAllTasksForBoardsAsync(boards, cancellationToken); + + foreach (var boardTask in allBoardTasks) + { + foreach (var cardTask in boardTask.CardTasks) + { + foreach (var task in cardTask.Tasks) + { + switch (task.CardType) + { + case BoardCardType.ToDo: + toDoTasksCount++; + break; + case BoardCardType.Doing: + doingTasksCount++; + break; + case BoardCardType.Done: + doneTasksCount++; + break; + case BoardCardType.Backlog: + backlogTasksCount++; + break; + } + } + } + } + + organizationReportDto.ToDoTasksCount = toDoTasksCount; + organizationReportDto.DoingTasksCount = doingTasksCount; + organizationReportDto.DoneTasksCount = doneTasksCount; + organizationReportDto.BacklogTasksCount = backlogTasksCount; + + + #endregion + + return organizationReportDto; + } + + #endregion + + #region Private Methods + private async Task> GetAllTasksForBoardsAsync(IEnumerable boards, CancellationToken cancellationToken) + { + var boardTasks = new List(); + + foreach (var board in boards) + { + var cards = await GetCardsAsync(board.Id, cancellationToken); + var cardTasks = new List(); + + foreach (var card in cards) + { + var tasks = await GetTasksAsync(card.Card.Id); + cardTasks.Add(new CardDetailsViewModel + { + Card = card.Card, + Tasks = tasks + }); + } + + boardTasks.Add(new BoardTasksViewModel + { + Board = board, + CardTasks = cardTasks + }); + } + + return boardTasks; + } + + public class BoardTasksViewModel + { + public GetBoardDto Board { get; set; } + public IEnumerable CardTasks { get; set; } + } + + private async Task> GetCardsAsync(string boardId, CancellationToken cancellationToken) + { + var cards = new List(); + + var cardsGrpcCall = _getCardsByBoardIdGrpcServiceClient.Handle(new GetCardsByBoardIdGrpcRequest { BoardId = boardId }); + + while (await cardsGrpcCall.ResponseStream.MoveNext(cancellationToken)) + { + var currentCardGrpcResponse = cardsGrpcCall.ResponseStream.Current; + + cards.Add( + new CardDetailsViewModel { Card = MapToCard(currentCardGrpcResponse), Tasks = await GetTasksAsync(currentCardGrpcResponse.Id), } + ); + } + + return cards.AsEnumerable(); + } + + private async Task> GetTasksAsync(string cardId) + { + var tasks = new List(); + + var tasksGrpcCall = _getTasksByCardIdGrpcServiceClient.Handle(new GetTasksByCardIdGrpcRequest { CardId = cardId }); + + await foreach (var response in tasksGrpcCall.ResponseStream.ReadAllAsync()) + tasks.Add(_mapper.Map(response)); + + return tasks; + } + + private GetCardDto MapToCard(GetCardGrpcResponse cardGrpcResponse) + { + return _mapper.Map(cardGrpcResponse); + } + + #endregion +} diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdRequest.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdRequest.cs new file mode 100644 index 000000000..fd73e7c08 --- /dev/null +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdRequest.cs @@ -0,0 +1,15 @@ +using TaskoMask.BuildingBlocks.Application.Queries; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Organizations; + +namespace TaskoMask.Services.Owners.Read.Api.Features.Organizations.GetOrganizationReportById; + + +public class GetOrganizationReportByIdRequest : BaseQuery +{ + public GetOrganizationReportByIdRequest(string id) + { + Id = id; + } + + public string Id { get; } +} diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdRestEndpoint.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdRestEndpoint.cs new file mode 100644 index 000000000..bf5208c78 --- /dev/null +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdRestEndpoint.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using TaskoMask.BuildingBlocks.Application.Bus; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Organizations; +using TaskoMask.BuildingBlocks.Contracts.Helpers; +using TaskoMask.BuildingBlocks.Contracts.Services; +using TaskoMask.BuildingBlocks.Web.MVC.Controllers; + +namespace TaskoMask.Services.Owners.Read.Api.Features.Organizations.GetOrganizationReportById; + + + +[Authorize("user-read-access")] +[Tags("Organizations")] +public class GetOrganizationReportByIdRestEndpoint : BaseApiController +{ + public GetOrganizationReportByIdRestEndpoint(IAuthenticatedUserService authenticatedUserService, IInMemoryBus inMemoryBus) + : base(authenticatedUserService, inMemoryBus) { } + + /// + /// get organization Report + /// + [HttpGet] + [Route("organizations/report")] + public async Task> Get(string organizationId) + { + return await _inMemoryBus.SendQuery(new GetOrganizationReportByIdRequest(organizationId)); + } +} \ No newline at end of file diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Infrastructure/Mapper/MappingProfile.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Infrastructure/Mapper/MappingProfile.cs index c8a0283da..cde45001e 100644 --- a/src/2-Services/Owners/Api/Owners.Read.Api/Infrastructure/Mapper/MappingProfile.cs +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Infrastructure/Mapper/MappingProfile.cs @@ -19,6 +19,8 @@ public MappingProfile() CreateMap(); + CreateMap(); + CreateMap(); CreateMap() diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Owners.Read.Api.csproj b/src/2-Services/Owners/Api/Owners.Read.Api/Owners.Read.Api.csproj index 9d2b11072..74a985c9f 100644 --- a/src/2-Services/Owners/Api/Owners.Read.Api/Owners.Read.Api.csproj +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Owners.Read.Api.csproj @@ -31,8 +31,12 @@ + + + + diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/appsettings.json b/src/2-Services/Owners/Api/Owners.Read.Api/appsettings.json index 6a694922d..d35c4a3c3 100644 --- a/src/2-Services/Owners/Api/Owners.Read.Api/appsettings.json +++ b/src/2-Services/Owners/Api/Owners.Read.Api/appsettings.json @@ -9,6 +9,11 @@ "UserName": "guest", "Password": "guest" }, + "Url": { + "Owner-Read-Service": "https://localhost:5021", + "Board-Read-Service": "https://localhost:5025", + "Task-Read-Service": "https://localhost:5029" + }, "Metric": { "StandAloneKestrelServerEnabled": false, "Port": 5021, diff --git a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Features/Organizations/GetOrganizationReportByIdTests.cs b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Features/Organizations/GetOrganizationReportByIdTests.cs new file mode 100644 index 000000000..23b30e792 --- /dev/null +++ b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Features/Organizations/GetOrganizationReportByIdTests.cs @@ -0,0 +1,61 @@ +using Moq; +using TaskoMask.BuildingBlocks.Contracts.Protos; +using TaskoMask.Services.Owners.Read.IntegrationTests.Fixtures; +using Xunit; + +namespace TaskoMask.Services.Owners.Read.IntegrationTests.Features.Organizations; + + +[Collection(nameof(OrganizationCollectionFixture))] +public class GetOrganizationReportByIdTests +{ + #region Fields + private readonly OrganizationCollectionFixture _fixture; + private readonly Mock _mockBoardsClient; + private readonly Mock _mockCardsClient; + private readonly Mock _mockTasksClient; + #endregion + + #region Ctor + public GetOrganizationReportByIdTests(OrganizationCollectionFixture fixture) + { + _fixture = fixture; + _mockBoardsClient = new Mock(); + _mockCardsClient = new Mock(); + _mockTasksClient = new Mock(); + } + + #endregion + + #region Test Methods + + //[Fact] + //public async Task OrganizationReport_are_fetched_by_Id() + //{ + // //Arrange + // var expectedOrganization = OrganizationObjectMother.GetOrganization(); + // await _fixture.SeedOrganizationAsync(expectedOrganization); + // var getOrganizationsReportByIdHandler = new GetOrganizationReportByIdHandler( + // _fixture._dbContext, + // _mockBoardsClient.Object, + // _mockCardsClient.Object, + // _mockTasksClient.Object, + // _fixture._mapper + // ); + // var request = new GetOrganizationReportByIdRequest(expectedOrganization.OwnerId); + + // //Act + // var result = await getOrganizationsReportByIdHandler.Handle(request, CancellationToken.None); + + // //Assert + // Assert.NotNull(result); + // Assert.True(result.ProjectsCount >= 0); + // Assert.True(result.BoardsCount >= 0); + // Assert.True(result.ToDoTasksCount >= 0); + // Assert.True(result.DoingTasksCount >= 0); + // Assert.True(result.DoneTasksCount >= 0); + // Assert.True(result.BacklogTasksCount >= 0); + //} + + #endregion +} diff --git a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Owners.Read.Tests.Integration.csproj b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Owners.Read.Tests.Integration.csproj index e7e053832..34aa39c6b 100644 --- a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Owners.Read.Tests.Integration.csproj +++ b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Owners.Read.Tests.Integration.csproj @@ -10,6 +10,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/3-ApiGateways/UserPanel/Aggregator/Aggregator.csproj b/src/3-ApiGateways/UserPanel/Aggregator/Aggregator.csproj index 110c397d3..8f6d7efe7 100644 --- a/src/3-ApiGateways/UserPanel/Aggregator/Aggregator.csproj +++ b/src/3-ApiGateways/UserPanel/Aggregator/Aggregator.csproj @@ -30,18 +30,19 @@ - - - - - - - - + + + + + + + + + diff --git a/src/3-ApiGateways/UserPanel/Aggregator/Configuration/GrpcExtensions.cs b/src/3-ApiGateways/UserPanel/Aggregator/Configuration/GrpcExtensions.cs index 9d0e65a45..c14caff1a 100644 --- a/src/3-ApiGateways/UserPanel/Aggregator/Configuration/GrpcExtensions.cs +++ b/src/3-ApiGateways/UserPanel/Aggregator/Configuration/GrpcExtensions.cs @@ -4,6 +4,7 @@ using static TaskoMask.BuildingBlocks.Contracts.Protos.GetBoardsByProjectIdGrpcService; using static TaskoMask.BuildingBlocks.Contracts.Protos.GetCardsByBoardIdGrpcService; using static TaskoMask.BuildingBlocks.Contracts.Protos.GetCommentsByTaskIdGrpcService; +using static TaskoMask.BuildingBlocks.Contracts.Protos.GetOrganizationReportIdGrpcService; using static TaskoMask.BuildingBlocks.Contracts.Protos.GetOrganizationsByOwnerIdGrpcService; using static TaskoMask.BuildingBlocks.Contracts.Protos.GetProjectByIdGrpcService; using static TaskoMask.BuildingBlocks.Contracts.Protos.GetProjectsByOrganizationIdGrpcService; @@ -24,6 +25,11 @@ public static void AddGrpcClients(this IServiceCollection services, IConfigurati options.Address = new Uri(configuration["Url:Owner-Read-Service"]); }); + services.AddGrpcClient(options => + { + options.Address = new Uri(configuration["Url:Owner-Read-Service"]); + }); + services.AddGrpcClient(options => { options.Address = new Uri(configuration["Url:Owner-Read-Service"]); diff --git a/src/3-ApiGateways/UserPanel/Aggregator/Features/GetOrganizationsByOwnerId/GetOrganizationsByOwnerIdHandler.cs b/src/3-ApiGateways/UserPanel/Aggregator/Features/GetOrganizationsByOwnerId/GetOrganizationsByOwnerIdHandler.cs index 12e4babe1..32a8f7b7d 100644 --- a/src/3-ApiGateways/UserPanel/Aggregator/Features/GetOrganizationsByOwnerId/GetOrganizationsByOwnerIdHandler.cs +++ b/src/3-ApiGateways/UserPanel/Aggregator/Features/GetOrganizationsByOwnerId/GetOrganizationsByOwnerIdHandler.cs @@ -8,6 +8,7 @@ using TaskoMask.BuildingBlocks.Contracts.Protos; using TaskoMask.BuildingBlocks.Contracts.ViewModels; using static TaskoMask.BuildingBlocks.Contracts.Protos.GetBoardsByOrganizationIdGrpcService; +using static TaskoMask.BuildingBlocks.Contracts.Protos.GetOrganizationReportIdGrpcService; using static TaskoMask.BuildingBlocks.Contracts.Protos.GetOrganizationsByOwnerIdGrpcService; using static TaskoMask.BuildingBlocks.Contracts.Protos.GetProjectsByOrganizationIdGrpcService; @@ -22,6 +23,8 @@ public class GetOrganizationsByOwnerIdHandler private readonly GetOrganizationsByOwnerIdGrpcServiceClient _getOrganizationsByOwnerIdGrpcServiceClient; private readonly GetProjectsByOrganizationIdGrpcServiceClient _getProjectsByOrganizationIdGrpcServiceClient; private readonly GetBoardsByOrganizationIdGrpcServiceClient _getBoardsByOrganizationIdGrpcServiceClient; + private readonly GetOrganizationReportIdGrpcServiceClient _getOrganizationReportIdGrpcServiceClient; + #endregion @@ -31,13 +34,15 @@ public GetOrganizationsByOwnerIdHandler( IMapper mapper, GetOrganizationsByOwnerIdGrpcServiceClient getOrganizationsByOwnerIdGrpcServiceClient, GetProjectsByOrganizationIdGrpcServiceClient getProjectsByOrganizationIdGrpcServiceClient, - GetBoardsByOrganizationIdGrpcServiceClient getBoardsByOrganizationIdGrpcServiceClient + GetBoardsByOrganizationIdGrpcServiceClient getBoardsByOrganizationIdGrpcServiceClient, + GetOrganizationReportIdGrpcServiceClient getOrganizationReportIdGrpcServiceClient ) : base(mapper) { _getOrganizationsByOwnerIdGrpcServiceClient = getOrganizationsByOwnerIdGrpcServiceClient; _getProjectsByOrganizationIdGrpcServiceClient = getProjectsByOrganizationIdGrpcServiceClient; _getBoardsByOrganizationIdGrpcServiceClient = getBoardsByOrganizationIdGrpcServiceClient; + _getOrganizationReportIdGrpcServiceClient = getOrganizationReportIdGrpcServiceClient; } #endregion @@ -67,8 +72,7 @@ public async Task> Handle(GetOrganizat Organization = MapToOrganization(currentOrganizationGrpcResponse), Projects = await GetProjectsAsync(currentOrganizationGrpcResponse.Id), Boards = await GetBoardsAsync(currentOrganizationGrpcResponse.Id), - //Will be done by issue #143 - Reports = new OrganizationReportDto(), + Reports = await GetReportAsync(currentOrganizationGrpcResponse.Id, cancellationToken) } ); } @@ -123,5 +127,17 @@ private async Task> GetBoardsAsync(string organizationI return boards; } + + /// + /// + /// + private async Task GetReportAsync(string organizationId, CancellationToken cancellationToken) + { + var reportGrpcResponse = await _getOrganizationReportIdGrpcServiceClient.HandleAsync( + new GetOrganizationReportIdGrpcRequest { OrganizationId = organizationId, }, + cancellationToken: cancellationToken + ); + return _mapper.Map(reportGrpcResponse); + } #endregion } diff --git a/src/3-ApiGateways/UserPanel/Aggregator/Infrastructure/Mapper/MappingProfile.cs b/src/3-ApiGateways/UserPanel/Aggregator/Infrastructure/Mapper/MappingProfile.cs index dcf3c32cf..71ce7105c 100644 --- a/src/3-ApiGateways/UserPanel/Aggregator/Infrastructure/Mapper/MappingProfile.cs +++ b/src/3-ApiGateways/UserPanel/Aggregator/Infrastructure/Mapper/MappingProfile.cs @@ -23,5 +23,7 @@ public MappingProfile() CreateMap(); CreateMap(); + + CreateMap(); } } diff --git a/src/7-Docker/Infrastructure.yml b/src/7-Docker/Infrastructure.yml index 280c41d19..53e8fb268 100644 --- a/src/7-Docker/Infrastructure.yml +++ b/src/7-Docker/Infrastructure.yml @@ -39,7 +39,7 @@ services: mongo: container_name: mongo - image: mongo:4.4.6 + image: mongo:latest restart: unless-stopped ports: - 27017:27017 From 74350182e8d752ed2227d74c92e1d43782a9e9ec Mon Sep 17 00:00:00 2001 From: "amir.gholami" Date: Thu, 6 Jun 2024 13:29:55 +0330 Subject: [PATCH 2/2] add integration test for Organization Report --- .../GetOrganizationReportByIdHandler.cs | 23 +-- .../Infrastructure/Mapper/MappingProfile.cs | 37 +++++ .../GetOrganizationReportByIdTests.cs | 136 +++++++++++++----- .../Fixtures/TestsBaseFixture.cs | 4 + .../Owners.Read.Tests.Integration.csproj | 1 + 5 files changed, 158 insertions(+), 43 deletions(-) diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdHandler.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdHandler.cs index 8715f86ff..38de092d6 100644 --- a/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdHandler.cs +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Features/Organizations/GetOrganizationReportById/GetOrganizationReportByIdHandler.cs @@ -75,15 +75,9 @@ public async Task Handle(GetOrganizationReportByIdRequest #endregion #region Board Reports - var boards = new List(); - var boardsGrpcCall = _getBoardsByOrganizationIdGrpcServiceClient.Handle( - new GetBoardsByOrganizationIdGrpcRequest { OrganizationId = organization.Id } - ); - - await foreach (var response in boardsGrpcCall.ResponseStream.ReadAllAsync()) - boards.Add(_mapper.Map(response)); - organizationReportDto.BoardsCount = boards?.Count ?? 0; + var boards = (await GetBoardsAsync(organization.Id)).ToList(); + organizationReportDto.BoardsCount = boards.Any() ? boards.Count : 0; #endregion #region Task Reports @@ -198,6 +192,19 @@ private async Task> GetTasksAsync(string cardId) return tasks; } + private async Task> GetBoardsAsync(string organizationId) + { + var boards = new List(); + var boardsGrpcCall = _getBoardsByOrganizationIdGrpcServiceClient.Handle( + new GetBoardsByOrganizationIdGrpcRequest { OrganizationId = organizationId } + ); + + await foreach (var response in boardsGrpcCall.ResponseStream.ReadAllAsync()) + boards.Add(_mapper.Map(response)); + + return boards; + } + private GetCardDto MapToCard(GetCardGrpcResponse cardGrpcResponse) { return _mapper.Map(cardGrpcResponse); diff --git a/src/2-Services/Owners/Api/Owners.Read.Api/Infrastructure/Mapper/MappingProfile.cs b/src/2-Services/Owners/Api/Owners.Read.Api/Infrastructure/Mapper/MappingProfile.cs index cde45001e..7a3c48194 100644 --- a/src/2-Services/Owners/Api/Owners.Read.Api/Infrastructure/Mapper/MappingProfile.cs +++ b/src/2-Services/Owners/Api/Owners.Read.Api/Infrastructure/Mapper/MappingProfile.cs @@ -1,9 +1,13 @@ using AutoMapper; using Google.Protobuf.WellKnownTypes; +using System; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Boards; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Cards; using TaskoMask.BuildingBlocks.Contracts.Dtos.Common; using TaskoMask.BuildingBlocks.Contracts.Dtos.Organizations; using TaskoMask.BuildingBlocks.Contracts.Dtos.Owners; using TaskoMask.BuildingBlocks.Contracts.Dtos.Projects; +using TaskoMask.BuildingBlocks.Contracts.Dtos.Tasks; using TaskoMask.BuildingBlocks.Contracts.Protos; using TaskoMask.Services.Owners.Read.Api.Domain; @@ -23,6 +27,27 @@ public MappingProfile() CreateMap(); + CreateMap() + .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description ?? string.Empty)) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name ?? string.Empty)) + .ForMember(dest => dest.ProjectId, opt => opt.MapFrom(src => src.ProjectId ?? string.Empty)) + .ForMember(dest => dest.OrganizationName, opt => opt.MapFrom(src => src.OrganizationName ?? string.Empty)) + .ForMember(dest => dest.ProjectName, opt => opt.MapFrom(src => src.ProjectName ?? string.Empty)) + .ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.OwnerId ?? string.Empty)) + .ForMember(dest => dest.OrganizationId, opt => opt.MapFrom(src => src.OrganizationId ?? string.Empty)); + + CreateMap() + .ForMember(dest => dest.CreateDateTime, opt => opt.MapFrom(src => ConvertTimestamp(src.CreateDateTime))) + .ForMember(dest => dest.CreateDateTimeString, opt => opt.MapFrom(src => src.CreateDateTimeString ?? string.Empty)) + .ForMember(dest => dest.ModifiedDateTime, opt => opt.MapFrom(src => ConvertTimestamp(src.ModifiedDateTime))) + .ForMember(dest => dest.ModifiedDateTimeString, opt => opt.MapFrom(src => src.ModifiedDateTimeString ?? string.Empty)) + .ForMember(dest => dest.CreateDay, opt => opt.MapFrom(src => src.CreateDay)) + .ForMember(dest => dest.CreateMonth, opt => opt.MapFrom(src => src.CreateMonth)) + .ForMember(dest => dest.CreateYear, opt => opt.MapFrom(src => src.CreateYear)); + + CreateMap(); + CreateMap(); + CreateMap() .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description ?? string.Empty)); @@ -31,4 +56,16 @@ public MappingProfile() CreateMap() .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description ?? string.Empty)); } + + private DateTime? ConvertTimestamp(Timestamp timestamp) + { + try + { + return timestamp.ToDateTime(); + } + catch + { + return null; + } + } } diff --git a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Features/Organizations/GetOrganizationReportByIdTests.cs b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Features/Organizations/GetOrganizationReportByIdTests.cs index 23b30e792..ffca5b265 100644 --- a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Features/Organizations/GetOrganizationReportByIdTests.cs +++ b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Features/Organizations/GetOrganizationReportByIdTests.cs @@ -1,6 +1,10 @@ -using Moq; +using AutoBogus; +using Grpc.Core; +using Moq; using TaskoMask.BuildingBlocks.Contracts.Protos; +using TaskoMask.Services.Owners.Read.Api.Features.Organizations.GetOrganizationReportById; using TaskoMask.Services.Owners.Read.IntegrationTests.Fixtures; +using TaskoMask.Services.Owners.Read.IntegrationTests.TestData; using Xunit; namespace TaskoMask.Services.Owners.Read.IntegrationTests.Features.Organizations; @@ -11,51 +15,113 @@ public class GetOrganizationReportByIdTests { #region Fields private readonly OrganizationCollectionFixture _fixture; - private readonly Mock _mockBoardsClient; - private readonly Mock _mockCardsClient; - private readonly Mock _mockTasksClient; + #endregion #region Ctor public GetOrganizationReportByIdTests(OrganizationCollectionFixture fixture) { _fixture = fixture; - _mockBoardsClient = new Mock(); - _mockCardsClient = new Mock(); - _mockTasksClient = new Mock(); } #endregion #region Test Methods - //[Fact] - //public async Task OrganizationReport_are_fetched_by_Id() - //{ - // //Arrange - // var expectedOrganization = OrganizationObjectMother.GetOrganization(); - // await _fixture.SeedOrganizationAsync(expectedOrganization); - // var getOrganizationsReportByIdHandler = new GetOrganizationReportByIdHandler( - // _fixture._dbContext, - // _mockBoardsClient.Object, - // _mockCardsClient.Object, - // _mockTasksClient.Object, - // _fixture._mapper - // ); - // var request = new GetOrganizationReportByIdRequest(expectedOrganization.OwnerId); - - // //Act - // var result = await getOrganizationsReportByIdHandler.Handle(request, CancellationToken.None); - - // //Assert - // Assert.NotNull(result); - // Assert.True(result.ProjectsCount >= 0); - // Assert.True(result.BoardsCount >= 0); - // Assert.True(result.ToDoTasksCount >= 0); - // Assert.True(result.DoingTasksCount >= 0); - // Assert.True(result.DoneTasksCount >= 0); - // Assert.True(result.BacklogTasksCount >= 0); - //} + [Fact] + public async Task OrganizationReport_is_fetched_by_Id() + { + // Arrange + var expectedOrganization = OrganizationObjectMother.GetOrganization(); + await _fixture.SeedOrganizationAsync(expectedOrganization); + + var dbContext = _fixture._dbContext; + var mapper = _fixture._mapper; + + var mockBoardsClient = new Mock(); + var mockCardsClient = new Mock(); + var mockTasksClient = new Mock(); + + // Setup mock responses + var fakeBoardsResponse = new AutoFaker().Generate(); + var fakeCardsResponse = new AutoFaker().Generate(); + var fakeTasksResponse = new AutoFaker().Generate(); + + + // Create AsyncServerStreamingCall objects for the mocked responses + var fakeBoardsAsyncResponse = new AsyncServerStreamingCall( + GetAsyncStreamReader(fakeBoardsResponse), + Task.FromResult(new Metadata()), + () => Status.DefaultSuccess, + () => new Metadata(), + () => { } + ); + + + var fakeCardsAsyncResponse = new AsyncServerStreamingCall( + GetAsyncStreamReader(fakeCardsResponse), + Task.FromResult(new Metadata()), + () => Status.DefaultSuccess, + () => new Metadata(), + () => { } + ); + + var fakeTasksAsyncResponse = new AsyncServerStreamingCall( + GetAsyncStreamReader(fakeTasksResponse), + Task.FromResult(new Metadata()), + () => Status.DefaultSuccess, + () => new Metadata(), + () => { } + ); + + mockBoardsClient.Setup(client => client.Handle(It.IsAny(), null, null, CancellationToken.None)) + .Returns(fakeBoardsAsyncResponse); + + mockCardsClient.Setup(client => client.Handle(It.IsAny(), null, null, CancellationToken.None)) + .Returns(fakeCardsAsyncResponse); + + mockTasksClient.Setup(client => client.Handle(It.IsAny(), null, null, CancellationToken.None)) + .Returns(fakeTasksAsyncResponse); + + var handler = new GetOrganizationReportByIdHandler( + dbContext, + mockBoardsClient.Object, + mockCardsClient.Object, + mockTasksClient.Object, + mapper + ); + + var request = new GetOrganizationReportByIdRequest(expectedOrganization.Id); + + // Act + var result = await handler.Handle(request, CancellationToken.None); + + // Assert + Assert.NotNull(result); + Assert.True(result.ProjectsCount >= 0); + Assert.True(result.BoardsCount >= 0); + Assert.True(result.ToDoTasksCount >= 0); + Assert.True(result.DoingTasksCount >= 0); + Assert.True(result.DoneTasksCount >= 0); + Assert.True(result.BacklogTasksCount >= 0); + } + + + #endregion + + #region Methods + private static IAsyncStreamReader GetAsyncStreamReader(params T[] responses) + { + var mockStreamReader = new Mock>(); + var queue = new Queue(responses); + + mockStreamReader.Setup(r => r.MoveNext(It.IsAny())) + .ReturnsAsync(() => queue.Count > 0); + + mockStreamReader.Setup(r => r.Current).Returns(() => queue.Dequeue()); + + return mockStreamReader.Object; + } #endregion -} +} \ No newline at end of file diff --git a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Fixtures/TestsBaseFixture.cs b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Fixtures/TestsBaseFixture.cs index 02d3466cb..16f8f23a2 100644 --- a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Fixtures/TestsBaseFixture.cs +++ b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Fixtures/TestsBaseFixture.cs @@ -1,6 +1,7 @@ using AutoMapper; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using TaskoMask.BuildingBlocks.Application.Bus; using TaskoMask.BuildingBlocks.Test.TestBase; using TaskoMask.Services.Owners.Read.Api.Infrastructure.DbContext; using TaskoMask.Services.Owners.Read.Api.Infrastructure.DI; @@ -11,12 +12,14 @@ public abstract class TestsBaseFixture : IntegrationTestsBase { public readonly IMapper _mapper; public readonly OwnerReadDbContext _dbContext; + public readonly IInMemoryBus _inMemoryBus; protected TestsBaseFixture(string dbNameSuffix) : base(dbNameSuffix) { _mapper = GetRequiredService(); _dbContext = GetRequiredService(); + _inMemoryBus = GetRequiredService(); } /// @@ -27,6 +30,7 @@ public override void InitialDatabase() _serviceProvider.InitialDatabase(); } + /// /// /// diff --git a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Owners.Read.Tests.Integration.csproj b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Owners.Read.Tests.Integration.csproj index 34aa39c6b..671d1bdb8 100644 --- a/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Owners.Read.Tests.Integration.csproj +++ b/src/2-Services/Owners/Tests/Owners.Read.Tests.Integration/Owners.Read.Tests.Integration.csproj @@ -9,6 +9,7 @@ +