Skip to content
This repository has been archived by the owner on Jul 19, 2024. It is now read-only.

#248 Участник проекта может получать рейтинг за закрытую задачу #257

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Garnet.Common.Application.MessageBus;
using Garnet.Projects.Events.ProjectTask;

namespace Garnet.Projects.AcceptanceTests.FakeServices;

public class ProjectTaskClosedEventFakeConsumer : IMessageBusConsumer<ProjectTaskClosedEvent>
{
private ProjectTaskClosedEvent? _message;

public Task Consume(ProjectTaskClosedEvent message)
{
_message = message;
return Task.CompletedTask;
}

public ProjectTaskClosedEvent GetMessage()
{
return _message!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Функция:
Я, как пользователь-участник проекта
Хочу получать рейтинг за выполненную задачу
Чтобы другие пользователи могли оценить меня как специалиста по определенным навыкам

Контекст:
Допустим существует пользователь 'Вася'
И существует пользователь 'Маша'
И существует пользователь 'Дима'
И существует проект 'FooBar' с владельцем 'Дима'
И существует команда 'DreamTeam'
И команда 'DreamTeam' является участником проекта 'FooBar'
И пользователь 'Вася' является участником команды 'DreamTeam'
И пользователь 'Маша' является участником команды 'DreamTeam'
И существует задача 'DoSomething' в проекте 'FooBar' с тегами 'C#, SQL'
И команда 'DreamTeam' является исполнителем задачи 'DoSomething'
И пользователь 'Вася' является ответственным по задаче 'DoSomething'
И пользователь 'Вася' является исполнителем задачи 'DoSomething'
И пользователь 'Маша' является исполнителем задачи 'DoSomething'

Сценарий: Закрытие задачи с расчетом рейтинга
Когда пользователь 'Вася' закрывает задачу с названием 'DoSomething'
Тогда в системе существует задача с названием 'DoSomething' и статусом 'Close'
И у пользователя 'Вася' общий рейтинг равен '3,8' а рейтинг каждого из навыков 'C#, SQL' равен '1,9'
И у команды 'DreamTeam' общий рейтинг равен '1,9'
И у пользователя 'Дима' общий рейтинг равен '0,5'

Сценарий: Повторное закрытие задачи без расчета рейтинга
Допустим задача 'DoSomething' повторно открыта
Когда пользователь 'Вася' закрывает задачу с названием 'DoSomething'
Тогда в системе существует задача с названием 'DoSomething' и статусом 'Close'
И в событии нет расчета рейтинга
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using FluentAssertions;
using Garnet.Common.AcceptanceTests.Fakes;
using Garnet.Projects.AcceptanceTests.FakeServices;
using Garnet.Projects.AcceptanceTests.Support;
using Garnet.Projects.Application.ProjectTask.Args;
using Garnet.Projects.Infrastructure.MongoDb.ProjectTask;
using MongoDB.Driver;
using TechTalk.SpecFlow;

namespace Garnet.Projects.AcceptanceTests.Features.ProjectParticipantsGetRating;

[Binding]
public class ProjectParticipantsGetRatingSteps : BaseSteps
{
private readonly FilterDefinitionBuilder<ProjectTaskDocument> _f =
Builders<ProjectTaskDocument>.Filter;

private readonly UpdateDefinitionBuilder<ProjectTaskDocument> _u =
Builders<ProjectTaskDocument>.Update;

private readonly ProjectTaskClosedEventFakeConsumer _taskClosedFake;
private readonly CurrentUserProviderFake _currentUserProviderFake;

public ProjectParticipantsGetRatingSteps(StepsArgs args, ProjectTaskClosedEventFakeConsumer taskClosedFake,
CurrentUserProviderFake currentUserProviderFake) :
base(args)
{
_taskClosedFake = taskClosedFake;
_currentUserProviderFake = currentUserProviderFake;
}

[Given(@"задача '([^']*)' повторно открыта")]
public async Task GivenЗадачаПовторноОткрыта(string taskName)
{
await Db.ProjectTasks.UpdateOneAsync(
_f.Eq(x => x.Name, taskName),
_u.Set(x => x.Status, ProjectTaskStatuses.Open)
.Set(x => x.Reopened, true));
}

[Given(@"существует задача '([^']*)' в проекте '([^']*)' с тегами '([^']*)'")]
public async Task GivenСуществуетЗадачаВПроектеСТегами(string taskName, string projectName, string tags)
{
var tagList = tags.Split(", ");
var project = await Db.Projects.Find(x => x.ProjectName == projectName).FirstAsync();
var task = GiveMe.ProjectTask().WithName(taskName).WithProjectId(project.Id).WithTags(tagList).Build();
await Db.ProjectTasks.InsertOneAsync(task);
}

[Given(@"пользователь '(.*)' является исполнителем задачи '(.*)'")]
public async Task GivenПользовательЯвляетсяИсполнителемЗадачи(string username, string taskName)
{
var user = await Db.ProjectUsers.Find(x => x.UserName == username).FirstAsync();
await Db.ProjectTasks.UpdateOneAsync(
_f.Eq(x => x.Name, taskName),
_u.AddToSet(x => x.UserExecutorIds, user.Id));
}

[Then(@"в системе существует задача с названием '([^']*)' и статусом '([^']*)'")]
public async Task ThenВСистемеСуществуетЗадачаСоСтатусом(string taskName, string status)
{
var task = await Db.ProjectTasks.Find(x => x.Name == taskName).FirstOrDefaultAsync();
task.Status.Should().Be(status);
}

[Then(
@"у пользователя '([^']*)' общий рейтинг равен '([^']*)' а рейтинг каждого из навыков '([^']*)' равен '([^']*)'")]
public void ThenУПользователяОбщийРейтингИРейтингНавыков(string username, float totalScore, string tags,
float skillScore)
{
var tagList = tags.Split(", ");

_taskClosedFake.GetMessage().RatingCalculation!.UserExecutorIds
.Contains(_currentUserProviderFake.GetUserIdByUsername(username)).Should().BeTrue();

_taskClosedFake.GetMessage().RatingCalculation!.UserTotalScore.Should().BeApproximately(totalScore, 0.01f);


var skills = _taskClosedFake.GetMessage().RatingCalculation!.SkillScorePerUser;
foreach (var tag in tagList)
{
skills[tag].Should().BeApproximately(skillScore, 0.01f);
}
}

[Then(@"у команды '(.*)' общий рейтинг равен '(.*)'")]
public async void ThenУКомандыОбщийРейтинг(string teamName, float totalScore)
{
var team = await Db.ProjectTeams.Find(x => x.TeamName == teamName).FirstAsync();
_taskClosedFake.GetMessage().RatingCalculation!.TeamsTotalScore[team.Id].Should()
.BeApproximately(totalScore, 0.01f);
}

[Then(@"у пользователя '([^']*)' общий рейтинг равен '([^']*)'")]
public void ThenУПользователяОбщийРейтинг(string username, float totalScore)
{
_taskClosedFake.GetMessage().RatingCalculation!.ProjectOwnerId.Should()
.Be(_currentUserProviderFake.GetUserIdByUsername(username));
_taskClosedFake.GetMessage().RatingCalculation!.ProjectOwnerTotalScore.Should()
.BeApproximately(totalScore, 0.01f);
}

[Then(@"в событии нет расчета рейтинга")]
public void ThenВСобытииНетРасчетаРейтинга()
{
_taskClosedFake.GetMessage().RatingCalculation.Should().BeNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public async Task WhenПользовательСоздаетВПроектеЗа
}
}

[Then(@"в системе существует задача с названием '(.*)'")]
[Then(@"в системе существует задача с названием '([^']*)'")]
public async Task ThenВСистемеСуществуетЗадача(string taskName)
{
var task = await Db.ProjectTasks.Find(x => x.Name == taskName).FirstOrDefaultAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public ProjectTaskEditNameSteps(StepsArgs args, CurrentUserProviderFake currentU
_errorStepContext = errorStepContext;
}

[Given(@"существует задача '(.*)' в проекте '(.*)'")]
[Given(@"существует задача '([^']*)' в проекте '([^']*)'")]
public async Task GivenСуществуетЗадачаВПроекте(string taskName, string projectName)
{
var project = await Db.Projects.Find(x => x.ProjectName == projectName).FirstAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
using SolidToken.SpecFlow.DependencyInjection;
using Garnet.Project.AcceptanceTests.FakeServices.NotificationFake;
using Garnet.Notifications.Events;
using Garnet.Projects.AcceptanceTests.FakeServices;
using Garnet.Projects.Events.ProjectTask;

namespace Garnet.Projects.AcceptanceTests;

Expand Down Expand Up @@ -72,6 +74,7 @@ private static void AddMessageBus(IServiceCollection services)
services.AddGarnetMessageBus(Uuid.NewGuid(), o =>
{
o.RegisterConsumer<SendNotificationCommandMessageFakeConsumer, SendNotificationCommandMessage>();
o.RegisterConsumer<ProjectTaskClosedEventFakeConsumer, ProjectTaskClosedEvent>();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class ProjectTaskDocumentBuilder
private string[] _userExecutorIds = Array.Empty<string>();
private string[] _tags = Array.Empty<string>();
private string[] _labels = Array.Empty<string>();
private bool _reopened = false;
private AuditInfoDocument _auditInfo = new(DateTime.UtcNow, "CreatedByUser", DateTime.UtcNow, "UpdatedByUser", 0);


Expand Down Expand Up @@ -97,8 +98,7 @@ public ProjectTaskDocumentBuilder WithCreatedBy(string username)
public ProjectTaskDocument Build()
{
return ProjectTaskDocument.Create(_id, _taskNumber, _projectId, _responsibleUserId, _name, _description,
_status,
_teamExecutorIds, _userExecutorIds, _tags, _labels)
_status, _teamExecutorIds, _userExecutorIds, _tags, _labels, _reopened)
with
{
AuditInfo = _auditInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Garnet.Projects.Application.Project.Errors;
using Garnet.Projects.Application.ProjectTask.Args;
using Garnet.Projects.Application.ProjectTask.Errors;
using Garnet.Projects.Application.ProjectTeamParticipant;
using Garnet.Projects.Events.ProjectTask;

namespace Garnet.Projects.Application.ProjectTask.Commands;

Expand All @@ -14,17 +16,20 @@ public class ProjectTaskCloseCommand
private readonly IProjectTaskRepository _projectTaskRepository;
private readonly IProjectRepository _projectRepository;
private readonly IMessageBus _messageBus;
private readonly IProjectTeamParticipantRepository _projectTeamParticipantRepository;

public ProjectTaskCloseCommand(
ICurrentUserProvider currentUserProvider,
IProjectTaskRepository projectTaskRepository,
IMessageBus messageBus,
IProjectRepository projectRepository)
IProjectRepository projectRepository,
IProjectTeamParticipantRepository projectTeamParticipantRepository)
{
_currentUserProvider = currentUserProvider;
_projectTaskRepository = projectTaskRepository;
_messageBus = messageBus;
_projectRepository = projectRepository;
_projectTeamParticipantRepository = projectTeamParticipantRepository;
}

public async Task<Result<ProjectTaskEntity>> Execute(CancellationToken ct, string taskId)
Expand Down Expand Up @@ -53,9 +58,42 @@ public async Task<Result<ProjectTaskEntity>> Execute(CancellationToken ct, strin
return Result.Fail(new ProjectTaskAlreadySetThisStatusError(task.Status));
}

RatingCalculation? ratingCalculation = null;

if (task.Reopened is false)
{
var totalScore = (float)Math.Round(9.5 / task.UserExecutorIds.Length * 0.8, 2,
MidpointRounding.AwayFromZero);
var skillScore = (float)Math.Round(totalScore / task.Tags.Length, 2, MidpointRounding.AwayFromZero);
var teamScorePerUser = (float)Math.Round(9.5 / task.UserExecutorIds.Length * 0.2, 2,
MidpointRounding.AwayFromZero);

var tags = task.Tags;
var skillScorePerUser = tags.ToDictionary(x => x, x => skillScore);

var teams = await _projectTeamParticipantRepository.GetProjectTeamParticipantsByProjectId(ct, project.Id);
var teamExecutors = teams.Where(x => task.TeamExecutorIds.Contains(x.TeamId)).ToList();

Dictionary<string, float> teamsScore = new();

foreach (var team in teamExecutors)
{
var users = team.UserParticipants.Where(x => task.UserExecutorIds.Contains(x.Id)).ToList();
teamsScore[team.TeamId] = users.Count * teamScorePerUser;
}

ratingCalculation = new RatingCalculation(
project.OwnerUserId,
(float)0.5,
task.UserExecutorIds,
totalScore,
skillScorePerUser,
teamsScore);
}

task = await _projectTaskRepository.CloseProjectTask(ct, taskId, ProjectTaskStatuses.Close);

await _messageBus.Publish(task.ToClosedEvent());
await _messageBus.Publish(task.ToClosedEvent() with { RatingCalculation = ratingCalculation });
return Result.Ok(task);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public record ProjectTaskEntity(
string[] UserExecutorIds,
string[] Tags,
string[] Labels,
bool Reopened,
AuditInfo AuditInfo
);

Expand All @@ -24,27 +25,27 @@ public static ProjectTaskCreatedEvent ToCreatedEvent(this ProjectTaskEntity doc)
{
return new ProjectTaskCreatedEvent(doc.Id, doc.TaskNumber, doc.ProjectId, doc.ResponsibleUserId, doc.Name,
doc.Description,
doc.Status, doc.TeamExecutorIds, doc.UserExecutorIds, doc.Tags, doc.Labels);
doc.Status, doc.TeamExecutorIds, doc.UserExecutorIds, doc.Tags, doc.Labels, doc.Reopened);
}

public static ProjectTaskUpdatedEvent ToUpdatedEvent(this ProjectTaskEntity doc)
{
return new ProjectTaskUpdatedEvent(doc.Id, doc.TaskNumber, doc.ProjectId, doc.ResponsibleUserId, doc.Name,
doc.Description,
doc.Status, doc.TeamExecutorIds, doc.UserExecutorIds, doc.Tags, doc.Labels);
doc.Status, doc.TeamExecutorIds, doc.UserExecutorIds, doc.Tags, doc.Labels, doc.Reopened);
}

public static ProjectTaskDeletedEvent ToDeletedEvent(this ProjectTaskEntity doc)
{
return new ProjectTaskDeletedEvent(doc.Id, doc.TaskNumber, doc.ProjectId, doc.ResponsibleUserId, doc.Name,
doc.Description,
doc.Status, doc.TeamExecutorIds, doc.UserExecutorIds, doc.Tags, doc.Labels);
doc.Status, doc.TeamExecutorIds, doc.UserExecutorIds, doc.Tags, doc.Labels, doc.Reopened);
}

public static ProjectTaskClosedEvent ToClosedEvent(this ProjectTaskEntity doc)
{
return new ProjectTaskClosedEvent(doc.Id, doc.TaskNumber, doc.ProjectId, doc.ResponsibleUserId, doc.Name,
doc.Description,
doc.Status, doc.TeamExecutorIds, doc.UserExecutorIds, doc.Tags, doc.Labels);
doc.Status, doc.TeamExecutorIds, doc.UserExecutorIds, doc.Tags, doc.Labels, doc.Reopened, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,16 @@ public record ProjectTaskClosedEvent(
string[] TeamExecutorIds,
string[] UserExecutorIds,
string[] Tags,
string[] Labels
);
string[] Labels,
bool Reopened,
RatingCalculation? RatingCalculation
);

public record RatingCalculation(
string ProjectOwnerId,
float ProjectOwnerTotalScore,
string[] UserExecutorIds,
float UserTotalScore,
Dictionary<string, float> SkillScorePerUser,
Dictionary<string, float> TeamsTotalScore
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public record ProjectTaskCreatedEvent(
string[] TeamExecutorIds,
string[] UserExecutorIds,
string[] Tags,
string[] Labels
string[] Labels,
bool Reopened
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public record ProjectTaskDeletedEvent(
string[] TeamExecutorIds,
string[] UserExecutorIds,
string[] Tags,
string[] Labels
string[] Labels,
bool Reopened
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public record ProjectTaskUpdatedEvent(
string[] TeamExecutorIds,
string[] UserExecutorIds,
string[] Tags,
string[] Labels
string[] Labels,
bool Reopened
);
Loading