-
-
Notifications
You must be signed in to change notification settings - Fork 388
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* [406] WebUI - add page to see progress towards Annual Challenge tiers * added api client and contracts * [406] WebUI - new page to track Annual Challenge progress * better error handling
- Loading branch information
1 parent
fe0eb07
commit 9378dba
Showing
16 changed files
with
403 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
using Common.Dto; | ||
using Common.Dto.Api; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Peloton.AnnualChallenge; | ||
|
||
namespace Api.Controllers; | ||
|
||
[ApiController] | ||
[Produces("application/json")] | ||
[Consumes("application/json")] | ||
public class PelotonAnnualChallengeController : Controller | ||
{ | ||
private readonly IAnnualChallengeService _annualChallengeService; | ||
|
||
public PelotonAnnualChallengeController(IAnnualChallengeService annualChallengeService) | ||
{ | ||
_annualChallengeService = annualChallengeService; | ||
} | ||
|
||
/// <summary> | ||
/// Fetches a progress summary for the Peloton Annual Challenge. | ||
/// </summary> | ||
/// <response code="200">Returns the progress summary</response> | ||
/// <response code="400">Invalid request values.</response> | ||
/// <response code="500">Unhandled exception.</response> | ||
[HttpGet] | ||
[Route("api/pelotonannualchallenge/progress")] | ||
[ProducesResponseType(StatusCodes.Status200OK)] | ||
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] | ||
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)] | ||
public async Task<ActionResult<ProgressGetResponse>> GetProgressSummaryAsync() | ||
{ | ||
var userId = 1; | ||
try | ||
{ | ||
var serviceResult = await _annualChallengeService.GetAnnualChallengeProgressAsync(userId); | ||
|
||
if (serviceResult.IsErrored()) | ||
return serviceResult.GetResultForError(); | ||
|
||
var data = serviceResult.Result; | ||
var tiers = data.Tiers?.Select(t => new Common.Dto.Api.Tier() | ||
{ | ||
BadgeUrl = t.BadgeUrl, | ||
Title = t.Title, | ||
RequiredMinutes = t.RequiredMinutes, | ||
HasEarned = t.HasEarned, | ||
PercentComplete = Convert.ToSingle(t.PercentComplete * 100), | ||
IsOnTrackToEarndByEndOfYear = t.IsOnTrackToEarndByEndOfYear, | ||
MinutesBehindPace = t.MinutesBehindPace, | ||
MinutesAheadOfPace = t.MinutesAheadOfPace, | ||
MinutesNeededPerDay = t.MinutesNeededPerDay, | ||
MinutesNeededPerWeek = t.MinutesNeededPerWeek, | ||
}).ToList(); | ||
|
||
return Ok(new ProgressGetResponse() | ||
{ | ||
EarnedMinutes = data.EarnedMinutes, | ||
Tiers = tiers ?? new List<Common.Dto.Api.Tier>(), | ||
}); | ||
} | ||
catch (Exception e) | ||
{ | ||
return StatusCode(StatusCodes.Status500InternalServerError, new ErrorResponse($"Unexpected error occurred: {e.Message}")); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace Common.Dto.Api; | ||
|
||
public record ProgressGetResponse | ||
{ | ||
public double EarnedMinutes { get; init; } | ||
public ICollection<Tier> Tiers { get; init; } | ||
} | ||
|
||
public record Tier | ||
{ | ||
public string BadgeUrl { get; init; } | ||
public string Title { get; init; } | ||
public double RequiredMinutes { get; init; } | ||
public bool HasEarned { get; init; } | ||
public float PercentComplete { get; init; } | ||
public bool IsOnTrackToEarndByEndOfYear { get; init; } | ||
public double MinutesBehindPace { get; init; } | ||
public double MinutesAheadOfPace { get; init; } | ||
public double MinutesNeededPerDay { get; init; } | ||
public double MinutesNeededPerWeek { get; init; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace Peloton.AnnualChallenge; | ||
|
||
public record AnnualChallengeProgress | ||
{ | ||
public bool HasJoined { get; set; } | ||
public double EarnedMinutes { get; set; } | ||
public ICollection<Tier> Tiers { get; set; } | ||
} | ||
|
||
public record Tier | ||
{ | ||
public string BadgeUrl { get; set; } | ||
public string Title { get; set; } | ||
public double RequiredMinutes { get; set; } | ||
public bool HasEarned { get; set; } | ||
public double PercentComplete { get; set; } | ||
public bool IsOnTrackToEarndByEndOfYear { get; set; } | ||
public double MinutesBehindPace { get; set; } | ||
public double MinutesAheadOfPace { get; set; } | ||
public double MinutesNeededPerDay { get; set; } | ||
public double MinutesNeededPerWeek { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
using Common.Dto; | ||
using System; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace Peloton.AnnualChallenge; | ||
|
||
public interface IAnnualChallengeService | ||
{ | ||
Task<ServiceResult<AnnualChallengeProgress>> GetAnnualChallengeProgressAsync(int userId); | ||
} | ||
|
||
public class AnnualChallengeService : IAnnualChallengeService | ||
{ | ||
private const string AnnualChallengeId = "66863eacd9d04447979d5dba7bf0e766"; | ||
|
||
private IPelotonApi _pelotonApi; | ||
|
||
public AnnualChallengeService(IPelotonApi pelotonApi) | ||
{ | ||
_pelotonApi = pelotonApi; | ||
} | ||
|
||
public async Task<ServiceResult<AnnualChallengeProgress>> GetAnnualChallengeProgressAsync(int userId) | ||
{ | ||
var result = new ServiceResult<AnnualChallengeProgress>(); | ||
result.Result = new AnnualChallengeProgress(); | ||
|
||
var joinedChallenges = await _pelotonApi.GetJoinedChallengesAsync(userId); | ||
if (joinedChallenges == null || joinedChallenges.Challenges.Length <= 0) | ||
return result; | ||
|
||
var annualChallenge = joinedChallenges.Challenges.FirstOrDefault(c => c.Challenge_Summary.Id == AnnualChallengeId || c.Challenge_Summary.Title == "The Annual 2023"); | ||
if (annualChallenge is null) | ||
return result; | ||
|
||
var annualChallengeProgressDetail = await _pelotonApi.GetUserChallengeDetailsAsync(userId, AnnualChallengeId); | ||
if (annualChallengeProgressDetail is null) | ||
return result; | ||
|
||
var tiers = annualChallengeProgressDetail.Challenge_Detail.Tiers; | ||
var progress = annualChallengeProgressDetail.Progress; | ||
|
||
var now = DateTime.UtcNow; | ||
var startTimeUtc = DateTimeOffset.FromUnixTimeSeconds(annualChallengeProgressDetail.Challenge_Summary.Start_Time).UtcDateTime; | ||
var endTimeUtc = DateTimeOffset.FromUnixTimeSeconds(annualChallengeProgressDetail.Challenge_Summary.End_Time).UtcDateTime; | ||
|
||
result.Result.HasJoined = true; | ||
result.Result.EarnedMinutes = progress.Metric_Value; | ||
result.Result.Tiers = tiers.Where(t => t.Metric_Value > 0).Select(t => | ||
{ | ||
var requiredMinutes = t.Metric_Value; | ||
var actualMinutes = progress.Metric_Value; | ||
var onTrackDetails = CalculateOnTrackDetails(now, startTimeUtc, endTimeUtc, actualMinutes, requiredMinutes); | ||
|
||
return new Tier() | ||
{ | ||
BadgeUrl = t.detailed_badge_image_url, | ||
Title = t.Title, | ||
RequiredMinutes = requiredMinutes, | ||
HasEarned = onTrackDetails.HasEarned, | ||
PercentComplete= onTrackDetails.PercentComplete, | ||
IsOnTrackToEarndByEndOfYear = onTrackDetails.IsOnTrackToEarnByEndOfYear, | ||
MinutesBehindPace = onTrackDetails.MinutesBehindPace, | ||
MinutesAheadOfPace = onTrackDetails.MinutesBehindPace * -1, | ||
MinutesNeededPerDay = onTrackDetails.MinutesNeededPerDay, | ||
MinutesNeededPerWeek = onTrackDetails.MinutesNeededPerDay * 7, | ||
}; | ||
}).ToList(); | ||
|
||
return result; | ||
} | ||
|
||
public static OnTrackDetails CalculateOnTrackDetails(DateTime now, DateTime startTimeUtc, DateTime endTimeUtc, double earnedMinutes, double requiredMinutes) | ||
{ | ||
var totalTime = endTimeUtc - startTimeUtc; | ||
var totalDays = Math.Ceiling(totalTime.TotalDays); | ||
|
||
var minutesNeededPerDay = requiredMinutes / totalDays; | ||
|
||
var elapsedTime = now - startTimeUtc; | ||
var elapsedDays = Math.Ceiling(elapsedTime.TotalDays); | ||
|
||
var neededMinutesToBeOnTrack = elapsedDays * minutesNeededPerDay; | ||
|
||
return new OnTrackDetails() | ||
{ | ||
IsOnTrackToEarnByEndOfYear = earnedMinutes >= neededMinutesToBeOnTrack, | ||
MinutesBehindPace = neededMinutesToBeOnTrack - earnedMinutes, | ||
MinutesNeededPerDay = minutesNeededPerDay, | ||
HasEarned = earnedMinutes >= requiredMinutes, | ||
PercentComplete = earnedMinutes / requiredMinutes, | ||
}; | ||
} | ||
|
||
public record OnTrackDetails | ||
{ | ||
public bool IsOnTrackToEarnByEndOfYear { get; init; } | ||
public double MinutesBehindPace { get; init; } | ||
public double MinutesNeededPerDay { get; init; } | ||
public bool HasEarned { get; init; } | ||
public double PercentComplete { get; init; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
namespace Peloton.AnnualChallenge; | ||
|
||
public record PelotonChallenges | ||
{ | ||
public PelotonChallenge[] Challenges { get; set; } | ||
} | ||
|
||
public record PelotonChallenge | ||
{ | ||
public ChallengeSummary Challenge_Summary { get; set; } | ||
|
||
// participants | ||
// progress | ||
} | ||
|
||
public record ChallengeSummary | ||
{ | ||
public string Id { get; set; } | ||
public string Title { get; set; } | ||
public string Symbol_Image_Url { get; set; } | ||
public long Start_Time { get; set; } | ||
public long End_Time { get; set; } | ||
} | ||
|
||
public record PelotonUserChallengeDetail | ||
{ | ||
public ChallengeDetail Challenge_Detail { get; set; } | ||
public ChallengeSummary Challenge_Summary { get; set; } | ||
public ChallengeProgress Progress { get; set; } | ||
} | ||
|
||
public record ChallengeDetail | ||
{ | ||
public string Detailed_Description { get; set; } | ||
public PelotonChallengeTier[] Tiers { get; set; } | ||
|
||
} | ||
|
||
public record PelotonChallengeTier | ||
{ | ||
public string Id { get; set; } | ||
public string Title { get; set; } | ||
public string detailed_badge_image_url { get; set; } | ||
public double Metric_Value { get; set; } | ||
public string Metric_Display_Unit { get; set; } | ||
} | ||
|
||
public record ChallengeProgress | ||
{ | ||
// Current User Value | ||
public double Metric_Value { get; set; } | ||
public string Metric_Display_Unit { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.