diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/Messages.cs b/src/Inc.TeamAssistant.Connector.DataAccess/Messages.cs new file mode 100644 index 00000000..368b934c --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.DataAccess/Messages.cs @@ -0,0 +1,8 @@ +using Inc.TeamAssistant.Primitives.Languages; + +namespace Inc.TeamAssistant.Connector.DataAccess; + +public static class Messages +{ + public static readonly MessageId Connector_PersonNotFound = new(nameof(Connector_PersonNotFound)); +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/TeamAccessor.cs b/src/Inc.TeamAssistant.Connector.DataAccess/TeamAccessor.cs index 437fe6e5..63152bc2 100644 --- a/src/Inc.TeamAssistant.Connector.DataAccess/TeamAccessor.cs +++ b/src/Inc.TeamAssistant.Connector.DataAccess/TeamAccessor.cs @@ -44,6 +44,15 @@ public async Task> GetTeammates( return await _personRepository.Find(personId, token); } + public async Task GetPerson(long personId, CancellationToken token) + { + var person = await _personRepository.Find(personId, token); + if (person is null) + throw new TeamAssistantUserException(Messages.Connector_PersonNotFound, personId); + + return person; + } + public async Task GetClientLanguage(Guid botId, long personId, CancellationToken token) { return await _clientLanguageRepository.Get(botId, personId, token); diff --git a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json index c4e8a6f4..352ac80f 100644 --- a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json +++ b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json @@ -132,6 +132,7 @@ "Reviewer_PreviewTitle": "Previewing a draft of the task for review.", "Reviewer_PreviewReviewerTemplate": "The task will be assigned to {0}", "Reviewer_PreviewCheckDescription": "You need to provide a link to the source code and a description.", + "Reviewer_PreviewCheckTeammate": "Participant {0} is not part of team {1}.", "Reviewer_PreviewEditHelp": "To edit a draft, please edit the original post.", "Reviewer_PreviewMoveToReview": "Submit", "Reviewer_PreviewRemoveDraft": "Cancel", diff --git a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json index dd08c7ec..ef5fe654 100644 --- a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json +++ b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json @@ -132,6 +132,7 @@ "Reviewer_PreviewTitle": "Предварительный просмотр черновика задачи на ревью.", "Reviewer_PreviewReviewerTemplate": "Задача будет назначена на {0}", "Reviewer_PreviewCheckDescription": "Необходимо указать ссылку на исходный код и описание.", + "Reviewer_PreviewCheckTeammate": "Участник {0} не подключен к команде {1}.", "Reviewer_PreviewEditHelp": "Для редактирования черновика необходимо отредактировать исходное сообщение.", "Reviewer_PreviewMoveToReview": "Отправить на ревью", "Reviewer_PreviewRemoveDraft": "Отменить", diff --git a/src/Inc.TeamAssistant.Primitives/ITeamAccessor.cs b/src/Inc.TeamAssistant.Primitives/ITeamAccessor.cs index 151c6c7e..db8ba5ec 100644 --- a/src/Inc.TeamAssistant.Primitives/ITeamAccessor.cs +++ b/src/Inc.TeamAssistant.Primitives/ITeamAccessor.cs @@ -7,5 +7,6 @@ public interface ITeamAccessor Task<(Guid BotId, string TeamName)> GetTeamContext(Guid teamId, CancellationToken token); Task> GetTeammates(Guid teamId, DateTimeOffset now, CancellationToken token); Task FindPerson(long personId, CancellationToken token); + Task GetPerson(long personId, CancellationToken token); Task GetClientLanguage(Guid botId, long personId, CancellationToken token); } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Reviewer.Application/CommandHandlers/EditDraft/EditDraftCommandHandler.cs b/src/Inc.TeamAssistant.Reviewer.Application/CommandHandlers/EditDraft/EditDraftCommandHandler.cs index b0d4d8ca..c51b47fa 100644 --- a/src/Inc.TeamAssistant.Reviewer.Application/CommandHandlers/EditDraft/EditDraftCommandHandler.cs +++ b/src/Inc.TeamAssistant.Reviewer.Application/CommandHandlers/EditDraft/EditDraftCommandHandler.cs @@ -28,9 +28,11 @@ public async Task Handle(EditDraftCommand command, CancellationTo if (draft is not null) { draft.WithDescription(command.Description); - + if (command.MessageContext.TargetPersonId.HasValue) draft.WithTargetPerson(command.MessageContext.TargetPersonId.Value); + else + draft.WithoutTargetPerson(); var notification = await _reviewMessageBuilder.Build(draft, command.MessageContext.LanguageId, token); diff --git a/src/Inc.TeamAssistant.Reviewer.Application/CommandHandlers/MoveToReview/Validators/MoveToReviewCommandValidator.cs b/src/Inc.TeamAssistant.Reviewer.Application/CommandHandlers/MoveToReview/Validators/MoveToReviewCommandValidator.cs index e9afd85a..2466bc03 100644 --- a/src/Inc.TeamAssistant.Reviewer.Application/CommandHandlers/MoveToReview/Validators/MoveToReviewCommandValidator.cs +++ b/src/Inc.TeamAssistant.Reviewer.Application/CommandHandlers/MoveToReview/Validators/MoveToReviewCommandValidator.cs @@ -21,14 +21,25 @@ public MoveToReviewCommandValidator( RuleFor(e => e.DraftId) .NotEmpty() - .MustAsync(HasDescriptionAndLinks) - .WithMessage("'Description' must contains a link to the source code and some description"); + .CustomAsync(ValidateDraft); } - private async Task HasDescriptionAndLinks(Guid draftId, CancellationToken token) + private async Task ValidateDraft( + Guid draftId, + ValidationContext context, + CancellationToken token) { + ArgumentNullException.ThrowIfNull(context); + var draft = await _draftTaskForReviewRepository.GetById(draftId, token); - return _draftTaskForReviewService.HasDescriptionAndLinks(draft.Description); + if (!_draftTaskForReviewService.HasDescriptionAndLinks(draft.Description)) + context.AddFailure( + nameof(draft.Description), + "Must contains a link to the source code and some description"); + + if (draft.TargetPersonId.HasValue && + !await _draftTaskForReviewService.HasTeammate(draft.TeamId, draft.TargetPersonId.Value, token)) + context.AddFailure(nameof(draft.TargetPersonId), "Teammate not found"); } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Reviewer.Application/Messages.cs b/src/Inc.TeamAssistant.Reviewer.Application/Messages.cs index e6099c74..e57d02ef 100644 --- a/src/Inc.TeamAssistant.Reviewer.Application/Messages.cs +++ b/src/Inc.TeamAssistant.Reviewer.Application/Messages.cs @@ -32,6 +32,7 @@ internal static class Messages public static readonly MessageId Reviewer_PreviewTitle = new(nameof(Reviewer_PreviewTitle)); public static readonly MessageId Reviewer_PreviewReviewerTemplate = new(nameof(Reviewer_PreviewReviewerTemplate)); public static readonly MessageId Reviewer_PreviewCheckDescription = new(nameof(Reviewer_PreviewCheckDescription)); + public static readonly MessageId Reviewer_PreviewCheckTeammate = new(nameof(Reviewer_PreviewCheckTeammate)); public static readonly MessageId Reviewer_PreviewEditHelp = new(nameof(Reviewer_PreviewEditHelp)); public static readonly MessageId Reviewer_PreviewMoveToReview = new(nameof(Reviewer_PreviewMoveToReview)); public static readonly MessageId Reviewer_PreviewRemoveDraft = new(nameof(Reviewer_PreviewRemoveDraft)); diff --git a/src/Inc.TeamAssistant.Reviewer.Application/Services/DraftTaskForReviewService.cs b/src/Inc.TeamAssistant.Reviewer.Application/Services/DraftTaskForReviewService.cs index ff86ef4d..ab3d4d20 100644 --- a/src/Inc.TeamAssistant.Reviewer.Application/Services/DraftTaskForReviewService.cs +++ b/src/Inc.TeamAssistant.Reviewer.Application/Services/DraftTaskForReviewService.cs @@ -8,11 +8,22 @@ namespace Inc.TeamAssistant.Reviewer.Application.Services; internal sealed class DraftTaskForReviewService { private readonly IDraftTaskForReviewRepository _draftTaskForReviewRepository; + private readonly ITeamAccessor _teamAccessor; - public DraftTaskForReviewService(IDraftTaskForReviewRepository draftTaskForReviewRepository) + public DraftTaskForReviewService( + IDraftTaskForReviewRepository draftTaskForReviewRepository, + ITeamAccessor teamAccessor) { _draftTaskForReviewRepository = draftTaskForReviewRepository ?? throw new ArgumentNullException(nameof(draftTaskForReviewRepository)); + _teamAccessor = teamAccessor ?? throw new ArgumentNullException(nameof(teamAccessor)); + } + + public async Task HasTeammate(Guid teamId, long personId, CancellationToken token) + { + var teammates = await _teamAccessor.GetTeammates(teamId, DateTimeOffset.UtcNow, token); + + return teammates.Any(t => t.Id == personId); } public bool HasDescriptionAndLinks(string description) diff --git a/src/Inc.TeamAssistant.Reviewer.Application/Services/ReviewMessageBuilder.cs b/src/Inc.TeamAssistant.Reviewer.Application/Services/ReviewMessageBuilder.cs index 4fbc1947..68e99009 100644 --- a/src/Inc.TeamAssistant.Reviewer.Application/Services/ReviewMessageBuilder.cs +++ b/src/Inc.TeamAssistant.Reviewer.Application/Services/ReviewMessageBuilder.cs @@ -106,24 +106,37 @@ public async Task Build( ArgumentNullException.ThrowIfNull(draft); ArgumentNullException.ThrowIfNull(languageId); + var teamContext = await _teamAccessor.GetTeamContext(draft.TeamId, token); + var reviewTargetMessageTemplate = await _messageBuilder.Build( + Messages.Reviewer_PreviewReviewerTemplate, + languageId); var messageBuilder = new StringBuilder(); + messageBuilder.AppendLine(await _messageBuilder.Build(Messages.Reviewer_PreviewTitle, languageId)); messageBuilder.AppendLine(); messageBuilder.AppendLine(draft.Description); + messageBuilder.AppendLine(); if (draft.TargetPersonId.HasValue) { - var targetPersonMessageTemplate = await _messageBuilder.Build( - Messages.Reviewer_PreviewReviewerTemplate, - languageId); - var targetPerson = await _teamAccessor.FindPerson(draft.TargetPersonId.Value, token); - if (targetPerson is null) - throw new TeamAssistantUserException(Messages.Connector_PersonNotFound, draft.TargetPersonId.Value); + var reviewTarget = await _teamAccessor.GetPerson(draft.TargetPersonId.Value, token); + messageBuilder.AppendLine(string.Format(reviewTargetMessageTemplate, reviewTarget.DisplayName)); - messageBuilder.AppendLine(); - messageBuilder.AppendLine(string.Format(targetPersonMessageTemplate, targetPerson.DisplayName)); + if (!await _service.HasTeammate(draft.TeamId, draft.TargetPersonId.Value, token)) + { + messageBuilder.AppendLine(); + messageBuilder.Append('❗'); + messageBuilder.Append(await _messageBuilder.Build( + Messages.Reviewer_PreviewCheckTeammate, + languageId, + reviewTarget.DisplayName, + teamContext.TeamName)); + messageBuilder.AppendLine(); + } } - + else + messageBuilder.AppendLine(string.Format(reviewTargetMessageTemplate, teamContext.TeamName)); + if (!_service.HasDescriptionAndLinks(draft.Description)) { messageBuilder.AppendLine(); diff --git a/src/Inc.TeamAssistant.Reviewer.Domain/DraftTaskForReview.cs b/src/Inc.TeamAssistant.Reviewer.Domain/DraftTaskForReview.cs index 3375201c..1f5b7a08 100644 --- a/src/Inc.TeamAssistant.Reviewer.Domain/DraftTaskForReview.cs +++ b/src/Inc.TeamAssistant.Reviewer.Domain/DraftTaskForReview.cs @@ -58,6 +58,13 @@ public DraftTaskForReview WithTargetPerson(long personId) return this; } + public DraftTaskForReview WithoutTargetPerson() + { + TargetPersonId = null; + + return this; + } + public DraftTaskForReview WithPreviewMessage(int messageId) { PreviewMessageId = messageId;