From e64d51834e177f87a4407052218798bbd3169752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janis=20Daniel=20Da=CC=88hne?= <janis.daehne@informatik.uni-halle.de> Date: Fri, 21 Jan 2022 16:10:07 +0100 Subject: [PATCH] - added feature #169 - add status for passed normal tests in list views --- src/ClientServer/Config/Constants.cs | 2 +- .../Exercises/ExerciseOverviewController.cs | 200 +++++++++++++++++- 2 files changed, 200 insertions(+), 2 deletions(-) diff --git a/src/ClientServer/Config/Constants.cs b/src/ClientServer/Config/Constants.cs index 9fb6e96..b948e48 100644 --- a/src/ClientServer/Config/Constants.cs +++ b/src/ClientServer/Config/Constants.cs @@ -13,7 +13,7 @@ namespace ClientServer.Helpers /// </summary> public static class Constants { - public static string VersionString = "2.19.0"; + public static string VersionString = "2.20.0"; /// <summary> /// this is only set once at program.cs!! diff --git a/src/ClientServer/Controllers/Core/Exercises/ExerciseOverviewController.cs b/src/ClientServer/Controllers/Core/Exercises/ExerciseOverviewController.cs index 20458d8..72537ba 100644 --- a/src/ClientServer/Controllers/Core/Exercises/ExerciseOverviewController.cs +++ b/src/ClientServer/Controllers/Core/Exercises/ExerciseOverviewController.cs @@ -14,6 +14,7 @@ using ClientServer.Models.Users; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; namespace ClientServer.Controllers.Core.Exercises { @@ -181,7 +182,7 @@ namespace ClientServer.Controllers.Core.Exercises } else { - //video visibility + //via visibility participations = _context.ExerciseReleaseWithUserAsParticipations .Include(p => p.ExerciseRelease) @@ -207,6 +208,7 @@ namespace ClientServer.Controllers.Core.Exercises DisplayName = participation.ExerciseRelease.Exercise.DisplayName, ShortDescription = participation.ExerciseRelease.Exercise.ShortDescription, GeneratedCode = participation.ExerciseRelease.GeneratedCode, + ReleaseId = participation.ExerciseRelease.Id, Tags = participation.ExerciseRelease.Exercise.MetaData.TagWithMetaDatas.Select(p => new TagFullBase() { @@ -399,6 +401,7 @@ namespace ClientServer.Controllers.Core.Exercises DisplayName = participation.ExerciseRelease.Exercise.DisplayName, ShortDescription = participation.ExerciseRelease.Exercise.ShortDescription, GeneratedCode = participation.ExerciseRelease.GeneratedCode, + ReleaseId = participation.ExerciseRelease.Id, Tags = participation.ExerciseRelease.Exercise.MetaData.TagWithMetaDatas.Select(p => new TagFullBase() { @@ -425,6 +428,8 @@ namespace ClientServer.Controllers.Core.Exercises .ToList(); var frontendData = PaginationHelper.TransformForFrontend(pagedData, temp); + + await GetAndSetAdditionalDataForExercisesPreviews(userId, frontendData); await Response.WriteAsync( @@ -493,6 +498,7 @@ namespace ClientServer.Controllers.Core.Exercises DisplayName = connection.ExerciseRelease.Exercise.DisplayName, ShortDescription = connection.ExerciseRelease.Exercise.ShortDescription, GeneratedCode = connection.ExerciseRelease.GeneratedCode, + ReleaseId = connection.ExerciseRelease.Id, Tags = connection.ExerciseRelease.Exercise.MetaData.TagWithMetaDatas.Select(p => new TagFullBase() { @@ -626,6 +632,7 @@ namespace ClientServer.Controllers.Core.Exercises DisplayName = connection.ExerciseRelease.Exercise.DisplayName, ShortDescription = connection.ExerciseRelease.Exercise.ShortDescription, GeneratedCode = connection.ExerciseRelease.GeneratedCode, + ReleaseId = connection.ExerciseRelease.Id, Tags = connection.ExerciseRelease.Exercise.MetaData.TagWithMetaDatas.Select(p => new TagFullBase() { @@ -701,6 +708,7 @@ namespace ClientServer.Controllers.Core.Exercises DisplayName = exerciseRelease.Exercise.DisplayName, ShortDescription = exerciseRelease.Exercise.ShortDescription, GeneratedCode = exerciseRelease.GeneratedCode, + ReleaseId = exerciseRelease.Id, Tags = exerciseRelease.Exercise.MetaData.TagWithMetaDatas.Select(p => new TagFullBase() { Id = p.Tag.Id, @@ -844,6 +852,7 @@ namespace ClientServer.Controllers.Core.Exercises DisplayName = exerciseRelease.Exercise.DisplayName, ShortDescription = exerciseRelease.Exercise.ShortDescription, GeneratedCode = exerciseRelease.GeneratedCode, + ReleaseId = exerciseRelease.Id, Tags = exerciseRelease.Exercise.MetaData.TagWithMetaDatas.Select(p => new TagFullBase() { Id = p.Tag.Id, @@ -914,6 +923,8 @@ namespace ClientServer.Controllers.Core.Exercises } } + await GetAndSetAdditionalDataForExercisesPreviews(userId, frontendData); + await Response.WriteAsync( Jc.Serialize(new BasicResponseWithData<PaginatedData<ExercisePreviewFromBackend>>(ResponseCode.Ok, @@ -921,6 +932,158 @@ namespace ClientServer.Controllers.Core.Exercises frontendData))); } + private async Task GetAndSetAdditionalDataForExercisesPreviews(int userId, PaginatedData<ExercisePreviewFromBackend> list) + { + int[] exerciseReleaseIds = new int[list.Items.Count]; + int[] plangIds = new int[list.Items.Count]; + int[] exerciseIds = new int[list.Items.Count]; + + for (int i = 0; i < list.Items.Count; i++) + { + var data = list.Items[i]; + exerciseReleaseIds[i] = data.ReleaseId; + plangIds[i] = data.ReleasedForPLangId; + exerciseIds[i] = data.Id; + } + + foreach (var exercisePreviewFromBackend in list.Items) + { + exercisePreviewFromBackend.PassedNormalTestsCount = 0; + exercisePreviewFromBackend.MaxNormalTestsCount = 0; //passed + not passed + exercisePreviewFromBackend.SubsequentEditorPassedNormalTestsCount = null; + exercisePreviewFromBackend.HasUserSolution = false; + } + + //check if the user has a solution + var releasesWithSolutions = await _context.Solutions + .Where(p => p.UserId == userId + && plangIds.Contains(p.PLangId) + && exerciseReleaseIds.Contains( + p.ExerciseReleaseId) + ) + .Select(p => p.ExerciseReleaseId) + .ToListAsync(); + + foreach (var releasesWithSolution in releasesWithSolutions) + { + var exPreview = list.Items.Find(p => p.ReleaseId == releasesWithSolution); + + if (exPreview == null) + { + Console.WriteLine($"[ERROR] solution not found for user {userId} and release {exPreview.GeneratedCode}"); + return; + } + + exPreview.HasUserSolution = true; + } + + //if a user has not run the tests we get 0 test results but this is not the max/real test count + var testsForExercises = await _context.Tests + .Where(p => exerciseIds.Contains(p.ExerciseId) && p.IsSubmitTest == false) + .GroupBy(p => p.ExerciseId) + .Select(p => new + { + p.Key, + Count = p.Count() + }) + .ToListAsync(); + + foreach (var tuple in testsForExercises) + { + //note that the same exercise can be listed twice because of different release codes + foreach (var exercisePreview in list.Items) + { + if (exercisePreview.Id == tuple.Key) + { + exercisePreview.MaxNormalTestsCount = tuple.Count; + } + } + } + + //get the normal test results + //pk + //SolutionPLangId, SolutionUserId, SolutionExerciseReleaseId + var normalTestResults = await _context.TestWithSingleSolutionAsTestResult + .Where(p => p.SolutionUserId == userId + && plangIds.Contains(p.SolutionPLangId) + && exerciseReleaseIds.Contains(p.SolutionExerciseReleaseId) + && p.Test.IsSubmitTest == false + ) + .GroupBy(p => new {p.SolutionExerciseReleaseId, p.Passed}) + .Select(p => new + { + p.Key, + Count = p.Count(), + }) + .ToListAsync(); + + + var afterSolutionIdWithReleaseId = await _context.AfterSolutions + .Where(p => p.SolutionUserId == userId + && plangIds.Contains(p.SolutionPLangId) + && exerciseReleaseIds.Contains(p.SolutionExerciseReleaseId) + ) + .Select(p => new {p.Id, p.SolutionExerciseReleaseId}) + .ToListAsync(); + + var allAfterSolutionIds = afterSolutionIdWithReleaseId.Select(p => p.Id).ToList(); + + var afterEditorNormalTestResults = await _context.TestWithAfterSolutionAsTestResults + .Where(p => allAfterSolutionIds.Contains(p.AfterSolutionId) + && p.Test.IsSubmitTest == false + && p.Passed != null //we don't need not passed here because form normal test we know the total count + ) + .GroupBy(p => new {p.AfterSolutionId, p.Passed}) + .Select(p => new + { + p.Key, + Count = p.Count(), + }) + .ToListAsync(); + + foreach (var tuple in normalTestResults) + { + var exercisePreview = list.Items.Find(p => p.ReleaseId == tuple.Key.SolutionExerciseReleaseId); + + if (exercisePreview == null) + { + Console.WriteLine($"[ERROR] exercise preview not found for test results with release id {tuple.Key.SolutionExerciseReleaseId}"); + return; + } + + if (tuple.Key.Passed.HasValue && tuple.Key.Passed.Value) + { + exercisePreview.PassedNormalTestsCount = tuple.Count; + } + } + + foreach (var tuple in afterEditorNormalTestResults) + { + var afterSolutionIdTuple = afterSolutionIdWithReleaseId + .Find(p => p.Id == tuple.Key.AfterSolutionId); + + if (afterSolutionIdTuple == null) + { + Console.WriteLine($"[ERROR] after solution not found for test results with release id {tuple.Key}"); + return; + } + var exercisePreview = list.Items.Find(p => p.ReleaseId == afterSolutionIdTuple.SolutionExerciseReleaseId); + + if (exercisePreview == null) + { + Console.WriteLine($"[ERROR] exercise preview not found for test results with release id {afterSolutionIdTuple.SolutionExerciseReleaseId}"); + return; + } + + if (tuple.Key.Passed.HasValue) + { + //if not passed set to 0 so we know we have run after tests + exercisePreview.SubsequentEditorPassedNormalTestsCount = tuple.Key.Passed.Value ? tuple.Count : 0; + } + //if not ignore because filtered them out with where in sql + } + } + /// <summary> /// returns the groups where the user has permission to do something with the groups exercises /// almost the same as getting a single group but here the user also needs permission in the group @@ -2464,6 +2627,9 @@ namespace ClientServer.Controllers.Core.Exercises /// </summary> public string GeneratedCode { get; set; } + [JsonIgnore] + public int ReleaseId { get; set; } + /// <summary> /// true: assessment result should not count, false: not /// </summary> @@ -2534,5 +2700,37 @@ namespace ClientServer.Controllers.Core.Exercises /// true: is still released, false: not /// </summary> public bool IsReleased { get; set; } + + /// <summary> + /// count of passed tests + /// needed to know if the exercise was finished fully (info for the user) + /// if no test was executed this is 0 + /// + /// MUST BE QUERIED SEPARATELY via <see cref="ExerciseOverviewController.GetAndSetAdditionalDataForExercisesPreviews"/> + /// </summary> + public int PassedNormalTestsCount { get; set; } + /// <summary> + /// can be 0 if no test was specified... + /// + /// MUST BE QUERIED SEPARATELY via <see cref="ExerciseOverviewController.GetAndSetAdditionalDataForExercisesPreviews"/> + /// </summary> + public int MaxNormalTestsCount { get; set; } + + /// <summary> + /// same as <see cref="PassedNormalTestsCount"/> but for the subsequent editor / after solution + /// can be null if we don't have an after solution yet + /// if we have an after solution but after tests haven't been run yet, this is zero + /// + /// MUST BE QUERIED SEPARATELY via <see cref="ExerciseOverviewController.GetAndSetAdditionalDataForExercisesPreviews"/> + /// </summary> + public int? SubsequentEditorPassedNormalTestsCount { get; set; } + + /// <summary> + /// true: user has a solution, + /// false: not + /// + /// MUST BE QUERIED SEPARATELY via <see cref="ExerciseOverviewController.GetAndSetAdditionalDataForExercisesPreviews"/> + /// </summary> + public bool HasUserSolution { get; set; } } } -- GitLab