From 50d356c1bd298c4bce9b9df2eaf95738f922d878 Mon Sep 17 00:00:00 2001 From: Space-tourist <114026452+Space-tourist@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:47:39 +0300 Subject: [PATCH] Single url added to story --- .../AddStory/AddStoryCommandHandler.cs | 4 +- .../Validators/AddStoryCommandValidator.cs | 37 +++++++++++++-- .../Converters/StoryConverter.cs | 4 +- .../Converters/SummaryByStoryConverter.cs | 4 +- .../Messages.cs | 2 + .../Services/SummaryByStoryBuilder.cs | 12 ++--- .../Internal/GetStoryQuery.cs | 4 +- .../StoryReader.cs | 4 +- .../StoryRepository.cs | 12 ++--- .../Story.cs | 8 ++-- .../Common/StoryDto.cs | 4 +- .../Common/SummaryByStory.cs | 4 +- .../wwwroot/langs/en.json | 2 + .../wwwroot/langs/ru.json | 2 + .../2024_11_25_0_AddUrl.cs | 45 +++++++++++++++++++ .../AssessmentSession.stories.razor | 4 +- .../AssessmentSessionHistory.stories.razor | 12 ++--- .../AssessmentSession/StoryDetails.razor | 18 ++++---- 18 files changed, 130 insertions(+), 52 deletions(-) create mode 100644 src/Inc.TeamAssistant.Migrations/2024_11_25_0_AddUrl.cs diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStory/AddStoryCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStory/AddStoryCommandHandler.cs index b5f42fb82..485f59068 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStory/AddStoryCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStory/AddStoryCommandHandler.cs @@ -43,8 +43,8 @@ public async Task Handle(AddStoryCommand command, CancellationTok command.MessageContext.LanguageId, command.Title); - foreach (var link in command.Links) - story.AddLink(link); + if(command.Links.Any()) + story.AddLink(command.Links.Single()); foreach (var teammate in command.Teammates) story.AddStoryForEstimate(new StoryForEstimate( diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStory/Validators/AddStoryCommandValidator.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStory/Validators/AddStoryCommandValidator.cs index 93de0bd8b..aeaf3a6df 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStory/Validators/AddStoryCommandValidator.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStory/Validators/AddStoryCommandValidator.cs @@ -1,12 +1,16 @@ using FluentValidation; using Inc.TeamAssistant.Appraiser.Model.Commands.AddStory; +using Inc.TeamAssistant.Primitives.Languages; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.AddStory.Validators; internal sealed class AddStoryCommandValidator : AbstractValidator { - public AddStoryCommandValidator() + private readonly IMessageBuilder _messageBuilder; + public AddStoryCommandValidator(IMessageBuilder messageBuilder) { + _messageBuilder = messageBuilder ?? throw new ArgumentNullException(nameof(messageBuilder)); + RuleFor(e => e.TeamId) .NotEmpty(); @@ -18,10 +22,37 @@ public AddStoryCommandValidator() .Must(e => !e.StartsWith("/")) .WithMessage("'{PropertyName}' please enter text value."); - RuleForEach(e => e.Links) - .NotEmpty(); + RuleFor(e => e.Links) + .CustomAsync(CheckLinks); RuleFor(e => e.Teammates) .NotEmpty(); } + + private async Task CheckLinks( + IReadOnlyCollection links, + ValidationContext context, + CancellationToken token) + { + ArgumentNullException.ThrowIfNull(links); + ArgumentNullException.ThrowIfNull(context); + + if (links.Count > 1) + { + var errorMessage = await _messageBuilder.Build(Messages.Appraiser_MultipleLinkError, + context.InstanceToValidate.MessageContext.LanguageId); + context.AddFailure(nameof(AddStoryCommand.Links), errorMessage); + } + else + { + var link = links.FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(link) && link.Length > 2000) + { + var errorMessage = await _messageBuilder.Build(Messages.Appraiser_LinkLengthError, + context.InstanceToValidate.MessageContext.LanguageId); + context.AddFailure(nameof(AddStoryCommand.Links), errorMessage); + } + } + } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.Application/Converters/StoryConverter.cs b/src/Inc.TeamAssistant.Appraiser.Application/Converters/StoryConverter.cs index f2e473678..859307d2c 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/Converters/StoryConverter.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/Converters/StoryConverter.cs @@ -28,12 +28,12 @@ public static StoryDto Convert(Story story) return new( story.Id, story.Title, - story.Links.ToArray(), items, story.EstimateEnded, story.CalculateMean().DisplayValue, story.CalculateMedian().DisplayValue, story.AcceptedValue.DisplayValue, - story.RoundsCount); + story.RoundsCount, + story.Url); } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.Application/Converters/SummaryByStoryConverter.cs b/src/Inc.TeamAssistant.Appraiser.Application/Converters/SummaryByStoryConverter.cs index 000664950..ccad8aa2e 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/Converters/SummaryByStoryConverter.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/Converters/SummaryByStoryConverter.cs @@ -35,7 +35,6 @@ public static SummaryByStory ConvertTo(Story story) story.ExternalId, story.Title, story.StoryType.ToString(), - story.Links.ToArray(), story.EstimateEnded, story.CalculateMean().DisplayValue, story.CalculateMedian().DisplayValue, @@ -44,6 +43,7 @@ public static SummaryByStory ConvertTo(Story story) assessments, story.Accepted, assessmentsToAccept, - story.RoundsCount); + story.RoundsCount, + story.Url); } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.Application/Messages.cs b/src/Inc.TeamAssistant.Appraiser.Application/Messages.cs index b6bd4d292..44e792696 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/Messages.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/Messages.cs @@ -16,4 +16,6 @@ internal static class Messages public static readonly MessageId Connector_TeamNotFound = new(nameof(Connector_TeamNotFound)); public static readonly MessageId Appraiser_MissingTaskForEvaluate = new(nameof(Appraiser_MissingTaskForEvaluate)); public static readonly MessageId Appraiser_NumberOfRounds = new(nameof(Appraiser_NumberOfRounds)); + public static readonly MessageId Appraiser_MultipleLinkError = new(nameof(Appraiser_MultipleLinkError)); + public static readonly MessageId Appraiser_LinkLengthError = new(nameof(Appraiser_LinkLengthError)); } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.Application/Services/SummaryByStoryBuilder.cs b/src/Inc.TeamAssistant.Appraiser.Application/Services/SummaryByStoryBuilder.cs index 5ef7d7eaa..0fbce6a4b 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/Services/SummaryByStoryBuilder.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/Services/SummaryByStoryBuilder.cs @@ -70,10 +70,11 @@ private async Task AddBody(StringBuilder builder, SummaryByStory summary) summary.LanguageId); builder.AppendLine(storyHeader); + builder.AppendLine(summary.StoryTitle); - if (summary.StoryLinks.Any()) - foreach (var link in summary.StoryLinks) - builder.AppendLine(link); + + if (!string.IsNullOrWhiteSpace(summary.Url)) + builder.AppendLine(summary.Url); } private async Task AddEstimateSummary(StringBuilder builder, SummaryByStory summary) @@ -170,8 +171,7 @@ private async Task AddRoundsInfo(StringBuilder builder, SummaryByStory summary) ArgumentNullException.ThrowIfNull(summary); builder.AppendLine(); - builder.Append(await _messageBuilder.Build(Messages.Appraiser_NumberOfRounds, summary.LanguageId)); - builder.Append(' '); - builder.Append(summary.RoundsCount); + var roundsInfo = await _messageBuilder.Build(Messages.Appraiser_NumberOfRounds, summary.LanguageId); + builder.AppendLine($"{roundsInfo} {summary.RoundsCount}"); } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.DataAccess/Internal/GetStoryQuery.cs b/src/Inc.TeamAssistant.Appraiser.DataAccess/Internal/GetStoryQuery.cs index 1f59a82dd..481de1382 100644 --- a/src/Inc.TeamAssistant.Appraiser.DataAccess/Internal/GetStoryQuery.cs +++ b/src/Inc.TeamAssistant.Appraiser.DataAccess/Internal/GetStoryQuery.cs @@ -25,9 +25,9 @@ public static async Task> Get( s.language_id AS languageid, s.title AS title, s.external_id AS externalid, - s.links AS links, s.total_value AS totalvalue, - s.rounds_count AS roundscount + s.rounds_count AS roundscount, + s.url AS url FROM appraiser.stories AS s WHERE s.id = ANY(@story_ids); diff --git a/src/Inc.TeamAssistant.Appraiser.DataAccess/StoryReader.cs b/src/Inc.TeamAssistant.Appraiser.DataAccess/StoryReader.cs index 3758bcff7..2b9e6237b 100644 --- a/src/Inc.TeamAssistant.Appraiser.DataAccess/StoryReader.cs +++ b/src/Inc.TeamAssistant.Appraiser.DataAccess/StoryReader.cs @@ -33,9 +33,9 @@ public async Task> GetStories( s.language_id AS languageid, s.title AS title, s.external_id AS externalid, - s.links AS links, s.total_value AS totalvalue, - s.rounds_count AS roundscount + s.rounds_count AS roundscount, + s.url AS url FROM appraiser.stories AS s WHERE s.team_id = @team_id AND s.created <= @before AND (@from is null OR s.created >= @from);", new diff --git a/src/Inc.TeamAssistant.Appraiser.DataAccess/StoryRepository.cs b/src/Inc.TeamAssistant.Appraiser.DataAccess/StoryRepository.cs index d7be94413..4bec3bb88 100644 --- a/src/Inc.TeamAssistant.Appraiser.DataAccess/StoryRepository.cs +++ b/src/Inc.TeamAssistant.Appraiser.DataAccess/StoryRepository.cs @@ -47,10 +47,10 @@ public async Task Upsert(Story story, CancellationToken token) var upsertStory = new CommandDefinition(@" INSERT INTO appraiser.stories ( id, bot_id, story_type, created, team_id, chat_id, moderator_id, language_id, title, - external_id, links, total_value, rounds_count) + external_id, total_value, rounds_count, url) VALUES ( @id, @bot_id, @story_type, @created, @team_id, @chat_id, @moderator_id, @language_id, @title, - @external_id, @links::jsonb, @total_value, @rounds_count) + @external_id, @total_value, @rounds_count, @url) ON CONFLICT (id) DO UPDATE SET bot_id = EXCLUDED.bot_id, story_type = EXCLUDED.story_type, @@ -61,9 +61,9 @@ ON CONFLICT (id) DO UPDATE SET language_id = EXCLUDED.language_id, title = EXCLUDED.title, external_id = EXCLUDED.external_id, - links = EXCLUDED.links, total_value = EXCLUDED.total_value, - rounds_count = EXCLUDED.rounds_count;", + rounds_count = EXCLUDED.rounds_count, + url = EXCLUDED.url;", new { id = story.Id, @@ -76,9 +76,9 @@ ON CONFLICT (id) DO UPDATE SET language_id = story.LanguageId.Value, title = story.Title, external_id = story.ExternalId, - links = JsonSerializer.Serialize(story.Links), total_value = story.TotalValue, - rounds_count = story.RoundsCount + rounds_count = story.RoundsCount, + url = story.Url }, flags: CommandFlags.None, cancellationToken: token); diff --git a/src/Inc.TeamAssistant.Appraiser.Domain/Story.cs b/src/Inc.TeamAssistant.Appraiser.Domain/Story.cs index 15c941300..751a38287 100644 --- a/src/Inc.TeamAssistant.Appraiser.Domain/Story.cs +++ b/src/Inc.TeamAssistant.Appraiser.Domain/Story.cs @@ -18,18 +18,16 @@ public sealed class Story public bool Accepted => TotalValue.HasValue; public int? TotalValue { get; private set; } public int RoundsCount { get; private set; } + public string? Url { get; private set; } private readonly List _storyForEstimates; public IReadOnlyCollection StoryForEstimates => _storyForEstimates; - public ICollection Links { get; private set; } - private IEstimationStrategy EstimationStrategy => EstimationStrategyFactory.Create(StoryType); private Story() { _storyForEstimates = new(); - Links = new List(); } public Story( @@ -79,8 +77,8 @@ public void AddStoryForEstimate(StoryForEstimate storyForEstimate) public void AddLink(string link) { ArgumentException.ThrowIfNullOrWhiteSpace(link); - - Links.Add(link); + + Url = link; } public void Reset(long participantId, bool hasManagerAccess) diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Common/StoryDto.cs b/src/Inc.TeamAssistant.Appraiser.Model/Common/StoryDto.cs index 06c549ed8..196ae1ca6 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Common/StoryDto.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Common/StoryDto.cs @@ -3,10 +3,10 @@ namespace Inc.TeamAssistant.Appraiser.Model.Common; public sealed record StoryDto( Guid Id, string Title, - IReadOnlyCollection Links, IReadOnlyCollection StoryForEstimates, bool EstimateEnded, string Mean, string Median, string AcceptedValue, - int RoundsCount); \ No newline at end of file + int RoundsCount, + string? Url); \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Common/SummaryByStory.cs b/src/Inc.TeamAssistant.Appraiser.Model/Common/SummaryByStory.cs index 87afcb1b7..c020d9f45 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Common/SummaryByStory.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Common/SummaryByStory.cs @@ -10,7 +10,6 @@ public sealed record SummaryByStory( int? StoryExternalId, string StoryTitle, string StoryType, - IReadOnlyCollection StoryLinks, bool EstimateEnded, string Mean, string Median, @@ -19,4 +18,5 @@ public sealed record SummaryByStory( IReadOnlyCollection Assessments, bool Accepted, IReadOnlyCollection AssessmentsToAccept, - int RoundsCount); \ No newline at end of file + int RoundsCount, + string? Url); \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json index f94a67e97..2e1d01ce3 100644 --- a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json +++ b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json @@ -41,6 +41,8 @@ "Appraiser_PowerOfTwo_32SP": "32", "Appraiser_PowerOfTwo_64SP": "64", "Appraiser_NumberOfRounds": "Number of evaluation rounds", + "Appraiser_MultipleLinkError": "Description must contain one link", + "Appraiser_LinkLengthError": "Link length must be less than 2000 symbols", "CheckIn_GetStarted": "To get started, please add the bot to your chat room", "CheckIn_ConnectLinkText": "Use {0} to see our locations", diff --git a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json index 80fd33800..209aad7fa 100644 --- a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json +++ b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json @@ -41,6 +41,8 @@ "Appraiser_PowerOfTwo_32SP": "32", "Appraiser_PowerOfTwo_64SP": "64", "Appraiser_NumberOfRounds": "Количество раундов оценки", + "Appraiser_MultipleLinkError": "Описание должно содержать одну ссылку", + "Appraiser_LinkLengthError": "Длина ссылки не должна превышать 2000 символов", "CheckIn_GetStarted": "Добавьте бота в чат для начала работы", "CheckIn_ConnectLinkText": "Перейдите по ссылке {0} чтобы увидеть наши локации", diff --git a/src/Inc.TeamAssistant.Migrations/2024_11_25_0_AddUrl.cs b/src/Inc.TeamAssistant.Migrations/2024_11_25_0_AddUrl.cs new file mode 100644 index 000000000..b44a1a84a --- /dev/null +++ b/src/Inc.TeamAssistant.Migrations/2024_11_25_0_AddUrl.cs @@ -0,0 +1,45 @@ +using FluentMigrator; + +namespace Inc.TeamAssistant.Migrations; + +[Migration(2024_11_25_0)] + +public sealed class AddUrl : Migration +{ + public override void Up() + { + Create + .Column("url") + .OnTable("stories") + .InSchema("appraiser") + .AsString(2000) + .Nullable(); + + Execute.Sql( + """ + UPDATE appraiser.stories + SET url = links->>0; + """, + "Add single url to story"); + + Delete + .Column("links") + .FromTable("stories") + .InSchema("appraiser"); + } + + public override void Down() + { + Delete + .Column("url") + .FromTable("stories") + .InSchema("appraiser"); + + Create + .Column("links") + .OnTable("stories") + .InSchema("appraiser") + .AsCustom("jsonb") + .NotNullable(); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Stories/Features/AssessmentSession/AssessmentSession.stories.razor b/src/Inc.TeamAssistant.Stories/Features/AssessmentSession/AssessmentSession.stories.razor index fdb3783e1..a32660c85 100644 --- a/src/Inc.TeamAssistant.Stories/Features/AssessmentSession/AssessmentSession.stories.razor +++ b/src/Inc.TeamAssistant.Stories/Features/AssessmentSession/AssessmentSession.stories.razor @@ -79,7 +79,6 @@ new StoryDto( Id: Guid.NewGuid(), Title: "Create order from user card", - Links: ["https://amazon.com", "https://ozon.ru"], StoryForEstimates: [ new StoryForEstimateDto( ParticipantId: 0, @@ -104,7 +103,8 @@ Mean: "9.6", Median: "8", AcceptedValue: string.Empty, - RoundsCount: 1)); + RoundsCount: 1, + Url: "https://ozon.ru")); private void OnViewChanged(AssessmentType view) { diff --git a/src/Inc.TeamAssistant.Stories/Features/AssessmentSession/AssessmentSessionHistory.stories.razor b/src/Inc.TeamAssistant.Stories/Features/AssessmentSession/AssessmentSessionHistory.stories.razor index f5bee06cd..f0d435a17 100644 --- a/src/Inc.TeamAssistant.Stories/Features/AssessmentSession/AssessmentSessionHistory.stories.razor +++ b/src/Inc.TeamAssistant.Stories/Features/AssessmentSession/AssessmentSessionHistory.stories.razor @@ -50,7 +50,6 @@ new( Id: Guid.NewGuid(), Title: "Create order from user card", - Links: ["https://amazon.com", "https://ozon.ru"], StoryForEstimates: [ new StoryForEstimateDto( @@ -76,11 +75,11 @@ Mean: "9.6", Median: "8", AcceptedValue: "8", - RoundsCount: 1), + RoundsCount: 1, + Url: "https://ozon.ru"), new( Id: Guid.NewGuid(), Title: "Create order from user card", - Links: ["https://amazon.com", "https://ozon.ru"], StoryForEstimates: [ new StoryForEstimateDto( @@ -106,11 +105,11 @@ Mean: "9.6", Median: "8", AcceptedValue: "8", - RoundsCount: 1), + RoundsCount: 1, + Url: "https://ozon.ru"), new( Id: Guid.NewGuid(), Title: "Create order from user card", - Links: ["https://amazon.com", "https://ozon.ru"], StoryForEstimates: [ new StoryForEstimateDto( @@ -136,7 +135,8 @@ Mean: "9.6", Median: "8", AcceptedValue: "8", - RoundsCount: 1) + RoundsCount: 1, + Url: "https://ozon.ru") ]; private void OnViewChanged(AssessmentType view) diff --git a/src/Inc.TeamAssistant.WebUI/Features/AssessmentSession/StoryDetails.razor b/src/Inc.TeamAssistant.WebUI/Features/AssessmentSession/StoryDetails.razor index 0bb92f5e9..8220e6cac 100644 --- a/src/Inc.TeamAssistant.WebUI/Features/AssessmentSession/StoryDetails.razor +++ b/src/Inc.TeamAssistant.WebUI/Features/AssessmentSession/StoryDetails.razor @@ -1,15 +1,13 @@
-

@Story.Title

- @if (Story.Links.Any()) + @if (String.IsNullOrWhiteSpace(Story.Url)) { -
    - @foreach (var link in Story.Links) - { -
  • - @link -
  • - } -
+

@Story.Title

+ } + else + { +

+ @Story.Title +

}