diff --git a/src/ClientServer/Config/Constants.cs b/src/ClientServer/Config/Constants.cs
index 9fb6e9679cbaac773b1366ea5617643eabd07c4f..b948e48805f86da263deab604257d6e2305a797c 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 20458d85902638fa41f8f06644cfcb5d40599401..72537ba55f209ba72dc00576158aed89a2965bf0 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; }
     }
 }