From fe154207907bdd62ebefe2d50b4dc1204d3d2d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucie=20Proch=C3=A1zkov=C3=A1?= Date: Sat, 7 May 2022 01:46:56 +0200 Subject: [PATCH 01/18] StandUpStreak --- .../HonzaBotnerDbContext.cs | 2 + src/HonzaBotner.Database/StandUpStreak.cs | 13 ++ .../Jobs/StandUpJobProvider.cs | 9 +- .../Dto/StandUpStreak.cs | 13 ++ .../IStandUpStreakService.cs | 13 ++ .../ServiceCollectionExtensions.cs | 1 + .../StandUpStreakService.cs | 119 ++++++++++++++++++ 7 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/HonzaBotner.Database/StandUpStreak.cs create mode 100644 src/HonzaBotner.Services.Contract/Dto/StandUpStreak.cs create mode 100644 src/HonzaBotner.Services.Contract/IStandUpStreakService.cs create mode 100644 src/HonzaBotner.Services/StandUpStreakService.cs diff --git a/src/HonzaBotner.Database/HonzaBotnerDbContext.cs b/src/HonzaBotner.Database/HonzaBotnerDbContext.cs index bbc34d90..4260f131 100644 --- a/src/HonzaBotner.Database/HonzaBotnerDbContext.cs +++ b/src/HonzaBotner.Database/HonzaBotnerDbContext.cs @@ -27,4 +27,6 @@ protected override void OnModelCreating(ModelBuilder builder) public DbSet RoleBindings { get; set; } public DbSet Warnings { get; set; } public DbSet Reminders { get; set; } + public DbSet StandUpStreaks { get; set; } + } diff --git a/src/HonzaBotner.Database/StandUpStreak.cs b/src/HonzaBotner.Database/StandUpStreak.cs new file mode 100644 index 00000000..9a9b870f --- /dev/null +++ b/src/HonzaBotner.Database/StandUpStreak.cs @@ -0,0 +1,13 @@ +using System; + +namespace HonzaBotner.Database; + +public class StandUpStreak +{ + public int Id { get; set; } + public ulong UserId { get; set; } + public int Streak { get; set; } + public int LongestStreak { get; set; } + public int Freezes { get; set; } + public DateTime LastDayOfStreak { get; set; } +} diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 6e9e9c4e..62a56ece 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -8,6 +8,7 @@ using HonzaBotner.Discord.Services.Helpers; using HonzaBotner.Discord.Services.Options; using HonzaBotner.Scheduler.Contract; +using HonzaBotner.Services.Contract; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -22,15 +23,19 @@ public class StandUpJobProvider : IJob private readonly CommonCommandOptions _commonOptions; + private readonly IStandUpStreakService _streakService; + public StandUpJobProvider( ILogger logger, DiscordWrapper discord, - IOptions commonOptions + IOptions commonOptions, + IStandUpStreakService streakService ) { _logger = logger; _discord = discord; _commonOptions = commonOptions.Value; + _streakService = streakService; } /// @@ -94,6 +99,8 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) { fail.Increment(priority); } + + await _streakService.UpdateStreak(msg.Author.Id); } } diff --git a/src/HonzaBotner.Services.Contract/Dto/StandUpStreak.cs b/src/HonzaBotner.Services.Contract/Dto/StandUpStreak.cs new file mode 100644 index 00000000..60736b08 --- /dev/null +++ b/src/HonzaBotner.Services.Contract/Dto/StandUpStreak.cs @@ -0,0 +1,13 @@ +using System; + +namespace HonzaBotner.Services.Contract.Dto; + +public class StandUpStreak{ + + public int Id { get; set; } + public ulong UserId { get; set; } + public int Streak { get; set; } + public int LongestStreak { get; set; } + public int Freezes { get; set; } + public DateTime LastDayOfStreak { get; set; } +}; diff --git a/src/HonzaBotner.Services.Contract/IStandUpStreakService.cs b/src/HonzaBotner.Services.Contract/IStandUpStreakService.cs new file mode 100644 index 00000000..822e3546 --- /dev/null +++ b/src/HonzaBotner.Services.Contract/IStandUpStreakService.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using HonzaBotner.Services.Contract.Dto; +namespace HonzaBotner.Services.Contract; + +public interface IStandUpStreakService +{ + Task GetStreak(ulong userId); + + Task UpdateStreak(ulong userId); + + Task IsValidStreak(ulong userId); +} diff --git a/src/HonzaBotner.Services/ServiceCollectionExtensions.cs b/src/HonzaBotner.Services/ServiceCollectionExtensions.cs index a4a84f76..83e5a1dd 100644 --- a/src/HonzaBotner.Services/ServiceCollectionExtensions.cs +++ b/src/HonzaBotner.Services/ServiceCollectionExtensions.cs @@ -31,6 +31,7 @@ public static IServiceCollection AddBotnerServices(this IServiceCollection servi serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); + serviceCollection.AddScoped(); return serviceCollection; } diff --git a/src/HonzaBotner.Services/StandUpStreakService.cs b/src/HonzaBotner.Services/StandUpStreakService.cs new file mode 100644 index 00000000..824adc1e --- /dev/null +++ b/src/HonzaBotner.Services/StandUpStreakService.cs @@ -0,0 +1,119 @@ +using System; +using System.Threading.Tasks; +using HonzaBotner.Database; +using HonzaBotner.Services.Contract; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using StandUpStreak = HonzaBotner.Services.Contract.Dto.StandUpStreak; + +namespace HonzaBotner.Services; + +public class StandUpStreakService : IStandUpStreakService +{ + private readonly HonzaBotnerDbContext _dbContext; + private readonly ILogger _logger; + + public StandUpStreakService(HonzaBotnerDbContext dbContext, ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + public async Task GetStreak(ulong userId) + { + Database.StandUpStreak? standUpStreak = await _dbContext.StandUpStreaks + .FirstOrDefaultAsync(streak => streak.UserId == userId); + + return standUpStreak == null ? null : GetDto(standUpStreak); + } + + public async Task UpdateStreak(ulong userId) + { + Database.StandUpStreak? streak = await _dbContext.StandUpStreaks + .FirstOrDefaultAsync(streak => streak.UserId == userId); + ; + + // Create new streak for a new user + if (streak is null) + { + Database.StandUpStreak newStreak = new() + { + UserId = userId, + Freezes = 0, + LastDayOfStreak = DateTime.Today.AddDays(-1), + Streak = 1, + LongestStreak = 1 + }; + + await _dbContext.StandUpStreaks.AddAsync(newStreak); + + try + { + await _dbContext.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Couldn't add streak {@Streak}", streak); + } + + return; + } + + int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; + + if (days > streak.Freezes) //Streak broken + { + streak.Freezes = 0; + streak.LastDayOfStreak = DateTime.Today.AddDays(-1); + streak.Streak = 1; + } + else //streak restored + { + streak.Freezes -= days; + streak.Streak++; + streak.LastDayOfStreak = DateTime.Today.AddDays(-1); + + if (streak.Streak > streak.LongestStreak) + { + streak.LongestStreak = streak.Streak; + } + + if (streak.Streak % 6 == 0) // freeze acquired + { + streak.Freezes++; + } + } + + try + { + _dbContext.StandUpStreaks.Update(streak); + await _dbContext.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Couldn't update streak {@Streak}", streak); + } + } + + public async Task IsValidStreak(ulong userId) + { + StandUpStreak? streak = await GetStreak(userId); + + if (streak is null) + return false; + int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; + + return days <= streak.Freezes; + } + + private static StandUpStreak GetDto(Database.StandUpStreak standUpStreak) => + new StandUpStreak() + { + Id = standUpStreak.Id, + UserId = standUpStreak.UserId, + Streak = standUpStreak.Streak, + LongestStreak = standUpStreak.LongestStreak, + Freezes = standUpStreak.Freezes, + LastDayOfStreak = standUpStreak.LastDayOfStreak + }; +} From 3d1620ef64d5eb78abc93ae18dceec49f78a6307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucie=20Proch=C3=A1zkov=C3=A1?= Date: Sat, 7 May 2022 13:06:54 +0200 Subject: [PATCH 02/18] Changes implemented. --- .../Jobs/StandUpJobProvider.cs | 5 +++++ src/HonzaBotner.Services/StandUpStreakService.cs | 13 +++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 62a56ece..f22eb55b 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -86,6 +86,7 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) foreach (DiscordMessage msg in messageList.Where(msg => msg.Timestamp.Date == yesterday)) { + bool streakMaintained = false; foreach (Match match in Regex.Matches(msg.Content)) { string state = match.Groups["State"].ToString(); @@ -94,12 +95,16 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) if (OkList.Any(s => state.Contains(s))) { ok.Increment(priority); + streakMaintained = true; } else { fail.Increment(priority); } + } + if (streakMaintained) + { await _streakService.UpdateStreak(msg.Author.Id); } } diff --git a/src/HonzaBotner.Services/StandUpStreakService.cs b/src/HonzaBotner.Services/StandUpStreakService.cs index 824adc1e..4be204a7 100644 --- a/src/HonzaBotner.Services/StandUpStreakService.cs +++ b/src/HonzaBotner.Services/StandUpStreakService.cs @@ -11,9 +11,10 @@ namespace HonzaBotner.Services; public class StandUpStreakService : IStandUpStreakService { private readonly HonzaBotnerDbContext _dbContext; - private readonly ILogger _logger; + private readonly ILogger _logger; + private const int DaysToAcquireFreeze = 6; - public StandUpStreakService(HonzaBotnerDbContext dbContext, ILogger logger) + public StandUpStreakService(HonzaBotnerDbContext dbContext, ILogger logger) { _dbContext = dbContext; _logger = logger; @@ -31,7 +32,6 @@ public async Task UpdateStreak(ulong userId) { Database.StandUpStreak? streak = await _dbContext.StandUpStreaks .FirstOrDefaultAsync(streak => streak.UserId == userId); - ; // Create new streak for a new user if (streak is null) @@ -61,6 +61,11 @@ public async Task UpdateStreak(ulong userId) int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; + if (days == -1) //Streak was already restored today + { + return; + } + if (days > streak.Freezes) //Streak broken { streak.Freezes = 0; @@ -78,7 +83,7 @@ public async Task UpdateStreak(ulong userId) streak.LongestStreak = streak.Streak; } - if (streak.Streak % 6 == 0) // freeze acquired + if (streak.Streak % DaysToAcquireFreeze == 0) // freeze acquired { streak.Freezes++; } From 4e42d7233bffb18403d39442b0756a069e3ff0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucie=20Proch=C3=A1zkov=C3=A1?= Date: Sun, 8 May 2022 00:06:40 +0200 Subject: [PATCH 03/18] Streaks migration --- .../20220507220503_StandUpStreak.cs | 37 +++++++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 30 ++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/HonzaBotner/Migrations/20220507220503_StandUpStreak.cs diff --git a/src/HonzaBotner/Migrations/20220507220503_StandUpStreak.cs b/src/HonzaBotner/Migrations/20220507220503_StandUpStreak.cs new file mode 100644 index 00000000..3d3392e7 --- /dev/null +++ b/src/HonzaBotner/Migrations/20220507220503_StandUpStreak.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + public partial class StandUpStreak : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "StandUpStreaks", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "numeric(20,0)", nullable: false), + Streak = table.Column(type: "integer", nullable: false), + LongestStreak = table.Column(type: "integer", nullable: false), + Freezes = table.Column(type: "integer", nullable: false), + LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StandUpStreaks", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StandUpStreaks"); + } + } +} diff --git a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs index 47f71d51..aa37f028 100644 --- a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("ProductVersion", "6.0.4") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -88,6 +88,34 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RoleBindings"); }); + modelBuilder.Entity("HonzaBotner.Database.StandUpStreak", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Freezes") + .HasColumnType("integer"); + + b.Property("LastDayOfStreak") + .HasColumnType("timestamp with time zone"); + + b.Property("LongestStreak") + .HasColumnType("integer"); + + b.Property("Streak") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("StandUpStreaks"); + }); + modelBuilder.Entity("HonzaBotner.Database.Verification", b => { b.Property("UserId") From ce01cb388d417ba8d71b4050a0da00c33168bad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucie=20Proch=C3=A1zkov=C3=A1?= Date: Sun, 8 May 2022 00:28:35 +0200 Subject: [PATCH 04/18] StandUpStats - counting stats --- .../HonzaBotnerDbContext.cs | 2 +- .../{StandUpStreak.cs => StandUpStat.cs} | 6 +- .../Jobs/StandUpJobProvider.cs | 14 +- .../Dto/{StandUpStreak.cs => StandUpStat.cs} | 2 +- .../IStandUpStatsService.cs | 14 ++ .../IStandUpStreakService.cs | 13 -- .../ServiceCollectionExtensions.cs | 2 +- .../StandUpStatsService.cs | 177 ++++++++++++++++++ .../StandUpStreakService.cs | 124 ------------ .../20220507222749_StandUpStats.Designer.cs | 177 ++++++++++++++++++ ...reak.cs => 20220507222749_StandUpStats.cs} | 14 +- .../ApplicationDbContextModelSnapshot.cs | 16 +- 12 files changed, 409 insertions(+), 152 deletions(-) rename src/HonzaBotner.Database/{StandUpStreak.cs => StandUpStat.cs} (59%) rename src/HonzaBotner.Services.Contract/Dto/{StandUpStreak.cs => StandUpStat.cs} (91%) create mode 100644 src/HonzaBotner.Services.Contract/IStandUpStatsService.cs delete mode 100644 src/HonzaBotner.Services.Contract/IStandUpStreakService.cs create mode 100644 src/HonzaBotner.Services/StandUpStatsService.cs delete mode 100644 src/HonzaBotner.Services/StandUpStreakService.cs create mode 100644 src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs rename src/HonzaBotner/Migrations/{20220507220503_StandUpStreak.cs => 20220507222749_StandUpStats.cs} (67%) diff --git a/src/HonzaBotner.Database/HonzaBotnerDbContext.cs b/src/HonzaBotner.Database/HonzaBotnerDbContext.cs index 4260f131..081ca254 100644 --- a/src/HonzaBotner.Database/HonzaBotnerDbContext.cs +++ b/src/HonzaBotner.Database/HonzaBotnerDbContext.cs @@ -27,6 +27,6 @@ protected override void OnModelCreating(ModelBuilder builder) public DbSet RoleBindings { get; set; } public DbSet Warnings { get; set; } public DbSet Reminders { get; set; } - public DbSet StandUpStreaks { get; set; } + public DbSet StandUpStats { get; set; } } diff --git a/src/HonzaBotner.Database/StandUpStreak.cs b/src/HonzaBotner.Database/StandUpStat.cs similarity index 59% rename from src/HonzaBotner.Database/StandUpStreak.cs rename to src/HonzaBotner.Database/StandUpStat.cs index 9a9b870f..1f1a2f08 100644 --- a/src/HonzaBotner.Database/StandUpStreak.cs +++ b/src/HonzaBotner.Database/StandUpStat.cs @@ -2,7 +2,7 @@ namespace HonzaBotner.Database; -public class StandUpStreak +public class StandUpStat { public int Id { get; set; } public ulong UserId { get; set; } @@ -10,4 +10,8 @@ public class StandUpStreak public int LongestStreak { get; set; } public int Freezes { get; set; } public DateTime LastDayOfStreak { get; set; } + public int LastDayCompleted { get; set; } + public int LastDayTasks { get; set; } + public int TotalCompleted { get; set; } + public int TotalTasks { get; set; } } diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index f22eb55b..abdcb74f 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -23,19 +23,19 @@ public class StandUpJobProvider : IJob private readonly CommonCommandOptions _commonOptions; - private readonly IStandUpStreakService _streakService; + private readonly IStandUpStatsService _statsService; public StandUpJobProvider( ILogger logger, DiscordWrapper discord, IOptions commonOptions, - IStandUpStreakService streakService + IStandUpStatsService statsService ) { _logger = logger; _discord = discord; _commonOptions = commonOptions.Value; - _streakService = streakService; + _statsService = statsService; } /// @@ -87,15 +87,19 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) foreach (DiscordMessage msg in messageList.Where(msg => msg.Timestamp.Date == yesterday)) { bool streakMaintained = false; + int total = 0; + int completed = 0; foreach (Match match in Regex.Matches(msg.Content)) { string state = match.Groups["State"].ToString(); string priority = match.Groups["Priority"].ToString(); + total++; if (OkList.Any(s => state.Contains(s))) { ok.Increment(priority); streakMaintained = true; + completed++; } else { @@ -103,9 +107,11 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) } } + await _statsService.UpdateStats(msg.Author.Id, completed, total); + if (streakMaintained) { - await _streakService.UpdateStreak(msg.Author.Id); + await _statsService.UpdateStreak(msg.Author.Id); } } diff --git a/src/HonzaBotner.Services.Contract/Dto/StandUpStreak.cs b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs similarity index 91% rename from src/HonzaBotner.Services.Contract/Dto/StandUpStreak.cs rename to src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs index 60736b08..0c2cf629 100644 --- a/src/HonzaBotner.Services.Contract/Dto/StandUpStreak.cs +++ b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs @@ -2,7 +2,7 @@ namespace HonzaBotner.Services.Contract.Dto; -public class StandUpStreak{ +public class StandUpStat{ public int Id { get; set; } public ulong UserId { get; set; } diff --git a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs new file mode 100644 index 00000000..4f5de63d --- /dev/null +++ b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using HonzaBotner.Services.Contract.Dto; +namespace HonzaBotner.Services.Contract; + +public interface IStandUpStatsService +{ + Task GetStreak(ulong userId); + + Task UpdateStreak(ulong userId); + + Task IsValidStreak(ulong userId); + + Task UpdateStats(ulong userId, int completed, int total); +} diff --git a/src/HonzaBotner.Services.Contract/IStandUpStreakService.cs b/src/HonzaBotner.Services.Contract/IStandUpStreakService.cs deleted file mode 100644 index 822e3546..00000000 --- a/src/HonzaBotner.Services.Contract/IStandUpStreakService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading.Tasks; -using HonzaBotner.Services.Contract.Dto; -namespace HonzaBotner.Services.Contract; - -public interface IStandUpStreakService -{ - Task GetStreak(ulong userId); - - Task UpdateStreak(ulong userId); - - Task IsValidStreak(ulong userId); -} diff --git a/src/HonzaBotner.Services/ServiceCollectionExtensions.cs b/src/HonzaBotner.Services/ServiceCollectionExtensions.cs index 83e5a1dd..3ef779a9 100644 --- a/src/HonzaBotner.Services/ServiceCollectionExtensions.cs +++ b/src/HonzaBotner.Services/ServiceCollectionExtensions.cs @@ -31,7 +31,7 @@ public static IServiceCollection AddBotnerServices(this IServiceCollection servi serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); return serviceCollection; } diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs new file mode 100644 index 00000000..8d625d6f --- /dev/null +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -0,0 +1,177 @@ +using System; +using System.Threading.Tasks; +using HonzaBotner.Database; +using HonzaBotner.Services.Contract; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using StandUpStat = HonzaBotner.Services.Contract.Dto.StandUpStat; + +namespace HonzaBotner.Services; + +public class StandUpStatsService : IStandUpStatsService +{ + private readonly HonzaBotnerDbContext _dbContext; + private readonly ILogger _logger; + private const int DaysToAcquireFreeze = 6; + + public StandUpStatsService(HonzaBotnerDbContext dbContext, ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + public async Task GetStreak(ulong userId) + { + Database.StandUpStat? standUpStat = await _dbContext.StandUpStats + .FirstOrDefaultAsync(streak => streak.UserId == userId); + + return standUpStat == null ? null : GetDto(standUpStat); + } + + public async Task UpdateStreak(ulong userId) + { + Database.StandUpStat? streak = await _dbContext.StandUpStats + .FirstOrDefaultAsync(streak => streak.UserId == userId); + + // Create new streak for a new user + if (streak is null) + { + Database.StandUpStat newStat = new() + { + UserId = userId, + Freezes = 0, + LastDayOfStreak = DateTime.Today.AddDays(-1), + Streak = 1, + LongestStreak = 1, + LastDayCompleted = 0, + LastDayTasks = 0, + TotalCompleted = 0, + TotalTasks = 0 + }; + + await _dbContext.StandUpStats.AddAsync(newStat); + + try + { + await _dbContext.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Couldn't add streak {@Streak}", streak); + } + + return; + } + + int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; + + if (days == -1) //Streak was already restored today + { + return; + } + + if (days > streak.Freezes) //Streak broken + { + streak.Freezes = 0; + streak.LastDayOfStreak = DateTime.Today.AddDays(-1); + streak.Streak = 1; + } + else //streak restored + { + streak.Freezes -= days; + streak.Streak++; + streak.LastDayOfStreak = DateTime.Today.AddDays(-1); + + if (streak.Streak > streak.LongestStreak) + { + streak.LongestStreak = streak.Streak; + } + + if (streak.Streak % DaysToAcquireFreeze == 0) // freeze acquired + { + streak.Freezes++; + } + } + + try + { + _dbContext.StandUpStats.Update(streak); + await _dbContext.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Couldn't update streak {@Streak}", streak); + } + } + + public async Task IsValidStreak(ulong userId) + { + StandUpStat? streak = await GetStreak(userId); + + if (streak is null) + return false; + int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; + + return days <= streak.Freezes; + } + + public async Task UpdateStats(ulong userId, int completed, int total) { + Database.StandUpStat? stat = await _dbContext.StandUpStats + .FirstOrDefaultAsync(streak => streak.UserId == userId); + + if (stat is null) + { + Database.StandUpStat newStat = new() + { + UserId = userId, + Freezes = 0, + LastDayOfStreak = DateTime.Today.AddDays(-1), + Streak = 1, + LongestStreak = 1, + LastDayCompleted = completed, + LastDayTasks = total, + TotalCompleted = completed, + TotalTasks = total + }; + + await _dbContext.StandUpStats.AddAsync(newStat); + + try + { + await _dbContext.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Couldn't add streak {@Stat}", stat); + } + + return; + } + + stat.LastDayCompleted = completed; + stat.LastDayTasks = total; + stat.TotalCompleted += completed; + stat.TotalTasks += total; + + try + { + _dbContext.StandUpStats.Update(stat); + await _dbContext.SaveChangesAsync(); + } + catch (Exception e) + { + _logger.LogError(e, "Couldn't update streak {@Stat}", stat); + } + } + +private static StandUpStat GetDto(Database.StandUpStat standUpStat) => + new StandUpStat() + { + Id = standUpStat.Id, + UserId = standUpStat.UserId, + Streak = standUpStat.Streak, + LongestStreak = standUpStat.LongestStreak, + Freezes = standUpStat.Freezes, + LastDayOfStreak = standUpStat.LastDayOfStreak + }; +} diff --git a/src/HonzaBotner.Services/StandUpStreakService.cs b/src/HonzaBotner.Services/StandUpStreakService.cs deleted file mode 100644 index 4be204a7..00000000 --- a/src/HonzaBotner.Services/StandUpStreakService.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Threading.Tasks; -using HonzaBotner.Database; -using HonzaBotner.Services.Contract; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using StandUpStreak = HonzaBotner.Services.Contract.Dto.StandUpStreak; - -namespace HonzaBotner.Services; - -public class StandUpStreakService : IStandUpStreakService -{ - private readonly HonzaBotnerDbContext _dbContext; - private readonly ILogger _logger; - private const int DaysToAcquireFreeze = 6; - - public StandUpStreakService(HonzaBotnerDbContext dbContext, ILogger logger) - { - _dbContext = dbContext; - _logger = logger; - } - - public async Task GetStreak(ulong userId) - { - Database.StandUpStreak? standUpStreak = await _dbContext.StandUpStreaks - .FirstOrDefaultAsync(streak => streak.UserId == userId); - - return standUpStreak == null ? null : GetDto(standUpStreak); - } - - public async Task UpdateStreak(ulong userId) - { - Database.StandUpStreak? streak = await _dbContext.StandUpStreaks - .FirstOrDefaultAsync(streak => streak.UserId == userId); - - // Create new streak for a new user - if (streak is null) - { - Database.StandUpStreak newStreak = new() - { - UserId = userId, - Freezes = 0, - LastDayOfStreak = DateTime.Today.AddDays(-1), - Streak = 1, - LongestStreak = 1 - }; - - await _dbContext.StandUpStreaks.AddAsync(newStreak); - - try - { - await _dbContext.SaveChangesAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Couldn't add streak {@Streak}", streak); - } - - return; - } - - int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; - - if (days == -1) //Streak was already restored today - { - return; - } - - if (days > streak.Freezes) //Streak broken - { - streak.Freezes = 0; - streak.LastDayOfStreak = DateTime.Today.AddDays(-1); - streak.Streak = 1; - } - else //streak restored - { - streak.Freezes -= days; - streak.Streak++; - streak.LastDayOfStreak = DateTime.Today.AddDays(-1); - - if (streak.Streak > streak.LongestStreak) - { - streak.LongestStreak = streak.Streak; - } - - if (streak.Streak % DaysToAcquireFreeze == 0) // freeze acquired - { - streak.Freezes++; - } - } - - try - { - _dbContext.StandUpStreaks.Update(streak); - await _dbContext.SaveChangesAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Couldn't update streak {@Streak}", streak); - } - } - - public async Task IsValidStreak(ulong userId) - { - StandUpStreak? streak = await GetStreak(userId); - - if (streak is null) - return false; - int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; - - return days <= streak.Freezes; - } - - private static StandUpStreak GetDto(Database.StandUpStreak standUpStreak) => - new StandUpStreak() - { - Id = standUpStreak.Id, - UserId = standUpStreak.UserId, - Streak = standUpStreak.Streak, - LongestStreak = standUpStreak.LongestStreak, - Freezes = standUpStreak.Freezes, - LastDayOfStreak = standUpStreak.LastDayOfStreak - }; -} diff --git a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs new file mode 100644 index 00000000..8822cace --- /dev/null +++ b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs @@ -0,0 +1,177 @@ +// +using System; +using HonzaBotner.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + [DbContext(typeof(HonzaBotnerDbContext))] + [Migration("20220507222749_StandUpStats")] + partial class StandUpStats + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HonzaBotner.Database.CountedEmoji", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("FirstUsedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Times") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("CountedEmojis"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("OwnerId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("HonzaBotner.Database.RoleBinding", b => + { + b.Property("Emoji") + .HasColumnType("text"); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Emoji", "ChannelId", "MessageId", "RoleId"); + + b.ToTable("RoleBindings"); + }); + + modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Freezes") + .HasColumnType("integer"); + + b.Property("LastDayCompleted") + .HasColumnType("integer"); + + b.Property("LastDayOfStreak") + .HasColumnType("timestamp with time zone"); + + b.Property("LastDayTasks") + .HasColumnType("integer"); + + b.Property("LongestStreak") + .HasColumnType("integer"); + + b.Property("Streak") + .HasColumnType("integer"); + + b.Property("TotalCompleted") + .HasColumnType("integer"); + + b.Property("TotalTasks") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("StandUpStats"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Verification", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("AuthId") + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.HasIndex("AuthId") + .IsUnique(); + + b.ToTable("Verifications"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IssuerId") + .HasColumnType("numeric(20,0)"); + + b.Property("Reason") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HonzaBotner/Migrations/20220507220503_StandUpStreak.cs b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs similarity index 67% rename from src/HonzaBotner/Migrations/20220507220503_StandUpStreak.cs rename to src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs index 3d3392e7..299347d6 100644 --- a/src/HonzaBotner/Migrations/20220507220503_StandUpStreak.cs +++ b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs @@ -6,12 +6,12 @@ namespace HonzaBotner.Migrations { - public partial class StandUpStreak : Migration + public partial class StandUpStats : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "StandUpStreaks", + name: "StandUpStats", columns: table => new { Id = table.Column(type: "integer", nullable: false) @@ -20,18 +20,22 @@ protected override void Up(MigrationBuilder migrationBuilder) Streak = table.Column(type: "integer", nullable: false), LongestStreak = table.Column(type: "integer", nullable: false), Freezes = table.Column(type: "integer", nullable: false), - LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false) + LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false), + LastDayCompleted = table.Column(type: "integer", nullable: false), + LastDayTasks = table.Column(type: "integer", nullable: false), + TotalCompleted = table.Column(type: "integer", nullable: false), + TotalTasks = table.Column(type: "integer", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_StandUpStreaks", x => x.Id); + table.PrimaryKey("PK_StandUpStats", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "StandUpStreaks"); + name: "StandUpStats"); } } } diff --git a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs index aa37f028..718d8e23 100644 --- a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs @@ -88,7 +88,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RoleBindings"); }); - modelBuilder.Entity("HonzaBotner.Database.StandUpStreak", b => + modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -99,21 +99,33 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Freezes") .HasColumnType("integer"); + b.Property("LastDayCompleted") + .HasColumnType("integer"); + b.Property("LastDayOfStreak") .HasColumnType("timestamp with time zone"); + b.Property("LastDayTasks") + .HasColumnType("integer"); + b.Property("LongestStreak") .HasColumnType("integer"); b.Property("Streak") .HasColumnType("integer"); + b.Property("TotalCompleted") + .HasColumnType("integer"); + + b.Property("TotalTasks") + .HasColumnType("integer"); + b.Property("UserId") .HasColumnType("numeric(20,0)"); b.HasKey("Id"); - b.ToTable("StandUpStreaks"); + b.ToTable("StandUpStats"); }); modelBuilder.Entity("HonzaBotner.Database.Verification", b => From 7cc1931298df1f2c459edc3922cbae6438eaee4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucie=20Proch=C3=A1zkov=C3=A1?= Date: Sun, 8 May 2022 01:09:37 +0200 Subject: [PATCH 05/18] Updated logic & debugged after testing --- .../Jobs/StandUpJobProvider.cs | 7 +-- .../IStandUpStatsService.cs | 4 +- .../StandUpStatsService.cs | 53 +++++-------------- 3 files changed, 16 insertions(+), 48 deletions(-) diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index abdcb74f..26da7d85 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -107,12 +107,9 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) } } - await _statsService.UpdateStats(msg.Author.Id, completed, total); + await _statsService.UpdateStats(msg.Author.Id, completed, total, streakMaintained); + - if (streakMaintained) - { - await _statsService.UpdateStreak(msg.Author.Id); - } } await channel.SendMessageAsync($@" diff --git a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs index 4f5de63d..ed07aab4 100644 --- a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs +++ b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs @@ -6,9 +6,7 @@ public interface IStandUpStatsService { Task GetStreak(ulong userId); - Task UpdateStreak(ulong userId); - Task IsValidStreak(ulong userId); - Task UpdateStats(ulong userId, int completed, int total); + Task UpdateStats(ulong userId, int completed, int total, bool streakMaintained); } diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs index 8d625d6f..b39a67e1 100644 --- a/src/HonzaBotner.Services/StandUpStatsService.cs +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -28,41 +28,8 @@ public StandUpStatsService(HonzaBotnerDbContext dbContext, ILogger streak.UserId == userId); - - // Create new streak for a new user - if (streak is null) - { - Database.StandUpStat newStat = new() - { - UserId = userId, - Freezes = 0, - LastDayOfStreak = DateTime.Today.AddDays(-1), - Streak = 1, - LongestStreak = 1, - LastDayCompleted = 0, - LastDayTasks = 0, - TotalCompleted = 0, - TotalTasks = 0 - }; - - await _dbContext.StandUpStats.AddAsync(newStat); - - try - { - await _dbContext.SaveChangesAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Couldn't add streak {@Streak}", streak); - } - - return; - } - int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; if (days == -1) //Streak was already restored today @@ -73,14 +40,14 @@ public async Task UpdateStreak(ulong userId) if (days > streak.Freezes) //Streak broken { streak.Freezes = 0; - streak.LastDayOfStreak = DateTime.Today.AddDays(-1); + streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); streak.Streak = 1; } else //streak restored { streak.Freezes -= days; streak.Streak++; - streak.LastDayOfStreak = DateTime.Today.AddDays(-1); + streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime() ; if (streak.Streak > streak.LongestStreak) { @@ -115,7 +82,7 @@ public async Task IsValidStreak(ulong userId) return days <= streak.Freezes; } - public async Task UpdateStats(ulong userId, int completed, int total) { + public async Task UpdateStats(ulong userId, int completed, int total, bool streakMaintained) { Database.StandUpStat? stat = await _dbContext.StandUpStats .FirstOrDefaultAsync(streak => streak.UserId == userId); @@ -125,15 +92,16 @@ public async Task UpdateStats(ulong userId, int completed, int total) { { UserId = userId, Freezes = 0, - LastDayOfStreak = DateTime.Today.AddDays(-1), - Streak = 1, - LongestStreak = 1, + LastDayOfStreak = streakMaintained ? DateTime.Today.AddDays(-1).ToUniversalTime() : DateTime.Today.AddDays(-100).ToUniversalTime(), + Streak = streakMaintained? 1 : 0, + LongestStreak = streakMaintained? 1 : 0, LastDayCompleted = completed, LastDayTasks = total, TotalCompleted = completed, TotalTasks = total }; + await _dbContext.StandUpStats.AddAsync(newStat); try @@ -148,6 +116,11 @@ public async Task UpdateStats(ulong userId, int completed, int total) { return; } + if (streakMaintained) + { + await UpdateStreak(userId, stat); + } + stat.LastDayCompleted = completed; stat.LastDayTasks = total; stat.TotalCompleted += completed; From 483da867fce01362d8aa6fd52e81c3e4b12a4a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucie=20Proch=C3=A1zkov=C3=A1?= <58247294+LucyAnne98@users.noreply.github.com> Date: Sun, 8 May 2022 23:01:48 +0200 Subject: [PATCH 06/18] Update src/HonzaBotner.Database/HonzaBotnerDbContext.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Štorc --- src/HonzaBotner.Database/HonzaBotnerDbContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HonzaBotner.Database/HonzaBotnerDbContext.cs b/src/HonzaBotner.Database/HonzaBotnerDbContext.cs index 081ca254..7618f703 100644 --- a/src/HonzaBotner.Database/HonzaBotnerDbContext.cs +++ b/src/HonzaBotner.Database/HonzaBotnerDbContext.cs @@ -28,5 +28,4 @@ protected override void OnModelCreating(ModelBuilder builder) public DbSet Warnings { get; set; } public DbSet Reminders { get; set; } public DbSet StandUpStats { get; set; } - } From b45252fb20793fdd41b8e69ea7b5f45fa488b10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucie=20Proch=C3=A1zkov=C3=A1?= <58247294+LucyAnne98@users.noreply.github.com> Date: Sun, 8 May 2022 23:01:54 +0200 Subject: [PATCH 07/18] Update src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Štorc --- src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs index 0c2cf629..f6d9bcfc 100644 --- a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs +++ b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs @@ -2,8 +2,8 @@ namespace HonzaBotner.Services.Contract.Dto; -public class StandUpStat{ - +public class StandUpStat +{ public int Id { get; set; } public ulong UserId { get; set; } public int Streak { get; set; } From 1b51f90eef2b03184b1186e1af7b4b47a25915b4 Mon Sep 17 00:00:00 2001 From: Honza Bittner Date: Mon, 9 May 2022 13:04:24 +0200 Subject: [PATCH 08/18] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Enhance=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/HonzaBotner.Database/StandUpStat.cs | 2 - .../Jobs/StandUpJobProvider.cs | 58 ++- .../Dto/StandUpStat.cs | 15 +- .../IStandUpStatsService.cs | 5 +- .../StandUpStatsService.cs | 95 +++-- ...> 20220509110354_StandUpStats.Designer.cs} | 348 +++++++++--------- ...tats.cs => 20220509110354_StandUpStats.cs} | 80 ++-- .../ApplicationDbContextModelSnapshot.cs | 6 - 8 files changed, 326 insertions(+), 283 deletions(-) rename src/HonzaBotner/Migrations/{20220507222749_StandUpStats.Designer.cs => 20220509110354_StandUpStats.Designer.cs} (95%) rename src/HonzaBotner/Migrations/{20220507222749_StandUpStats.cs => 20220509110354_StandUpStats.cs} (90%) diff --git a/src/HonzaBotner.Database/StandUpStat.cs b/src/HonzaBotner.Database/StandUpStat.cs index 1f1a2f08..b5d172c9 100644 --- a/src/HonzaBotner.Database/StandUpStat.cs +++ b/src/HonzaBotner.Database/StandUpStat.cs @@ -10,8 +10,6 @@ public class StandUpStat public int LongestStreak { get; set; } public int Freezes { get; set; } public DateTime LastDayOfStreak { get; set; } - public int LastDayCompleted { get; set; } - public int LastDayTasks { get; set; } public int TotalCompleted { get; set; } public int TotalTasks { get; set; } } diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 26da7d85..262d62a1 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -9,6 +9,7 @@ using HonzaBotner.Discord.Services.Options; using HonzaBotner.Scheduler.Contract; using HonzaBotner.Services.Contract; +using HonzaBotner.Services.Contract.Dto; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -25,17 +26,21 @@ public class StandUpJobProvider : IJob private readonly IStandUpStatsService _statsService; + private IGuildProvider _guildProvider; + public StandUpJobProvider( ILogger logger, DiscordWrapper discord, IOptions commonOptions, - IStandUpStatsService statsService + IStandUpStatsService statsService, + IGuildProvider guildProvider ) { _logger = logger; _discord = discord; _commonOptions = commonOptions.Value; _statsService = statsService; + _guildProvider = guildProvider; } /// @@ -49,7 +54,8 @@ IStandUpStatsService statsService /// [] - normal /// []! - critical /// - private static readonly Regex Regex = new(@"^ *\[ *(?\S*) *\] *(?[!])?", RegexOptions.Multiline); + private static readonly Regex TaskRegex = new(@"^ *\[ *(?\S*) *\] *(?[!])?", + RegexOptions.Multiline); private static readonly List OkList = new() { "check", "done", "ok", "✅" }; @@ -84,22 +90,23 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) } } + HashSet membersToDm = new(); + foreach (DiscordMessage msg in messageList.Where(msg => msg.Timestamp.Date == yesterday)) { - bool streakMaintained = false; int total = 0; int completed = 0; - foreach (Match match in Regex.Matches(msg.Content)) + + foreach (Match match in TaskRegex.Matches(msg.Content)) { + total++; string state = match.Groups["State"].ToString(); string priority = match.Groups["Priority"].ToString(); - total++; if (OkList.Any(s => state.Contains(s))) { - ok.Increment(priority); - streakMaintained = true; completed++; + ok.Increment(priority); } else { @@ -107,11 +114,46 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) } } - await _statsService.UpdateStats(msg.Author.Id, completed, total, streakMaintained); + // Update DB. + await _statsService.UpdateStats(msg.Author.Id, completed, total); + StandUpStat? stats = await _statsService.GetStats(msg.Author.Id); + + if (stats is null) + { + _logger.LogWarning("No stats presented for member {Member}", msg.Author.Mention); + continue; + } + + // Send DM to the current member (only once). + if (membersToDm.Contains(msg.Author.Id)) + { + continue; + } + + membersToDm.Add(msg.Author.Id); + + try + { + DiscordGuild guild = await _guildProvider.GetCurrentGuildAsync(); + DiscordMember member = await guild.GetMemberAsync(msg.Author.Id); + // Send DM to the member. + string heading = await _statsService.IsValidStreak(msg.Author.Id) + ? "Skvělá práce!" + : "Nějak ti to nevyšlo..."; + await member.SendMessageAsync($@" +{heading} Včera jsi splnil {completed} z {total} tasků a jsi momentálně na streaku {stats.Streak} s {stats.Freezes} možnými freezes. +Celkově jsi splnil {stats.TotalCompleted} z {stats.TotalTasks} tasků a nejdelší streak jsi měl {stats.LongestStreak} dní. +"); + } + catch (Exception e) + { + _logger.LogInformation(e, "Could not send a message to {Member}", msg.Author.Mention); + } } + // Send stats message to channel. await channel.SendMessageAsync($@" Stand-up time, <@&{_commonOptions.StandUpRoleId}>! diff --git a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs index f6d9bcfc..efbf2aad 100644 --- a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs +++ b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs @@ -2,12 +2,9 @@ namespace HonzaBotner.Services.Contract.Dto; -public class StandUpStat -{ - public int Id { get; set; } - public ulong UserId { get; set; } - public int Streak { get; set; } - public int LongestStreak { get; set; } - public int Freezes { get; set; } - public DateTime LastDayOfStreak { get; set; } -}; +public record StandUpStat( + int Id, ulong UserId, + int Streak, int LongestStreak, + int Freezes, DateTime LastDayOfStreak, + int TotalCompleted, int TotalTasks +); diff --git a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs index ed07aab4..86f27294 100644 --- a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs +++ b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs @@ -1,12 +1,13 @@ using System.Threading.Tasks; using HonzaBotner.Services.Contract.Dto; + namespace HonzaBotner.Services.Contract; public interface IStandUpStatsService { - Task GetStreak(ulong userId); + Task GetStats(ulong userId); Task IsValidStreak(ulong userId); - Task UpdateStats(ulong userId, int completed, int total, bool streakMaintained); + Task UpdateStats(ulong userId, int completed, int total); } diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs index b39a67e1..bb7bd187 100644 --- a/src/HonzaBotner.Services/StandUpStatsService.cs +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -13,6 +13,9 @@ public class StandUpStatsService : IStandUpStatsService private readonly HonzaBotnerDbContext _dbContext; private readonly ILogger _logger; private const int DaysToAcquireFreeze = 6; + private const int TasksCompletedThreshold = 1; + + private const int ComparedDay = -1; public StandUpStatsService(HonzaBotnerDbContext dbContext, ILogger logger) { @@ -20,7 +23,7 @@ public StandUpStatsService(HonzaBotnerDbContext dbContext, ILogger GetStreak(ulong userId) + public async Task GetStats(ulong userId) { Database.StandUpStat? standUpStat = await _dbContext.StandUpStats .FirstOrDefaultAsync(streak => streak.UserId == userId); @@ -28,37 +31,41 @@ public StandUpStatsService(HonzaBotnerDbContext dbContext, ILogger streak.Freezes) //Streak broken - { - streak.Freezes = 0; - streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); - streak.Streak = 1; - } - else //streak restored + // Streak restored using freezes. + if (await IsValidStreak(userId, GetDto(streak))) { streak.Freezes -= days; streak.Streak++; - streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime() ; + streak.LastDayOfStreak = DateTime.Today.AddDays(ComparedDay).ToUniversalTime(); if (streak.Streak > streak.LongestStreak) { streak.LongestStreak = streak.Streak; } - if (streak.Streak % DaysToAcquireFreeze == 0) // freeze acquired + // Freeze acquired. + if (streak.Streak % DaysToAcquireFreeze == 0) { streak.Freezes++; } } + // Streak was broken and there are not enough freezes available. + else + { + streak.Freezes = 0; + streak.LastDayOfStreak = DateTime.Today.AddDays(ComparedDay).ToUniversalTime(); + streak.Streak = 1; + } try { @@ -71,18 +78,31 @@ public async Task UpdateStreak(ulong userId, Database.StandUpStat streak) } } + public async Task IsValidStreak(ulong userId) { - StandUpStat? streak = await GetStreak(userId); + StandUpStat? stats = await GetStats(userId); + return await IsValidStreak(userId, stats); + } + + private async Task IsValidStreak(ulong userId, StandUpStat? streak = null) + { + streak ??= await GetStats(userId); if (streak is null) + { return false; - int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; + } + + int days = (DateTime.Today.AddDays(ComparedDay - 1) - streak.LastDayOfStreak).Days; return days <= streak.Freezes; } - public async Task UpdateStats(ulong userId, int completed, int total, bool streakMaintained) { + public async Task UpdateStats(ulong userId, int tasksCompleted, int tasksTotal) + { + bool streakMaintained = tasksCompleted >= TasksCompletedThreshold; + Database.StandUpStat? stat = await _dbContext.StandUpStats .FirstOrDefaultAsync(streak => streak.UserId == userId); @@ -92,16 +112,16 @@ public async Task UpdateStats(ulong userId, int completed, int total, bool strea { UserId = userId, Freezes = 0, - LastDayOfStreak = streakMaintained ? DateTime.Today.AddDays(-1).ToUniversalTime() : DateTime.Today.AddDays(-100).ToUniversalTime(), - Streak = streakMaintained? 1 : 0, - LongestStreak = streakMaintained? 1 : 0, - LastDayCompleted = completed, - LastDayTasks = total, - TotalCompleted = completed, - TotalTasks = total + LastDayOfStreak = + streakMaintained + ? DateTime.Today.AddDays(ComparedDay).ToUniversalTime() + : DateTime.UnixEpoch, + Streak = streakMaintained ? 1 : 0, + LongestStreak = streakMaintained ? 1 : 0, + TotalCompleted = tasksCompleted, + TotalTasks = tasksTotal }; - await _dbContext.StandUpStats.AddAsync(newStat); try @@ -121,10 +141,8 @@ public async Task UpdateStats(ulong userId, int completed, int total, bool strea await UpdateStreak(userId, stat); } - stat.LastDayCompleted = completed; - stat.LastDayTasks = total; - stat.TotalCompleted += completed; - stat.TotalTasks += total; + stat.TotalCompleted += tasksCompleted; + stat.TotalTasks += tasksTotal; try { @@ -137,14 +155,15 @@ public async Task UpdateStats(ulong userId, int completed, int total, bool strea } } -private static StandUpStat GetDto(Database.StandUpStat standUpStat) => - new StandUpStat() - { - Id = standUpStat.Id, - UserId = standUpStat.UserId, - Streak = standUpStat.Streak, - LongestStreak = standUpStat.LongestStreak, - Freezes = standUpStat.Freezes, - LastDayOfStreak = standUpStat.LastDayOfStreak - }; + private static StandUpStat GetDto(Database.StandUpStat standUpStat) => + new( + standUpStat.Id, + standUpStat.UserId, + standUpStat.Streak, + standUpStat.LongestStreak, + standUpStat.Freezes, + standUpStat.LastDayOfStreak, + standUpStat.TotalCompleted, + standUpStat.TotalTasks + ); } diff --git a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs b/src/HonzaBotner/Migrations/20220509110354_StandUpStats.Designer.cs similarity index 95% rename from src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs rename to src/HonzaBotner/Migrations/20220509110354_StandUpStats.Designer.cs index 8822cace..b4136fbc 100644 --- a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs +++ b/src/HonzaBotner/Migrations/20220509110354_StandUpStats.Designer.cs @@ -1,177 +1,171 @@ -// -using System; -using HonzaBotner.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace HonzaBotner.Migrations -{ - [DbContext(typeof(HonzaBotnerDbContext))] - [Migration("20220507222749_StandUpStats")] - partial class StandUpStats - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.4") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("HonzaBotner.Database.CountedEmoji", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("numeric(20,0)"); - - b.Property("FirstUsedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Times") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("CountedEmojis"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Reminder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ChannelId") - .HasColumnType("numeric(20,0)"); - - b.Property("Content") - .HasColumnType("text"); - - b.Property("DateTime") - .HasColumnType("timestamp with time zone"); - - b.Property("MessageId") - .HasColumnType("numeric(20,0)"); - - b.Property("OwnerId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.HasIndex("DateTime"); - - b.ToTable("Reminders"); - }); - - modelBuilder.Entity("HonzaBotner.Database.RoleBinding", b => - { - b.Property("Emoji") - .HasColumnType("text"); - - b.Property("ChannelId") - .HasColumnType("numeric(20,0)"); - - b.Property("MessageId") - .HasColumnType("numeric(20,0)"); - - b.Property("RoleId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Emoji", "ChannelId", "MessageId", "RoleId"); - - b.ToTable("RoleBindings"); - }); - - modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Freezes") - .HasColumnType("integer"); - - b.Property("LastDayCompleted") - .HasColumnType("integer"); - - b.Property("LastDayOfStreak") - .HasColumnType("timestamp with time zone"); - - b.Property("LastDayTasks") - .HasColumnType("integer"); - - b.Property("LongestStreak") - .HasColumnType("integer"); - - b.Property("Streak") - .HasColumnType("integer"); - - b.Property("TotalCompleted") - .HasColumnType("integer"); - - b.Property("TotalTasks") - .HasColumnType("integer"); - - b.Property("UserId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("StandUpStats"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Verification", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("numeric(20,0)"); - - b.Property("AuthId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.HasIndex("AuthId") - .IsUnique(); - - b.ToTable("Verifications"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Warning", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("IssuerId") - .HasColumnType("numeric(20,0)"); - - b.Property("Reason") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("Warnings"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using HonzaBotner.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + [DbContext(typeof(HonzaBotnerDbContext))] + [Migration("20220509110354_StandUpStats")] + partial class StandUpStats + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HonzaBotner.Database.CountedEmoji", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("FirstUsedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Times") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("CountedEmojis"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("OwnerId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("HonzaBotner.Database.RoleBinding", b => + { + b.Property("Emoji") + .HasColumnType("text"); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Emoji", "ChannelId", "MessageId", "RoleId"); + + b.ToTable("RoleBindings"); + }); + + modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Freezes") + .HasColumnType("integer"); + + b.Property("LastDayOfStreak") + .HasColumnType("timestamp with time zone"); + + b.Property("LongestStreak") + .HasColumnType("integer"); + + b.Property("Streak") + .HasColumnType("integer"); + + b.Property("TotalCompleted") + .HasColumnType("integer"); + + b.Property("TotalTasks") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("StandUpStats"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Verification", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("AuthId") + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.HasIndex("AuthId") + .IsUnique(); + + b.ToTable("Verifications"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IssuerId") + .HasColumnType("numeric(20,0)"); + + b.Property("Reason") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs b/src/HonzaBotner/Migrations/20220509110354_StandUpStats.cs similarity index 90% rename from src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs rename to src/HonzaBotner/Migrations/20220509110354_StandUpStats.cs index 299347d6..4a1d1290 100644 --- a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs +++ b/src/HonzaBotner/Migrations/20220509110354_StandUpStats.cs @@ -1,41 +1,39 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace HonzaBotner.Migrations -{ - public partial class StandUpStats : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "StandUpStats", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserId = table.Column(type: "numeric(20,0)", nullable: false), - Streak = table.Column(type: "integer", nullable: false), - LongestStreak = table.Column(type: "integer", nullable: false), - Freezes = table.Column(type: "integer", nullable: false), - LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false), - LastDayCompleted = table.Column(type: "integer", nullable: false), - LastDayTasks = table.Column(type: "integer", nullable: false), - TotalCompleted = table.Column(type: "integer", nullable: false), - TotalTasks = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_StandUpStats", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "StandUpStats"); - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + public partial class StandUpStats : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "StandUpStats", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "numeric(20,0)", nullable: false), + Streak = table.Column(type: "integer", nullable: false), + LongestStreak = table.Column(type: "integer", nullable: false), + Freezes = table.Column(type: "integer", nullable: false), + LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false), + TotalCompleted = table.Column(type: "integer", nullable: false), + TotalTasks = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StandUpStats", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StandUpStats"); + } + } +} diff --git a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs index 718d8e23..4e9e65a7 100644 --- a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs @@ -99,15 +99,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Freezes") .HasColumnType("integer"); - b.Property("LastDayCompleted") - .HasColumnType("integer"); - b.Property("LastDayOfStreak") .HasColumnType("timestamp with time zone"); - b.Property("LastDayTasks") - .HasColumnType("integer"); - b.Property("LongestStreak") .HasColumnType("integer"); From be8e77114b27e73c46cf67ca006a59392f2e3b36 Mon Sep 17 00:00:00 2001 From: Honza Bittner Date: Mon, 9 May 2022 13:15:20 +0200 Subject: [PATCH 09/18] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20stand-up?= =?UTF-8?q?=20config=20to=20separate=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Jobs/StandUpJobProvider.cs | 10 +++++----- .../Options/CommonCommandOptions.cs | 3 --- .../Options/StandUpOptions.cs | 12 ++++++++++++ .../ServiceCollectionExtensions.cs | 1 + .../HonzaBotner.Services.csproj | 1 + src/HonzaBotner.Services/StandUpStatsService.cs | 16 +++++++++++----- src/HonzaBotner/appsettings.BotDev.json | 8 ++++++-- src/HonzaBotner/appsettings.CvutFit.json | 8 ++++++-- src/HonzaBotner/appsettings.Development.json | 8 ++++++-- 9 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 src/HonzaBotner.Discord.Services/Options/StandUpOptions.cs diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 262d62a1..e17a7ee1 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -22,7 +22,7 @@ public class StandUpJobProvider : IJob private readonly DiscordWrapper _discord; - private readonly CommonCommandOptions _commonOptions; + private readonly StandUpOptions _standUpOptions; private readonly IStandUpStatsService _statsService; @@ -31,14 +31,14 @@ public class StandUpJobProvider : IJob public StandUpJobProvider( ILogger logger, DiscordWrapper discord, - IOptions commonOptions, + IOptions standUpOptions, IStandUpStatsService statsService, IGuildProvider guildProvider ) { _logger = logger; _discord = discord; - _commonOptions = commonOptions.Value; + _standUpOptions = standUpOptions.Value; _statsService = statsService; _guildProvider = guildProvider; } @@ -68,7 +68,7 @@ public async Task ExecuteAsync(CancellationToken cancellationToken) try { - DiscordChannel channel = await _discord.Client.GetChannelAsync(_commonOptions.StandUpChannelId); + DiscordChannel channel = await _discord.Client.GetChannelAsync(_standUpOptions.StandUpChannelId); var ok = new StandUpStats(); var fail = new StandUpStats(); @@ -155,7 +155,7 @@ Celkově jsi splnil {stats.TotalCompleted} z {stats.TotalTasks} tasků a nejdel // Send stats message to channel. await channel.SendMessageAsync($@" -Stand-up time, <@&{_commonOptions.StandUpRoleId}>! +Stand-up time, <@&{_standUpOptions.StandUpRoleId}>! Results from : ``` diff --git a/src/HonzaBotner.Discord.Services/Options/CommonCommandOptions.cs b/src/HonzaBotner.Discord.Services/Options/CommonCommandOptions.cs index 6642e8c7..e6bd1bb7 100644 --- a/src/HonzaBotner.Discord.Services/Options/CommonCommandOptions.cs +++ b/src/HonzaBotner.Discord.Services/Options/CommonCommandOptions.cs @@ -21,8 +21,5 @@ public class CommonCommandOptions public ulong[]? ReactionIgnoreChannels { get; set; } - public ulong StandUpRoleId { get; set; } - public ulong StandUpChannelId { get; set; } - public ulong[] MemberCountAllowlistIds { get; set; } = System.Array.Empty(); } diff --git a/src/HonzaBotner.Discord.Services/Options/StandUpOptions.cs b/src/HonzaBotner.Discord.Services/Options/StandUpOptions.cs new file mode 100644 index 00000000..4d7bbb53 --- /dev/null +++ b/src/HonzaBotner.Discord.Services/Options/StandUpOptions.cs @@ -0,0 +1,12 @@ +namespace HonzaBotner.Discord.Services.Options; + +public class StandUpOptions +{ + public static string ConfigName => "StandUpOptions"; + + public ulong StandUpRoleId { get; set; } + public ulong StandUpChannelId { get; set; } + + public int DaysToAcquireFreeze { get; set; } + public int TasksCompletedThreshold { get; set; } +} diff --git a/src/HonzaBotner.Discord.Services/ServiceCollectionExtensions.cs b/src/HonzaBotner.Discord.Services/ServiceCollectionExtensions.cs index 2ebd1992..01cc675d 100644 --- a/src/HonzaBotner.Discord.Services/ServiceCollectionExtensions.cs +++ b/src/HonzaBotner.Discord.Services/ServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ public static IServiceCollection AddCommandOptions(this IServiceCollection servi services.Configure(configuration.GetSection(PinOptions.ConfigName)); services.Configure(configuration.GetSection(InfoOptions.ConfigName)); services.Configure(configuration.GetSection(ReminderOptions.ConfigName)); + services.Configure(configuration.GetSection(StandUpOptions.ConfigName)); services.Configure(configuration.GetSection(ButtonOptions.ConfigName)); services.Configure(configuration.GetSection(BadgeRoleOptions.ConfigName)); diff --git a/src/HonzaBotner.Services/HonzaBotner.Services.csproj b/src/HonzaBotner.Services/HonzaBotner.Services.csproj index 9429c890..108134e7 100644 --- a/src/HonzaBotner.Services/HonzaBotner.Services.csproj +++ b/src/HonzaBotner.Services/HonzaBotner.Services.csproj @@ -8,6 +8,7 @@ + diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs index bb7bd187..aa032a40 100644 --- a/src/HonzaBotner.Services/StandUpStatsService.cs +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -1,9 +1,11 @@ using System; using System.Threading.Tasks; using HonzaBotner.Database; +using HonzaBotner.Discord.Services.Options; using HonzaBotner.Services.Contract; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using StandUpStat = HonzaBotner.Services.Contract.Dto.StandUpStat; namespace HonzaBotner.Services; @@ -12,15 +14,19 @@ public class StandUpStatsService : IStandUpStatsService { private readonly HonzaBotnerDbContext _dbContext; private readonly ILogger _logger; - private const int DaysToAcquireFreeze = 6; - private const int TasksCompletedThreshold = 1; + private readonly StandUpOptions _standUpOptions; private const int ComparedDay = -1; - public StandUpStatsService(HonzaBotnerDbContext dbContext, ILogger logger) + public StandUpStatsService( + HonzaBotnerDbContext dbContext, + ILogger logger, + IOptions standUpOptions + ) { _dbContext = dbContext; _logger = logger; + _standUpOptions = standUpOptions.Value; } public async Task GetStats(ulong userId) @@ -54,7 +60,7 @@ private async Task UpdateStreak(ulong userId, Database.StandUpStat streak) } // Freeze acquired. - if (streak.Streak % DaysToAcquireFreeze == 0) + if (streak.Streak % _standUpOptions.DaysToAcquireFreeze == 0) { streak.Freezes++; } @@ -101,7 +107,7 @@ private async Task IsValidStreak(ulong userId, StandUpStat? streak = null) public async Task UpdateStats(ulong userId, int tasksCompleted, int tasksTotal) { - bool streakMaintained = tasksCompleted >= TasksCompletedThreshold; + bool streakMaintained = tasksCompleted >= _standUpOptions.TasksCompletedThreshold; Database.StandUpStat? stat = await _dbContext.StandUpStats .FirstOrDefaultAsync(streak => streak.UserId == userId); diff --git a/src/HonzaBotner/appsettings.BotDev.json b/src/HonzaBotner/appsettings.BotDev.json index 37c5a305..ea0fc89d 100644 --- a/src/HonzaBotner/appsettings.BotDev.json +++ b/src/HonzaBotner/appsettings.BotDev.json @@ -26,9 +26,13 @@ "GentlemenFilePath": "Static/gentleman.gif", "HornyJailRoleId": 0, "HornyJailChannelId": 0, - "HornyJailFilePath": "Static/hornyjail.mp4", + "HornyJailFilePath": "Static/hornyjail.mp4" + }, + "StandUpOptions": { "StandUpRoleId": 0, - "StandUpChannelId": 0 + "StandUpChannelId": 0, + "TasksCompletedThreshold": 6, + "DaysToAcquireFreeze": 1 }, "CommandAllowlistsOptions": { "MemberCount": [ diff --git a/src/HonzaBotner/appsettings.CvutFit.json b/src/HonzaBotner/appsettings.CvutFit.json index 3db7a58b..cf9d37c7 100644 --- a/src/HonzaBotner/appsettings.CvutFit.json +++ b/src/HonzaBotner/appsettings.CvutFit.json @@ -33,9 +33,13 @@ "ReactionIgnoreChannels": [ 691998530151120936, 774247506669600798 - ], + ] + }, + "StandUpOptions": { "StandUpRoleId": 972019490818768927, - "StandUpChannelId": 972019138446897162 + "StandUpChannelId": 972019138446897162, + "TasksCompletedThreshold": 6, + "DaysToAcquireFreeze": 1 }, "CommandAllowlistsOptions": { "MemberCount": [ diff --git a/src/HonzaBotner/appsettings.Development.json b/src/HonzaBotner/appsettings.Development.json index da4a2734..2617227e 100644 --- a/src/HonzaBotner/appsettings.Development.json +++ b/src/HonzaBotner/appsettings.Development.json @@ -29,9 +29,13 @@ "GentlemenFilePath": "Static/gentleman.gif", "HornyJailRoleId": 760050336366329856, "HornyJailChannelId": 819986928928686080, - "HornyJailFilePath": "Static/hornyjail.mp4", + "HornyJailFilePath": "Static/hornyjail.mp4" + }, + "StandUpOptions": { "StandUpRoleId": 972123386094440498, - "StandUpChannelId": 750108543125946448 + "StandUpChannelId": 750108543125946448, + "TasksCompletedThreshold": 6, + "DaysToAcquireFreeze": 1 }, "CommandAllowlistsOptions": { "MemberCount": [ From c0345c97e5031c0c12e00b519e2c96e6fdecbd4a Mon Sep 17 00:00:00 2001 From: Honza Bittner Date: Mon, 9 May 2022 13:27:33 +0200 Subject: [PATCH 10/18] =?UTF-8?q?=F0=9F=94=A5=20Remove=20sending=20DM,=20w?= =?UTF-8?q?ill=20be=20implemented=20using=20SC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Jobs/StandUpJobProvider.cs | 43 +------------------ src/HonzaBotner/appsettings.BotDev.json | 4 +- src/HonzaBotner/appsettings.CvutFit.json | 4 +- src/HonzaBotner/appsettings.Development.json | 4 +- 4 files changed, 7 insertions(+), 48 deletions(-) diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index e17a7ee1..41d9dcf6 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -26,21 +26,17 @@ public class StandUpJobProvider : IJob private readonly IStandUpStatsService _statsService; - private IGuildProvider _guildProvider; - public StandUpJobProvider( ILogger logger, DiscordWrapper discord, IOptions standUpOptions, - IStandUpStatsService statsService, - IGuildProvider guildProvider + IStandUpStatsService statsService ) { _logger = logger; _discord = discord; _standUpOptions = standUpOptions.Value; _statsService = statsService; - _guildProvider = guildProvider; } /// @@ -90,8 +86,6 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) } } - HashSet membersToDm = new(); - foreach (DiscordMessage msg in messageList.Where(msg => msg.Timestamp.Date == yesterday)) { int total = 0; @@ -116,41 +110,6 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) // Update DB. await _statsService.UpdateStats(msg.Author.Id, completed, total); - StandUpStat? stats = await _statsService.GetStats(msg.Author.Id); - - if (stats is null) - { - _logger.LogWarning("No stats presented for member {Member}", msg.Author.Mention); - continue; - } - - // Send DM to the current member (only once). - if (membersToDm.Contains(msg.Author.Id)) - { - continue; - } - - membersToDm.Add(msg.Author.Id); - - try - { - DiscordGuild guild = await _guildProvider.GetCurrentGuildAsync(); - DiscordMember member = await guild.GetMemberAsync(msg.Author.Id); - - // Send DM to the member. - string heading = await _statsService.IsValidStreak(msg.Author.Id) - ? "Skvělá práce!" - : "Nějak ti to nevyšlo..."; - await member.SendMessageAsync($@" -{heading} Včera jsi splnil {completed} z {total} tasků a jsi momentálně na streaku {stats.Streak} s {stats.Freezes} možnými freezes. - -Celkově jsi splnil {stats.TotalCompleted} z {stats.TotalTasks} tasků a nejdelší streak jsi měl {stats.LongestStreak} dní. -"); - } - catch (Exception e) - { - _logger.LogInformation(e, "Could not send a message to {Member}", msg.Author.Mention); - } } // Send stats message to channel. diff --git a/src/HonzaBotner/appsettings.BotDev.json b/src/HonzaBotner/appsettings.BotDev.json index ea0fc89d..dd8ba99d 100644 --- a/src/HonzaBotner/appsettings.BotDev.json +++ b/src/HonzaBotner/appsettings.BotDev.json @@ -31,8 +31,8 @@ "StandUpOptions": { "StandUpRoleId": 0, "StandUpChannelId": 0, - "TasksCompletedThreshold": 6, - "DaysToAcquireFreeze": 1 + "TasksCompletedThreshold": 1, + "DaysToAcquireFreeze": 6 }, "CommandAllowlistsOptions": { "MemberCount": [ diff --git a/src/HonzaBotner/appsettings.CvutFit.json b/src/HonzaBotner/appsettings.CvutFit.json index cf9d37c7..65b27c78 100644 --- a/src/HonzaBotner/appsettings.CvutFit.json +++ b/src/HonzaBotner/appsettings.CvutFit.json @@ -38,8 +38,8 @@ "StandUpOptions": { "StandUpRoleId": 972019490818768927, "StandUpChannelId": 972019138446897162, - "TasksCompletedThreshold": 6, - "DaysToAcquireFreeze": 1 + "TasksCompletedThreshold": 1, + "DaysToAcquireFreeze": 6 }, "CommandAllowlistsOptions": { "MemberCount": [ diff --git a/src/HonzaBotner/appsettings.Development.json b/src/HonzaBotner/appsettings.Development.json index 2617227e..f0c26140 100644 --- a/src/HonzaBotner/appsettings.Development.json +++ b/src/HonzaBotner/appsettings.Development.json @@ -34,8 +34,8 @@ "StandUpOptions": { "StandUpRoleId": 972123386094440498, "StandUpChannelId": 750108543125946448, - "TasksCompletedThreshold": 6, - "DaysToAcquireFreeze": 1 + "TasksCompletedThreshold": 1, + "DaysToAcquireFreeze": 6 }, "CommandAllowlistsOptions": { "MemberCount": [ From 3aa5a8d3c125e32fe63e2c5a2d3ce12336746bc1 Mon Sep 17 00:00:00 2001 From: Honza Bittner Date: Mon, 9 May 2022 13:35:09 +0200 Subject: [PATCH 11/18] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Fix=20lints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj | 1 - src/HonzaBotner.Services/StandUpStatsService.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj b/src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj index da3b6f92..82ffde28 100644 --- a/src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj +++ b/src/HonzaBotner.Services.Test/HonzaBotner.Services.Test.csproj @@ -18,7 +18,6 @@ - diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs index aa032a40..e730549f 100644 --- a/src/HonzaBotner.Services/StandUpStatsService.cs +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -91,7 +91,7 @@ public async Task IsValidStreak(ulong userId) return await IsValidStreak(userId, stats); } - private async Task IsValidStreak(ulong userId, StandUpStat? streak = null) + private async Task IsValidStreak(ulong userId, StandUpStat? streak) { streak ??= await GetStats(userId); From 8ca03cf7a91cdc632b251cfcea5ab505bc89a2c9 Mon Sep 17 00:00:00 2001 From: stepech <29132060+stepech@users.noreply.github.com> Date: Sat, 21 May 2022 13:57:06 +0200 Subject: [PATCH 12/18] Standup: Improve message collection logic --- .../Jobs/StandUpJobProvider.cs | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 76b62bca..21d73e3c 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -9,7 +9,6 @@ using HonzaBotner.Discord.Services.Options; using HonzaBotner.Scheduler.Contract; using HonzaBotner.Services.Contract; -using HonzaBotner.Services.Contract.Dto; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -86,43 +85,48 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) } } - foreach (DiscordMessage msg in messageList.Where(msg => msg.Timestamp.Date == yesterday)) + foreach (var authorGrouped in messageList.Where(msg => msg.Timestamp.Date == yesterday) + .GroupBy(msg => msg.Author.Id)) { int total = 0; int completed = 0; - foreach (Match match in TaskRegex.Matches(msg.Content)) + foreach (var msg in authorGrouped) { - total++; - string state = match.Groups["State"].ToString(); - string priority = match.Groups["Priority"].ToString(); - if (OkList.Any(s => state.Contains(s))) + foreach (Match match in TaskRegex.Matches(msg.Content)) { - completed++; - ok.Increment(priority); - } - else - { - fail.Increment(priority); + total++; + string state = match.Groups["State"].Value; + string priority = match.Groups["Priority"].Value; + + if (OkList.Any(s => state.Contains(s))) + { + completed++; + ok.Increment(priority); + } + else + { + fail.Increment(priority); + } } } // Update DB. - await _statsService.UpdateStats(msg.Author.Id, completed, total); + if (total != 0) {await _statsService.UpdateStats(authorGrouped.Key, completed, total);} } // Send stats message to channel. await channel.SendMessageAsync($@" -Stand-up time, <@&{_standUpOptions.StandUpRoleId}>! - -Results from : -``` -all: {ok.Add(fail)} -completed: {ok} -failed: {fail} -``` -"); + Stand-up time, <@&{_standUpOptions.StandUpRoleId}>! + + Results from : + ``` + all: {ok.Add(fail)} + completed: {ok} + failed: {fail} + ``` + "); } catch (Exception e) { From 97a8b6a53588d7c4520f89809ef4507c7fe8e79f Mon Sep 17 00:00:00 2001 From: stepech <29132060+stepech@users.noreply.github.com> Date: Sat, 21 May 2022 14:40:25 +0200 Subject: [PATCH 13/18] Revert code enhancement --- src/HonzaBotner.Database/StandUpStat.cs | 2 + .../Jobs/StandUpJobProvider.cs | 9 +- .../IStandUpStatsService.cs | 5 +- .../StandUpStatsService.cs | 79 ++-- ...> 20220507222749_StandUpStats.Designer.cs} | 348 +++++++++--------- ...tats.cs => 20220507222749_StandUpStats.cs} | 80 ++-- .../ApplicationDbContextModelSnapshot.cs | 6 + 7 files changed, 259 insertions(+), 270 deletions(-) rename src/HonzaBotner/Migrations/{20220509110354_StandUpStats.Designer.cs => 20220507222749_StandUpStats.Designer.cs} (95%) rename src/HonzaBotner/Migrations/{20220509110354_StandUpStats.cs => 20220507222749_StandUpStats.cs} (90%) diff --git a/src/HonzaBotner.Database/StandUpStat.cs b/src/HonzaBotner.Database/StandUpStat.cs index b5d172c9..1f1a2f08 100644 --- a/src/HonzaBotner.Database/StandUpStat.cs +++ b/src/HonzaBotner.Database/StandUpStat.cs @@ -10,6 +10,8 @@ public class StandUpStat public int LongestStreak { get; set; } public int Freezes { get; set; } public DateTime LastDayOfStreak { get; set; } + public int LastDayCompleted { get; set; } + public int LastDayTasks { get; set; } public int TotalCompleted { get; set; } public int TotalTasks { get; set; } } diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 21d73e3c..0d2a4e9d 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -110,10 +110,13 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) fail.Increment(priority); } } - } - // Update DB. - if (total != 0) {await _statsService.UpdateStats(authorGrouped.Key, completed, total);} + // Update DB. + if (total != 0) + { + await _statsService.UpdateStats(authorGrouped.Key, completed, total); + } + } } // Send stats message to channel. diff --git a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs index 86f27294..0ad66a4e 100644 --- a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs +++ b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs @@ -1,13 +1,10 @@ using System.Threading.Tasks; using HonzaBotner.Services.Contract.Dto; - namespace HonzaBotner.Services.Contract; public interface IStandUpStatsService { - Task GetStats(ulong userId); - - Task IsValidStreak(ulong userId); + Task GetStreak(ulong userId); Task UpdateStats(ulong userId, int completed, int total); } diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs index e730549f..56d6ed84 100644 --- a/src/HonzaBotner.Services/StandUpStatsService.cs +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -29,7 +29,7 @@ IOptions standUpOptions _standUpOptions = standUpOptions.Value; } - public async Task GetStats(ulong userId) + public async Task GetStreak(ulong userId) { Database.StandUpStat? standUpStat = await _dbContext.StandUpStats .FirstOrDefaultAsync(streak => streak.UserId == userId); @@ -37,22 +37,21 @@ IOptions standUpOptions return standUpStat == null ? null : GetDto(standUpStat); } - private async Task UpdateStreak(ulong userId, Database.StandUpStat streak) + public async Task UpdateStreak(ulong userId, Database.StandUpStat streak) { - int days = (DateTime.Today.AddDays(ComparedDay - 1) - streak.LastDayOfStreak).Days; + int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; - // Streak was already restored today. - if (days == ComparedDay) + if (days > streak.Freezes) //Streak broken { - return; + streak.Freezes = 0; + streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); + streak.Streak = 1; } - - // Streak restored using freezes. - if (await IsValidStreak(userId, GetDto(streak))) + else //streak restored { - streak.Freezes -= days; + streak.Freezes--; streak.Streak++; - streak.LastDayOfStreak = DateTime.Today.AddDays(ComparedDay).ToUniversalTime(); + streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); if (streak.Streak > streak.LongestStreak) { @@ -65,13 +64,6 @@ private async Task UpdateStreak(ulong userId, Database.StandUpStat streak) streak.Freezes++; } } - // Streak was broken and there are not enough freezes available. - else - { - streak.Freezes = 0; - streak.LastDayOfStreak = DateTime.Today.AddDays(ComparedDay).ToUniversalTime(); - streak.Streak = 1; - } try { @@ -84,31 +76,8 @@ private async Task UpdateStreak(ulong userId, Database.StandUpStat streak) } } - - public async Task IsValidStreak(ulong userId) + public async Task UpdateStats(ulong userId, int completed, int total) { - StandUpStat? stats = await GetStats(userId); - return await IsValidStreak(userId, stats); - } - - private async Task IsValidStreak(ulong userId, StandUpStat? streak) - { - streak ??= await GetStats(userId); - - if (streak is null) - { - return false; - } - - int days = (DateTime.Today.AddDays(ComparedDay - 1) - streak.LastDayOfStreak).Days; - - return days <= streak.Freezes; - } - - public async Task UpdateStats(ulong userId, int tasksCompleted, int tasksTotal) - { - bool streakMaintained = tasksCompleted >= _standUpOptions.TasksCompletedThreshold; - Database.StandUpStat? stat = await _dbContext.StandUpStats .FirstOrDefaultAsync(streak => streak.UserId == userId); @@ -119,13 +88,15 @@ public async Task UpdateStats(ulong userId, int tasksCompleted, int tasksTotal) UserId = userId, Freezes = 0, LastDayOfStreak = - streakMaintained - ? DateTime.Today.AddDays(ComparedDay).ToUniversalTime() - : DateTime.UnixEpoch, - Streak = streakMaintained ? 1 : 0, - LongestStreak = streakMaintained ? 1 : 0, - TotalCompleted = tasksCompleted, - TotalTasks = tasksTotal + completed != 0 + ? DateTime.Today.AddDays(-1).ToUniversalTime() + : DateTime.Today.AddDays(-100).ToUniversalTime(), + Streak = completed != 0 ? 1 : 0, + LongestStreak = completed != 0 ? 1 : 0, + LastDayCompleted = completed, + LastDayTasks = total, + TotalCompleted = completed, + TotalTasks = total }; await _dbContext.StandUpStats.AddAsync(newStat); @@ -142,13 +113,15 @@ public async Task UpdateStats(ulong userId, int tasksCompleted, int tasksTotal) return; } - if (streakMaintained) + if (completed != 0) { await UpdateStreak(userId, stat); } - stat.TotalCompleted += tasksCompleted; - stat.TotalTasks += tasksTotal; + stat.LastDayCompleted = completed; + stat.LastDayTasks = total; + stat.TotalCompleted += completed; + stat.TotalTasks += total; try { @@ -161,7 +134,7 @@ public async Task UpdateStats(ulong userId, int tasksCompleted, int tasksTotal) } } - private static StandUpStat GetDto(Database.StandUpStat standUpStat) => + static StandUpStat GetDto(Database.StandUpStat standUpStat) => new( standUpStat.Id, standUpStat.UserId, diff --git a/src/HonzaBotner/Migrations/20220509110354_StandUpStats.Designer.cs b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs similarity index 95% rename from src/HonzaBotner/Migrations/20220509110354_StandUpStats.Designer.cs rename to src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs index b4136fbc..8822cace 100644 --- a/src/HonzaBotner/Migrations/20220509110354_StandUpStats.Designer.cs +++ b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs @@ -1,171 +1,177 @@ -// -using System; -using HonzaBotner.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace HonzaBotner.Migrations -{ - [DbContext(typeof(HonzaBotnerDbContext))] - [Migration("20220509110354_StandUpStats")] - partial class StandUpStats - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.4") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("HonzaBotner.Database.CountedEmoji", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("numeric(20,0)"); - - b.Property("FirstUsedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Times") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("CountedEmojis"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Reminder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ChannelId") - .HasColumnType("numeric(20,0)"); - - b.Property("Content") - .HasColumnType("text"); - - b.Property("DateTime") - .HasColumnType("timestamp with time zone"); - - b.Property("MessageId") - .HasColumnType("numeric(20,0)"); - - b.Property("OwnerId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.HasIndex("DateTime"); - - b.ToTable("Reminders"); - }); - - modelBuilder.Entity("HonzaBotner.Database.RoleBinding", b => - { - b.Property("Emoji") - .HasColumnType("text"); - - b.Property("ChannelId") - .HasColumnType("numeric(20,0)"); - - b.Property("MessageId") - .HasColumnType("numeric(20,0)"); - - b.Property("RoleId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Emoji", "ChannelId", "MessageId", "RoleId"); - - b.ToTable("RoleBindings"); - }); - - modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Freezes") - .HasColumnType("integer"); - - b.Property("LastDayOfStreak") - .HasColumnType("timestamp with time zone"); - - b.Property("LongestStreak") - .HasColumnType("integer"); - - b.Property("Streak") - .HasColumnType("integer"); - - b.Property("TotalCompleted") - .HasColumnType("integer"); - - b.Property("TotalTasks") - .HasColumnType("integer"); - - b.Property("UserId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("StandUpStats"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Verification", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("numeric(20,0)"); - - b.Property("AuthId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.HasIndex("AuthId") - .IsUnique(); - - b.ToTable("Verifications"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Warning", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("IssuerId") - .HasColumnType("numeric(20,0)"); - - b.Property("Reason") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("Warnings"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using HonzaBotner.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + [DbContext(typeof(HonzaBotnerDbContext))] + [Migration("20220507222749_StandUpStats")] + partial class StandUpStats + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HonzaBotner.Database.CountedEmoji", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("FirstUsedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Times") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("CountedEmojis"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("OwnerId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("HonzaBotner.Database.RoleBinding", b => + { + b.Property("Emoji") + .HasColumnType("text"); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Emoji", "ChannelId", "MessageId", "RoleId"); + + b.ToTable("RoleBindings"); + }); + + modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Freezes") + .HasColumnType("integer"); + + b.Property("LastDayCompleted") + .HasColumnType("integer"); + + b.Property("LastDayOfStreak") + .HasColumnType("timestamp with time zone"); + + b.Property("LastDayTasks") + .HasColumnType("integer"); + + b.Property("LongestStreak") + .HasColumnType("integer"); + + b.Property("Streak") + .HasColumnType("integer"); + + b.Property("TotalCompleted") + .HasColumnType("integer"); + + b.Property("TotalTasks") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("StandUpStats"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Verification", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("AuthId") + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.HasIndex("AuthId") + .IsUnique(); + + b.ToTable("Verifications"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IssuerId") + .HasColumnType("numeric(20,0)"); + + b.Property("Reason") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HonzaBotner/Migrations/20220509110354_StandUpStats.cs b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs similarity index 90% rename from src/HonzaBotner/Migrations/20220509110354_StandUpStats.cs rename to src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs index 4a1d1290..299347d6 100644 --- a/src/HonzaBotner/Migrations/20220509110354_StandUpStats.cs +++ b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs @@ -1,39 +1,41 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace HonzaBotner.Migrations -{ - public partial class StandUpStats : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "StandUpStats", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserId = table.Column(type: "numeric(20,0)", nullable: false), - Streak = table.Column(type: "integer", nullable: false), - LongestStreak = table.Column(type: "integer", nullable: false), - Freezes = table.Column(type: "integer", nullable: false), - LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false), - TotalCompleted = table.Column(type: "integer", nullable: false), - TotalTasks = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_StandUpStats", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "StandUpStats"); - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + public partial class StandUpStats : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "StandUpStats", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "numeric(20,0)", nullable: false), + Streak = table.Column(type: "integer", nullable: false), + LongestStreak = table.Column(type: "integer", nullable: false), + Freezes = table.Column(type: "integer", nullable: false), + LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false), + LastDayCompleted = table.Column(type: "integer", nullable: false), + LastDayTasks = table.Column(type: "integer", nullable: false), + TotalCompleted = table.Column(type: "integer", nullable: false), + TotalTasks = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StandUpStats", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StandUpStats"); + } + } +} diff --git a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs index 4e9e65a7..718d8e23 100644 --- a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs @@ -99,9 +99,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Freezes") .HasColumnType("integer"); + b.Property("LastDayCompleted") + .HasColumnType("integer"); + b.Property("LastDayOfStreak") .HasColumnType("timestamp with time zone"); + b.Property("LastDayTasks") + .HasColumnType("integer"); + b.Property("LongestStreak") .HasColumnType("integer"); From 165e4c60163e196671244e045befcb8eabdd51d8 Mon Sep 17 00:00:00 2001 From: stepech <29132060+stepech@users.noreply.github.com> Date: Sat, 21 May 2022 20:59:54 +0200 Subject: [PATCH 14/18] StandUp: Final touches to service provider, prepare for button --- .../Jobs/StandUpJobProvider.cs | 15 +++++++-- .../Options/ButtonOptions.cs | 1 + .../Dto/StandUpStat.cs | 3 +- .../IStandUpStatsService.cs | 11 +++++++ .../StandUpStatsService.cs | 32 +++++++------------ src/HonzaBotner/appsettings.BotDev.json | 3 +- src/HonzaBotner/appsettings.CvutFit.json | 1 + src/HonzaBotner/appsettings.Development.json | 1 + 8 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 0d2a4e9d..89122199 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using DSharpPlus; using DSharpPlus.Entities; using HonzaBotner.Discord.Services.Helpers; using HonzaBotner.Discord.Services.Options; @@ -22,6 +23,7 @@ public class StandUpJobProvider : IJob private readonly DiscordWrapper _discord; private readonly StandUpOptions _standUpOptions; + private readonly ButtonOptions _buttonOptions; private readonly IStandUpStatsService _statsService; @@ -29,13 +31,15 @@ public StandUpJobProvider( ILogger logger, DiscordWrapper discord, IOptions standUpOptions, - IStandUpStatsService statsService + IStandUpStatsService statsService, + ButtonOptions buttonOptions ) { _logger = logger; _discord = discord; _standUpOptions = standUpOptions.Value; _statsService = statsService; + _buttonOptions = buttonOptions; } /// @@ -120,7 +124,7 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) } // Send stats message to channel. - await channel.SendMessageAsync($@" + DiscordMessageBuilder message = new DiscordMessageBuilder().WithContent($@" Stand-up time, <@&{_standUpOptions.StandUpRoleId}>! Results from : @@ -130,6 +134,13 @@ await channel.SendMessageAsync($@" failed: {fail} ``` "); + if (_buttonOptions.StandUpStatsId is not null) + { + message.AddComponents(new DiscordButtonComponent(ButtonStyle.Primary, _buttonOptions.StandUpStatsId, + "Get your stats", false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🐸")))); + } + + await message.SendAsync(channel); } catch (Exception e) { diff --git a/src/HonzaBotner.Discord.Services/Options/ButtonOptions.cs b/src/HonzaBotner.Discord.Services/Options/ButtonOptions.cs index 4980f914..8befad64 100644 --- a/src/HonzaBotner.Discord.Services/Options/ButtonOptions.cs +++ b/src/HonzaBotner.Discord.Services/Options/ButtonOptions.cs @@ -7,5 +7,6 @@ public class ButtonOptions public string? VerificationId { get; set; } public string? StaffVerificationId { get; set; } public string? StaffRemoveRoleId { get; set; } + public string? StandUpStatsId { get; set; } public ulong[]? CzechChannelsIds { get; set; } } diff --git a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs index efbf2aad..c7cdf04b 100644 --- a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs +++ b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs @@ -6,5 +6,6 @@ public record StandUpStat( int Id, ulong UserId, int Streak, int LongestStreak, int Freezes, DateTime LastDayOfStreak, - int TotalCompleted, int TotalTasks + int TotalCompleted, int TotalTasks, + int LastDayCompleted, int LastDayTasks ); diff --git a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs index 0ad66a4e..3eba84ae 100644 --- a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs +++ b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs @@ -4,7 +4,18 @@ namespace HonzaBotner.Services.Contract; public interface IStandUpStatsService { + /// + /// Get StandUp stats for a user with a given userId + /// + /// Id of the user + /// Null if user not in database, else his stats Task GetStreak(ulong userId); + /// + /// Update database record of given user regarding StandUp stats. Should be called just once a day. + /// + /// Id of the user + /// Amount of completed tasks yesterday + /// Total amount of tasks yesterday Task UpdateStats(ulong userId, int completed, int total); } diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs index 56d6ed84..ca886805 100644 --- a/src/HonzaBotner.Services/StandUpStatsService.cs +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -16,8 +16,6 @@ public class StandUpStatsService : IStandUpStatsService private readonly ILogger _logger; private readonly StandUpOptions _standUpOptions; - private const int ComparedDay = -1; - public StandUpStatsService( HonzaBotnerDbContext dbContext, ILogger logger, @@ -37,7 +35,7 @@ IOptions standUpOptions return standUpStat == null ? null : GetDto(standUpStat); } - public async Task UpdateStreak(ulong userId, Database.StandUpStat streak) + private Database.StandUpStat UpdateStreak(Database.StandUpStat streak) { int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; @@ -49,7 +47,7 @@ public async Task UpdateStreak(ulong userId, Database.StandUpStat streak) } else //streak restored { - streak.Freezes--; + streak.Freezes -= days; streak.Streak++; streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); @@ -58,26 +56,19 @@ public async Task UpdateStreak(ulong userId, Database.StandUpStat streak) streak.LongestStreak = streak.Streak; } - // Freeze acquired. - if (streak.Streak % _standUpOptions.DaysToAcquireFreeze == 0) + if (streak.Streak % _standUpOptions.DaysToAcquireFreeze == 0) // freeze acquired { streak.Freezes++; } } - try - { - _dbContext.StandUpStats.Update(streak); - await _dbContext.SaveChangesAsync(); - } - catch (Exception e) - { - _logger.LogError(e, "Couldn't update streak {@Streak}", streak); - } + return streak; } public async Task UpdateStats(ulong userId, int completed, int total) { + bool streakMaintained = completed >= _standUpOptions.TasksCompletedThreshold; + Database.StandUpStat? stat = await _dbContext.StandUpStats .FirstOrDefaultAsync(streak => streak.UserId == userId); @@ -90,7 +81,7 @@ public async Task UpdateStats(ulong userId, int completed, int total) LastDayOfStreak = completed != 0 ? DateTime.Today.AddDays(-1).ToUniversalTime() - : DateTime.Today.AddDays(-100).ToUniversalTime(), + : DateTime.UnixEpoch.ToUniversalTime(), Streak = completed != 0 ? 1 : 0, LongestStreak = completed != 0 ? 1 : 0, LastDayCompleted = completed, @@ -113,10 +104,7 @@ public async Task UpdateStats(ulong userId, int completed, int total) return; } - if (completed != 0) - { - await UpdateStreak(userId, stat); - } + stat = UpdateStreak(stat); stat.LastDayCompleted = completed; stat.LastDayTasks = total; @@ -143,6 +131,8 @@ static StandUpStat GetDto(Database.StandUpStat standUpStat) => standUpStat.Freezes, standUpStat.LastDayOfStreak, standUpStat.TotalCompleted, - standUpStat.TotalTasks + standUpStat.TotalTasks, + standUpStat.LastDayCompleted, + standUpStat.LastDayTasks ); } diff --git a/src/HonzaBotner/appsettings.BotDev.json b/src/HonzaBotner/appsettings.BotDev.json index dd8ba99d..62dfa477 100644 --- a/src/HonzaBotner/appsettings.BotDev.json +++ b/src/HonzaBotner/appsettings.BotDev.json @@ -74,7 +74,8 @@ "ButtonOptions": { "VerificationId": "verification-user", "StaffVerificationId": "verification-staff", - "StaffRemoveRoleId": "verification-remove-staff" + "StaffRemoveRoleId": "verification-remove-staff", + "StandUpStatsId": "standup-stats" }, "BadgeRoleOptions": { "TriggerRoles": [ diff --git a/src/HonzaBotner/appsettings.CvutFit.json b/src/HonzaBotner/appsettings.CvutFit.json index 65b27c78..7695e612 100644 --- a/src/HonzaBotner/appsettings.CvutFit.json +++ b/src/HonzaBotner/appsettings.CvutFit.json @@ -114,6 +114,7 @@ "VerificationId": "verification-user", "StaffVerificationId": "verification-staff", "StaffRemoveRoleId": "verification-remove-staff", + "StandUpStatsId": "standup-stats", "CzechChannelsIds": [507515506073403402] }, "BadgeRoleOptions": { diff --git a/src/HonzaBotner/appsettings.Development.json b/src/HonzaBotner/appsettings.Development.json index f0c26140..70221566 100644 --- a/src/HonzaBotner/appsettings.Development.json +++ b/src/HonzaBotner/appsettings.Development.json @@ -85,6 +85,7 @@ "VerificationId": "verification-user", "StaffVerificationId": "verification-staff", "StaffRemoveRoleId": "verification-remove-staff", + "StandUpStatsId": "standup-stats", "CzechChannelsIds": [750108543125946448] }, "BadgeRoleOptions": { From 618d00611de9524304db498ee330dd46f145f647 Mon Sep 17 00:00:00 2001 From: stepech <29132060+stepech@users.noreply.github.com> Date: Sat, 21 May 2022 21:02:57 +0200 Subject: [PATCH 15/18] StandUp: Like if I'm doing this for the first time.... --- src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 89122199..9e6509fd 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -32,14 +32,14 @@ public StandUpJobProvider( DiscordWrapper discord, IOptions standUpOptions, IStandUpStatsService statsService, - ButtonOptions buttonOptions + IOptions buttonOptions ) { _logger = logger; _discord = discord; _standUpOptions = standUpOptions.Value; _statsService = statsService; - _buttonOptions = buttonOptions; + _buttonOptions = buttonOptions.Value; } /// From 00e976c972326b15c3a47f237648d4b1c04f543b Mon Sep 17 00:00:00 2001 From: stepech <29132060+stepech@users.noreply.github.com> Date: Sun, 22 May 2022 01:28:36 +0200 Subject: [PATCH 16/18] StandUp: Streaks counting finish, and button --- src/HonzaBotner.Database/StandUpStat.cs | 4 +- .../EventHandlers/StandUpHandler.cs | 71 +++++++ .../Jobs/StandUpJobProvider.cs | 35 ++-- .../Dto/StandUpStat.cs | 2 +- .../IStandUpStatsService.cs | 2 +- .../StandUpStatsService.cs | 42 +++-- .../20220507222749_StandUpStats.Designer.cs | 177 ------------------ .../Migrations/20220507222749_StandUpStats.cs | 41 ---- .../ApplicationDbContextModelSnapshot.cs | 13 +- src/HonzaBotner/Startup.cs | 1 + 10 files changed, 120 insertions(+), 268 deletions(-) create mode 100644 src/HonzaBotner.Discord.Services/EventHandlers/StandUpHandler.cs delete mode 100644 src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs delete mode 100644 src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs diff --git a/src/HonzaBotner.Database/StandUpStat.cs b/src/HonzaBotner.Database/StandUpStat.cs index 1f1a2f08..5ff7687f 100644 --- a/src/HonzaBotner.Database/StandUpStat.cs +++ b/src/HonzaBotner.Database/StandUpStat.cs @@ -1,11 +1,11 @@ using System; +using System.ComponentModel.DataAnnotations; namespace HonzaBotner.Database; public class StandUpStat { - public int Id { get; set; } - public ulong UserId { get; set; } + [Key] public ulong UserId { get; set; } public int Streak { get; set; } public int LongestStreak { get; set; } public int Freezes { get; set; } diff --git a/src/HonzaBotner.Discord.Services/EventHandlers/StandUpHandler.cs b/src/HonzaBotner.Discord.Services/EventHandlers/StandUpHandler.cs new file mode 100644 index 00000000..ac199c8a --- /dev/null +++ b/src/HonzaBotner.Discord.Services/EventHandlers/StandUpHandler.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.EventArgs; +using HonzaBotner.Discord.EventHandler; +using HonzaBotner.Discord.Services.Options; +using HonzaBotner.Services.Contract; +using HonzaBotner.Services.Contract.Dto; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace HonzaBotner.Discord.Services.EventHandlers; + +public class StandUpHandler : IEventHandler +{ + + private readonly ButtonOptions _buttonOptions; + private readonly IStandUpStatsService _standUpStats; + private readonly ILogger _logger; + + public StandUpHandler( + IOptions buttonOptions, + IStandUpStatsService standUpStats, + ILogger logger) + { + _buttonOptions = buttonOptions.Value; + _standUpStats = standUpStats; + _logger = logger; + } + + public async Task Handle(ComponentInteractionCreateEventArgs args) + { + if (args.Id != _buttonOptions.StandUpStatsId) + { + return EventHandlerResult.Continue; + } + + _logger.LogDebug("{User} requested stats info", args.User.Username); + + StandUpStat? stats = await _standUpStats.GetStreak(args.User.Id); + + DiscordInteractionResponseBuilder response = new(); + response.AsEphemeral(true); + DiscordEmbedBuilder embed = new DiscordEmbedBuilder().WithAuthor("Stats", iconUrl: args.User.AvatarUrl); + + if (stats is null) + { + embed.Description = "Unfortunately you are not in the database yet.\nDatabase updates daily at 8 am"; + embed.Color = new Optional(DiscordColor.Gold); + response.AddEmbed(embed.Build()); + + await args.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, response); + return EventHandlerResult.Stop; + } + + embed.Description = $"Cool stats for {args.User.Mention}"; + embed.AddField("Current streak", stats.Streak.ToString(), true); + embed.AddField("Available freezes", stats.Freezes.ToString(), true); + embed.AddField("Total tasks", stats.TotalTasks.ToString(), true); + embed.AddField("Total completed tasks", stats.TotalCompleted.ToString(), true); + embed.AddField("Last streak update", + ""); + embed.Color = new Optional(DiscordColor.Wheat); + + response.AddEmbed(embed.Build()); + await args.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, response); + return EventHandlerResult.Stop; + } + +} diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 9e6509fd..006d0b05 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -62,8 +62,7 @@ IOptions buttonOptions public async Task ExecuteAsync(CancellationToken cancellationToken) { - var today = DateTime.Today; // Fix one point in time. - DateTime yesterday = today.AddDays(-1); + DateTime yesterday = DateTime.Today.AddDays(-1); try { @@ -73,13 +72,13 @@ public async Task ExecuteAsync(CancellationToken cancellationToken) var fail = new StandUpStats(); List messageList = new(); - messageList.AddRange(await channel.GetMessagesAsync()); + messageList.AddRange((await channel.GetMessagesAsync()).Where(msg => !msg.Author.IsBot)); while (messageList.LastOrDefault()?.Timestamp.Date == yesterday) { int messagesCount = messageList.Count; messageList.AddRange( - await channel.GetMessagesBeforeAsync(messageList.Last().Id) + (await channel.GetMessagesBeforeAsync(messageList.Last().Id)).Where(msg => !msg.Author.IsBot) ); // No new data. @@ -114,26 +113,26 @@ await channel.GetMessagesBeforeAsync(messageList.Last().Id) fail.Increment(priority); } } + } - // Update DB. - if (total != 0) - { - await _statsService.UpdateStats(authorGrouped.Key, completed, total); - } + // Update DB. + if (total != 0) + { + await _statsService.UpdateStats(authorGrouped.Key, completed, total); } } // Send stats message to channel. DiscordMessageBuilder message = new DiscordMessageBuilder().WithContent($@" - Stand-up time, <@&{_standUpOptions.StandUpRoleId}>! - - Results from : - ``` - all: {ok.Add(fail)} - completed: {ok} - failed: {fail} - ``` - "); +Stand-up time, <@&{_standUpOptions.StandUpRoleId}>! + +Results from : +``` +all: {ok.Add(fail)} +completed: {ok} +failed: {fail} +``` +"); if (_buttonOptions.StandUpStatsId is not null) { message.AddComponents(new DiscordButtonComponent(ButtonStyle.Primary, _buttonOptions.StandUpStatsId, diff --git a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs index c7cdf04b..8d7c6ab8 100644 --- a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs +++ b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs @@ -3,7 +3,7 @@ namespace HonzaBotner.Services.Contract.Dto; public record StandUpStat( - int Id, ulong UserId, + ulong UserId, int Streak, int LongestStreak, int Freezes, DateTime LastDayOfStreak, int TotalCompleted, int TotalTasks, diff --git a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs index 3eba84ae..d51e3114 100644 --- a/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs +++ b/src/HonzaBotner.Services.Contract/IStandUpStatsService.cs @@ -12,7 +12,7 @@ public interface IStandUpStatsService Task GetStreak(ulong userId); /// - /// Update database record of given user regarding StandUp stats. Should be called just once a day. + /// Update database record of given user regarding StandUp stats. Should be called just once per day per user. /// /// Id of the user /// Amount of completed tasks yesterday diff --git a/src/HonzaBotner.Services/StandUpStatsService.cs b/src/HonzaBotner.Services/StandUpStatsService.cs index ca886805..23de6b4c 100644 --- a/src/HonzaBotner.Services/StandUpStatsService.cs +++ b/src/HonzaBotner.Services/StandUpStatsService.cs @@ -35,32 +35,37 @@ IOptions standUpOptions return standUpStat == null ? null : GetDto(standUpStat); } - private Database.StandUpStat UpdateStreak(Database.StandUpStat streak) + private Database.StandUpStat UpdateStreak(Database.StandUpStat streak, bool streakMaintained) { int days = (DateTime.Today.AddDays(-2) - streak.LastDayOfStreak).Days; - if (days > streak.Freezes) //Streak broken + // Tasks completed and on time + if (streakMaintained && days <= streak.Freezes) { - streak.Freezes = 0; - streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); - streak.Streak = 1; - } - else //streak restored - { - streak.Freezes -= days; streak.Streak++; + // streak.Streak += days + 1; // Alternative in case we want to count frozen days in streak streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); + streak.Freezes -= days; if (streak.Streak > streak.LongestStreak) { streak.LongestStreak = streak.Streak; } - - if (streak.Streak % _standUpOptions.DaysToAcquireFreeze == 0) // freeze acquired - { - streak.Freezes++; - } } + // Tasks completed, but reset streak + else if (streakMaintained) + { + streak.Streak = 1; + streak.LastDayOfStreak = DateTime.Today.AddDays(-1).ToUniversalTime(); + streak.Freezes = 0; + } + // Not valid, and lateeeeeeeeeee + else if (days >= streak.Freezes) + { + streak.Streak = 0; + streak.Freezes = 0; + } + // Not valid && on time is ignored, in that case nothing happens return streak; } @@ -79,11 +84,11 @@ public async Task UpdateStats(ulong userId, int completed, int total) UserId = userId, Freezes = 0, LastDayOfStreak = - completed != 0 + streakMaintained ? DateTime.Today.AddDays(-1).ToUniversalTime() : DateTime.UnixEpoch.ToUniversalTime(), - Streak = completed != 0 ? 1 : 0, - LongestStreak = completed != 0 ? 1 : 0, + Streak = streakMaintained ? 1 : 0, + LongestStreak = streakMaintained ? 1 : 0, LastDayCompleted = completed, LastDayTasks = total, TotalCompleted = completed, @@ -104,7 +109,7 @@ public async Task UpdateStats(ulong userId, int completed, int total) return; } - stat = UpdateStreak(stat); + stat = UpdateStreak(stat, streakMaintained); stat.LastDayCompleted = completed; stat.LastDayTasks = total; @@ -124,7 +129,6 @@ public async Task UpdateStats(ulong userId, int completed, int total) static StandUpStat GetDto(Database.StandUpStat standUpStat) => new( - standUpStat.Id, standUpStat.UserId, standUpStat.Streak, standUpStat.LongestStreak, diff --git a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs deleted file mode 100644 index 8822cace..00000000 --- a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.Designer.cs +++ /dev/null @@ -1,177 +0,0 @@ -// -using System; -using HonzaBotner.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace HonzaBotner.Migrations -{ - [DbContext(typeof(HonzaBotnerDbContext))] - [Migration("20220507222749_StandUpStats")] - partial class StandUpStats - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.4") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("HonzaBotner.Database.CountedEmoji", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("numeric(20,0)"); - - b.Property("FirstUsedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Times") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("CountedEmojis"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Reminder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ChannelId") - .HasColumnType("numeric(20,0)"); - - b.Property("Content") - .HasColumnType("text"); - - b.Property("DateTime") - .HasColumnType("timestamp with time zone"); - - b.Property("MessageId") - .HasColumnType("numeric(20,0)"); - - b.Property("OwnerId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.HasIndex("DateTime"); - - b.ToTable("Reminders"); - }); - - modelBuilder.Entity("HonzaBotner.Database.RoleBinding", b => - { - b.Property("Emoji") - .HasColumnType("text"); - - b.Property("ChannelId") - .HasColumnType("numeric(20,0)"); - - b.Property("MessageId") - .HasColumnType("numeric(20,0)"); - - b.Property("RoleId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Emoji", "ChannelId", "MessageId", "RoleId"); - - b.ToTable("RoleBindings"); - }); - - modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Freezes") - .HasColumnType("integer"); - - b.Property("LastDayCompleted") - .HasColumnType("integer"); - - b.Property("LastDayOfStreak") - .HasColumnType("timestamp with time zone"); - - b.Property("LastDayTasks") - .HasColumnType("integer"); - - b.Property("LongestStreak") - .HasColumnType("integer"); - - b.Property("Streak") - .HasColumnType("integer"); - - b.Property("TotalCompleted") - .HasColumnType("integer"); - - b.Property("TotalTasks") - .HasColumnType("integer"); - - b.Property("UserId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("StandUpStats"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Verification", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("numeric(20,0)"); - - b.Property("AuthId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.HasIndex("AuthId") - .IsUnique(); - - b.ToTable("Verifications"); - }); - - modelBuilder.Entity("HonzaBotner.Database.Warning", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("IssuerId") - .HasColumnType("numeric(20,0)"); - - b.Property("Reason") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); - - b.ToTable("Warnings"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs b/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs deleted file mode 100644 index 299347d6..00000000 --- a/src/HonzaBotner/Migrations/20220507222749_StandUpStats.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace HonzaBotner.Migrations -{ - public partial class StandUpStats : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "StandUpStats", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserId = table.Column(type: "numeric(20,0)", nullable: false), - Streak = table.Column(type: "integer", nullable: false), - LongestStreak = table.Column(type: "integer", nullable: false), - Freezes = table.Column(type: "integer", nullable: false), - LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false), - LastDayCompleted = table.Column(type: "integer", nullable: false), - LastDayTasks = table.Column(type: "integer", nullable: false), - TotalCompleted = table.Column(type: "integer", nullable: false), - TotalTasks = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_StandUpStats", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "StandUpStats"); - } - } -} diff --git a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs index 718d8e23..6177f2f8 100644 --- a/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/HonzaBotner/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("ProductVersion", "6.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -90,11 +90,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => { - b.Property("Id") + b.Property("UserId") .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + .HasColumnType("numeric(20,0)"); b.Property("Freezes") .HasColumnType("integer"); @@ -120,10 +118,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TotalTasks") .HasColumnType("integer"); - b.Property("UserId") - .HasColumnType("numeric(20,0)"); - - b.HasKey("Id"); + b.HasKey("UserId"); b.ToTable("StandUpStats"); }); diff --git a/src/HonzaBotner/Startup.cs b/src/HonzaBotner/Startup.cs index 877b897a..614ed42b 100644 --- a/src/HonzaBotner/Startup.cs +++ b/src/HonzaBotner/Startup.cs @@ -84,6 +84,7 @@ public void ConfigureServices(IServiceCollection services) .AddEventHandler(EventHandlerPriority.Urgent) .AddEventHandler() .AddEventHandler() + .AddEventHandler() ; } ) From c83696377641a9e9281175871b9179a47e850262 Mon Sep 17 00:00:00 2001 From: stepech <29132060+stepech@users.noreply.github.com> Date: Sun, 22 May 2022 01:28:57 +0200 Subject: [PATCH 17/18] And this.. Commit this too --- .../20220521212254_StandUpStats.Designer.cs | 172 ++++++++++++++++++ .../Migrations/20220521212254_StandUpStats.cs | 38 ++++ 2 files changed, 210 insertions(+) create mode 100644 src/HonzaBotner/Migrations/20220521212254_StandUpStats.Designer.cs create mode 100644 src/HonzaBotner/Migrations/20220521212254_StandUpStats.cs diff --git a/src/HonzaBotner/Migrations/20220521212254_StandUpStats.Designer.cs b/src/HonzaBotner/Migrations/20220521212254_StandUpStats.Designer.cs new file mode 100644 index 00000000..66e2a474 --- /dev/null +++ b/src/HonzaBotner/Migrations/20220521212254_StandUpStats.Designer.cs @@ -0,0 +1,172 @@ +// +using System; +using HonzaBotner.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + [DbContext(typeof(HonzaBotnerDbContext))] + [Migration("20220521212254_StandUpStats")] + partial class StandUpStats + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HonzaBotner.Database.CountedEmoji", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("FirstUsedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Times") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("CountedEmojis"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("OwnerId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("HonzaBotner.Database.RoleBinding", b => + { + b.Property("Emoji") + .HasColumnType("text"); + + b.Property("ChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("MessageId") + .HasColumnType("numeric(20,0)"); + + b.Property("RoleId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Emoji", "ChannelId", "MessageId", "RoleId"); + + b.ToTable("RoleBindings"); + }); + + modelBuilder.Entity("HonzaBotner.Database.StandUpStat", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("Freezes") + .HasColumnType("integer"); + + b.Property("LastDayCompleted") + .HasColumnType("integer"); + + b.Property("LastDayOfStreak") + .HasColumnType("timestamp with time zone"); + + b.Property("LastDayTasks") + .HasColumnType("integer"); + + b.Property("LongestStreak") + .HasColumnType("integer"); + + b.Property("Streak") + .HasColumnType("integer"); + + b.Property("TotalCompleted") + .HasColumnType("integer"); + + b.Property("TotalTasks") + .HasColumnType("integer"); + + b.HasKey("UserId"); + + b.ToTable("StandUpStats"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Verification", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)"); + + b.Property("AuthId") + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.HasIndex("AuthId") + .IsUnique(); + + b.ToTable("Verifications"); + }); + + modelBuilder.Entity("HonzaBotner.Database.Warning", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IssuerId") + .HasColumnType("numeric(20,0)"); + + b.Property("Reason") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.ToTable("Warnings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HonzaBotner/Migrations/20220521212254_StandUpStats.cs b/src/HonzaBotner/Migrations/20220521212254_StandUpStats.cs new file mode 100644 index 00000000..9d3b5316 --- /dev/null +++ b/src/HonzaBotner/Migrations/20220521212254_StandUpStats.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HonzaBotner.Migrations +{ + public partial class StandUpStats : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "StandUpStats", + columns: table => new + { + UserId = table.Column(type: "numeric(20,0)", nullable: false), + Streak = table.Column(type: "integer", nullable: false), + LongestStreak = table.Column(type: "integer", nullable: false), + Freezes = table.Column(type: "integer", nullable: false), + LastDayOfStreak = table.Column(type: "timestamp with time zone", nullable: false), + LastDayCompleted = table.Column(type: "integer", nullable: false), + LastDayTasks = table.Column(type: "integer", nullable: false), + TotalCompleted = table.Column(type: "integer", nullable: false), + TotalTasks = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_StandUpStats", x => x.UserId); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "StandUpStats"); + } + } +} From 168b1c6b484a9630eca740cede08d0c476618ecd Mon Sep 17 00:00:00 2001 From: stepech <29132060+stepech@users.noreply.github.com> Date: Mon, 23 May 2022 17:40:49 +0200 Subject: [PATCH 18/18] StandUp: Commit suggestions (code formatting) --- .../Jobs/StandUpJobProvider.cs | 4 ++-- src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs index 006d0b05..bd83e91b 100644 --- a/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs +++ b/src/HonzaBotner.Discord.Services/Jobs/StandUpJobProvider.cs @@ -71,8 +71,8 @@ public async Task ExecuteAsync(CancellationToken cancellationToken) var ok = new StandUpStats(); var fail = new StandUpStats(); - List messageList = new(); - messageList.AddRange((await channel.GetMessagesAsync()).Where(msg => !msg.Author.IsBot)); + List messageList = (await channel.GetMessagesAsync()) + .Where(msg => !msg.Author.IsBot).ToList(); while (messageList.LastOrDefault()?.Timestamp.Date == yesterday) { diff --git a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs index 8d7c6ab8..4a2aa3bf 100644 --- a/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs +++ b/src/HonzaBotner.Services.Contract/Dto/StandUpStat.cs @@ -3,9 +3,9 @@ namespace HonzaBotner.Services.Contract.Dto; public record StandUpStat( - ulong UserId, - int Streak, int LongestStreak, - int Freezes, DateTime LastDayOfStreak, - int TotalCompleted, int TotalTasks, - int LastDayCompleted, int LastDayTasks + ulong UserId, int Streak, + int LongestStreak, int Freezes, + DateTime LastDayOfStreak, int TotalCompleted, + int TotalTasks, int LastDayCompleted, + int LastDayTasks );