Sort team picks by the time they were team picked (#980)

This commit is contained in:
Josh 2024-02-23 08:38:02 -06:00 committed by GitHub
commit a2eaedc85b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 136 additions and 21 deletions

View file

@ -61,7 +61,7 @@ public struct ApiSlot
MoveRequired = slot.MoveRequired, MoveRequired = slot.MoveRequired,
FirstUploaded = slot.FirstUploaded, FirstUploaded = slot.FirstUploaded,
LastUpdated = slot.LastUpdated, LastUpdated = slot.LastUpdated,
TeamPick = slot.TeamPick, TeamPick = slot.TeamPickTime != 0,
Location = slot.Location, Location = slot.Location,
GameVersion = slot.GameVersion, GameVersion = slot.GameVersion,
Plays = slot.Plays, Plays = slot.Plays,

View file

@ -31,7 +31,7 @@ public struct MinimalApiSlot
Type = slot.Type, Type = slot.Type,
Name = slot.Name, Name = slot.Name,
IconHash = slot.IconHash, IconHash = slot.IconHash,
TeamPick = slot.TeamPick, TeamPick = slot.TeamPickTime != 0,
IsAdventure = slot.IsAdventurePlanet, IsAdventure = slot.IsAdventurePlanet,
Location = slot.Location, Location = slot.Location,
GameVersion = slot.GameVersion, GameVersion = slot.GameVersion,

View file

@ -247,7 +247,8 @@ public class SlotsController : ControllerBase
pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder); pageData.TotalElements = await StatisticsHelper.SlotCount(this.database, queryBuilder);
SlotSortBuilder<SlotEntity> sortBuilder = new(); SlotSortBuilder<SlotEntity> sortBuilder = new();
sortBuilder.AddSort(new LastUpdatedSort()); sortBuilder.AddSort(new TeamPickSort());
sortBuilder.AddSort(new FirstUploadedSort());
List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder); List<SlotBase> slots = await this.database.GetSlots(token, queryBuilder, pageData, sortBuilder);

View file

@ -19,5 +19,5 @@ public class TeamPicksCategory : SlotCategory
public override IQueryable<SlotEntity> GetItems(DatabaseContext database, GameTokenEntity token, SlotQueryBuilder queryBuilder) => public override IQueryable<SlotEntity> GetItems(DatabaseContext database, GameTokenEntity token, SlotQueryBuilder queryBuilder) =>
database.Slots.Where(queryBuilder.Clone().AddFilter(new TeamPickFilter()).Build()) database.Slots.Where(queryBuilder.Clone().AddFilter(new TeamPickFilter()).Build())
.ApplyOrdering(new SlotSortBuilder<SlotEntity>().AddSort(new FirstUploadedSort())); .ApplyOrdering(new SlotSortBuilder<SlotEntity>().AddSort(new TeamPickSort()).AddSort(new FirstUploadedSort()));
} }

View file

@ -28,7 +28,7 @@ public class ModerationSlotController : ControllerBase
SlotEntity? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
slot.TeamPick = true; slot.TeamPickTime = TimeHelper.TimestampMillis;
// Send webhook with slot.Name and slot.Creator.Username // Send webhook with slot.Name and slot.Creator.Username
await WebhookHelper.SendWebhook("New Team Pick!", $"The level [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId}) by **{slot.Creator?.Username}** has been team picked"); await WebhookHelper.SendWebhook("New Team Pick!", $"The level [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId}) by **{slot.Creator?.Username}** has been team picked");
@ -51,7 +51,7 @@ public class ModerationSlotController : ControllerBase
SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
slot.TeamPick = false; slot.TeamPickTime = 0;
// Send a notification to the creator // Send a notification to the creator
await this.database.SendNotification(slot.CreatorId, await this.database.SendNotification(slot.CreatorId,

View file

@ -41,8 +41,9 @@ public class LandingPage : BaseLayout
const int maxShownLevels = 5; const int maxShownLevels = 5;
this.LatestTeamPicks = await this.Database.Slots.Where(s => s.Type == SlotType.User && !s.SubLevel && !s.Hidden) this.LatestTeamPicks = await this.Database.Slots.Where(s => s.Type == SlotType.User && !s.SubLevel && !s.Hidden)
.Where(s => s.TeamPick) .Where(s => s.TeamPickTime != 0)
.OrderByDescending(s => s.FirstUploaded) .OrderByDescending(s => s.TeamPickTime)
.ThenByDescending(s => s.FirstUploaded)
.Take(maxShownLevels) .Take(maxShownLevels)
.Include(s => s.Creator) .Include(s => s.Creator)
.ToListAsync(); .ToListAsync();

View file

@ -217,9 +217,9 @@ else
<div class="ui green segment"> <div class="ui green segment">
<h2>Moderation Options</h2> <h2>Moderation Options</h2>
@if (Model.Slot?.TeamPick ?? false) @if ((Model.Slot?.TeamPickTime ?? 0) != 0)
{ {
<a href="/moderation/slot/@Model.Slot.SlotId/removeTeamPick"> <a href="/moderation/slot/@Model.Slot?.SlotId/removeTeamPick">
<div class="ui pink button"> <div class="ui pink button">
<i class="star icon"></i> <i class="star icon"></i>
<span>Remove Team Pick</span> <span>Remove Team Pick</span>

View file

@ -495,4 +495,62 @@ public class SlotControllerTests
} }
} }
#endregion #endregion
#region Team Picks
[Fact]
public async Task TeamPick_ShouldOnlyIncludeTeamPickedLevels()
{
DatabaseContext db = await MockHelper.GetTestDatabase(new List<SlotEntity>
{
new()
{
SlotId = 1,
CreatorId = 1,
TeamPickTime = 1,
},
new()
{
SlotId = 2,
CreatorId = 1,
TeamPickTime = 0,
},
});
SlotsController controller = new(db);
controller.SetupTestController();
IActionResult result = await controller.TeamPickedSlots();
GenericSlotResponse slotResponse = result.CastTo<OkObjectResult, GenericSlotResponse>();
Assert.Single(slotResponse.Slots);
Assert.Equal(1, slotResponse.Slots.OfType<GameUserSlot>().First().SlotId);
}
[Fact]
public async Task TeamPick_LevelsAreSortedByTimestamp()
{
DatabaseContext db = await MockHelper.GetTestDatabase(new List<SlotEntity>
{
new()
{
SlotId = 1,
CreatorId = 1,
TeamPickTime = 1,
},
new()
{
SlotId = 2,
CreatorId = 1,
TeamPickTime = 5,
},
});
SlotsController controller = new(db);
controller.SetupTestController();
IActionResult result = await controller.TeamPickedSlots();
GenericSlotResponse slotResponse = result.CastTo<OkObjectResult, GenericSlotResponse>();
Assert.Equal(2, slotResponse.Slots.Count);
Assert.Equal(2, slotResponse.Slots.OfType<GameUserSlot>().First().SlotId);
Assert.Equal(1, slotResponse.Slots.OfType<GameUserSlot>().Last().SlotId);
}
#endregion
} }

View file

@ -51,7 +51,7 @@ public class StatisticsControllerTests
{ {
SlotId = 3, SlotId = 3,
CreatorId = 1, CreatorId = 1,
TeamPick = true, TeamPickTime = 1,
}, },
}; };
await using DatabaseContext db = await MockHelper.GetTestDatabase(slots); await using DatabaseContext db = await MockHelper.GetTestDatabase(slots);
@ -90,7 +90,7 @@ public class StatisticsControllerTests
{ {
SlotId = 3, SlotId = 3,
CreatorId = 1, CreatorId = 1,
TeamPick = true, TeamPickTime = 1,
GameVersion = GameVersion.LittleBigPlanet2, GameVersion = GameVersion.LittleBigPlanet2,
}, },
}; };
@ -130,7 +130,7 @@ public class StatisticsControllerTests
{ {
SlotId = 3, SlotId = 3,
CreatorId = 1, CreatorId = 1,
TeamPick = true, TeamPickTime = 1,
GameVersion = GameVersion.LittleBigPlanet1, GameVersion = GameVersion.LittleBigPlanet1,
}, },
}; };
@ -168,7 +168,7 @@ public class StatisticsControllerTests
{ {
SlotId = 3, SlotId = 3,
CreatorId = 1, CreatorId = 1,
TeamPick = true, TeamPickTime = 1,
GameVersion = GameVersion.LittleBigPlanet2, GameVersion = GameVersion.LittleBigPlanet2,
}, },
}; };

View file

@ -729,7 +729,7 @@ public class FilterTests
SlotEntity slot = new() SlotEntity slot = new()
{ {
TeamPick = true, TeamPickTime = 1,
}; };
Assert.True(teamPickFunc(slot)); Assert.True(teamPickFunc(slot));
@ -743,7 +743,7 @@ public class FilterTests
SlotEntity slot = new() SlotEntity slot = new()
{ {
TeamPick = false, TeamPickTime = 0,
}; };
Assert.False(teamPickFunc(slot)); Assert.False(teamPickFunc(slot));

View file

@ -7,5 +7,5 @@ namespace LBPUnion.ProjectLighthouse.Filter.Filters;
public class TeamPickFilter : ISlotFilter public class TeamPickFilter : ISlotFilter
{ {
public Expression<Func<SlotEntity, bool>> GetPredicate() => s => s.TeamPick; public Expression<Func<SlotEntity, bool>> GetPredicate() => s => s.TeamPickTime != 0;
} }

View file

@ -0,0 +1,11 @@
using System;
using System.Linq.Expressions;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Filter.Sorts;
namespace LBPUnion.ProjectLighthouse.Filter.Sorts;
public class TeamPickSort : ISlotSort
{
public Expression<Func<SlotEntity, dynamic>> GetExpression() => s => s.TeamPickTime;
}

View file

@ -0,0 +1,44 @@
using LBPUnion.ProjectLighthouse.Database;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20240214031744_UpdateTeamPickBoolToTimestamp")]
public partial class UpdateTeamPickBoolToTimestamp : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(name: "TeamPickTime",
table: "Slots",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.Sql("UPDATE `Slots` SET TeamPickTime = 1 WHERE TeamPick = 1");
migrationBuilder.DropColumn(
name: "TeamPick",
table: "Slots");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TeamPickTime",
table: "Slots");
migrationBuilder.AddColumn<bool>(
name: "TeamPick",
table: "Slots",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
}
}

View file

@ -472,8 +472,8 @@ namespace ProjectLighthouse.Migrations
b.Property<bool>("SubLevel") b.Property<bool>("SubLevel")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<bool>("TeamPick") b.Property<long>("TeamPickTime")
.HasColumnType("tinyint(1)"); .HasColumnType("bigint");
b.Property<int>("Type") b.Property<int>("Type")
.HasColumnType("int"); .HasColumnType("int");

View file

@ -102,7 +102,7 @@ public class SlotEntity
public long LastUpdated { get; set; } public long LastUpdated { get; set; }
public bool TeamPick { get; set; } public long TeamPickTime { get; set; }
public GameVersion GameVersion { get; set; } public GameVersion GameVersion { get; set; }

View file

@ -84,7 +84,7 @@ public abstract class SlotBase : ILbpSerializable
InitiallyLocked = slot.InitiallyLocked, InitiallyLocked = slot.InitiallyLocked,
RootLevel = slot.RootLevel, RootLevel = slot.RootLevel,
IsShareable = slot.Shareable, IsShareable = slot.Shareable,
IsTeamPicked = slot.TeamPick, IsTeamPicked = slot.TeamPickTime != 0,
FirstUploaded = slot.FirstUploaded, FirstUploaded = slot.FirstUploaded,
LastUpdated = slot.LastUpdated, LastUpdated = slot.LastUpdated,
IsCrossControlRequired = slot.CrossControllerRequired, IsCrossControlRequired = slot.CrossControllerRequired,