public ActionResult UnitStatistics(UnitStatisticsParams param) { const int usersLimit = 200; var courseId = param.CourseId; var unitId = param.UnitId; var periodStart = param.PeriodStartDate; var periodFinish = param.PeriodFinishDate; var groupsIds = Request.GetMultipleValues("group"); var realPeriodFinish = periodFinish.Add(TimeSpan.FromDays(1)); var course = courseManager.GetCourse(courseId); if (!unitId.HasValue) { return(View("UnitStatisticsList", new UnitStatisticPageModel { Course = course, Units = course.Units, })); } var selectedUnit = course.GetUnitById(unitId.Value); var slides = selectedUnit.Slides; var slidesIds = slides.Select(s => s.Id).ToList(); var quizzes = slides.OfType <QuizSlide>(); var exersices = slides.OfType <ExerciseSlide>(); var groups = groupsRepo.GetAvailableForUserGroups(courseId, User); var filterOptions = ControllerUtils.GetFilterOptionsByGroup <VisitsFilterOptions>(groupsRepo, User, courseId, groupsIds); filterOptions.SlidesIds = slidesIds; filterOptions.PeriodStart = periodStart; filterOptions.PeriodFinish = realPeriodFinish; /* Dictionary<SlideId, List<Visit>> */ var slidesVisits = visitsRepo.GetVisitsInPeriodForEachSlide(filterOptions); var usersVisitedAllSlidesBeforePeriodCount = visitsRepo.GetUsersVisitedAllSlides(filterOptions.WithPeriodStart(DateTime.MinValue).WithPeriodFinish(periodStart)).Count(); var usersVisitedAllSlidesInPeriodCount = visitsRepo.GetUsersVisitedAllSlides(filterOptions).Count(); var usersVisitedAllSlidesBeforePeriodFinishedCount = visitsRepo.GetUsersVisitedAllSlides(filterOptions.WithPeriodStart(DateTime.MinValue)).Count(); var quizzesAverageScore = quizzes.ToDictionary(q => q.Id, q => (int)slidesVisits.GetOrDefault(q.Id, new List <Visit>()) .Where(v => v.IsPassed) .Select(v => 100 * Math.Min(v.Score, q.MaxScore) / (q.MaxScore != 0 ? q.MaxScore : 1)) .DefaultIfEmpty(-1) .Average() ); /* Dictionary<SlideId, count (distinct by user)> */ var exercisesSolutionsCount = userSolutionsRepo.GetAllSubmissions(courseId, slidesIds, periodStart, realPeriodFinish) .GroupBy(s => s.SlideId) .ToDictionary(g => g.Key, g => g.DistinctBy(s => s.UserId).Count()); var exercisesAcceptedSolutionsCount = userSolutionsRepo.GetAllAcceptedSubmissions(courseId, slidesIds, periodStart, realPeriodFinish) .GroupBy(s => s.SlideId) .ToDictionary(g => g.Key, g => g.DistinctBy(s => s.UserId).Count()); var usersIds = visitsRepo.GetVisitsInPeriod(filterOptions).DistinctBy(v => v.UserId).Select(v => v.UserId); /* If we filtered out users from one or several groups show them all */ if (filterOptions.UsersIds != null && !filterOptions.IsUserIdsSupplement) { usersIds = filterOptions.UsersIds; } var visitedUsers = usersIds .Join(db.Users, v => v, u => u.Id, (v, u) => new UnitStatisticUserInfo { UserId = u.Id, UserName = u.UserName, UserVisibleName = (u.LastName + u.FirstName != "" ? u.LastName + " " + u.FirstName : u.UserName).Trim() }) .ToList(); var isMore = visitedUsers.Count > usersLimit; var visitedSlidesCountByUser = visitsRepo.GetVisitsInPeriod(filterOptions) .GroupBy(v => v.UserId) .ToDictionary(g => g.Key, g => g.Count()); var visitedSlidesCountByUserAllTime = visitsRepo.GetVisitsInPeriod(filterOptions.WithPeriodStart(DateTime.MinValue).WithPeriodFinish(DateTime.MaxValue)) .GroupBy(v => v.UserId) .ToDictionary(g => g.Key, g => g.Count()); /* Get `usersLimit` best by slides count and order them by name */ visitedUsers = visitedUsers .OrderByDescending(u => visitedSlidesCountByUserAllTime.GetOrDefault(u.UserId, 0)) .Take(usersLimit) .OrderBy(u => u.UserVisibleName) .ToList(); var visitedUsersIds = visitedUsers.Select(v => v.UserId).ToList(); var additionalScores = additionalScoresRepo .GetAdditionalScoresForUsers(courseId, unitId.Value, visitedUsersIds) .ToDictionary(kv => kv.Key, kv => kv.Value.Score); var usersGroupsIds = groupsRepo.GetUsersGroupsIds(courseId, visitedUsersIds); var enabledAdditionalScoringGroupsForGroups = groupsRepo.GetEnabledAdditionalScoringGroups(courseId) .GroupBy(e => e.GroupId) .ToDictionary(g => g.Key, g => g.Select(e => e.ScoringGroupId).ToList()); var model = new UnitStatisticPageModel { Course = course, Units = course.Units, Unit = selectedUnit, SelectedGroupsIds = groupsIds, Groups = groups, PeriodStart = periodStart, PeriodFinish = periodFinish, Slides = slides, SlidesVisits = slidesVisits, UsersVisitedAllSlidesBeforePeriodCount = usersVisitedAllSlidesBeforePeriodCount, UsersVisitedAllSlidesInPeriodCount = usersVisitedAllSlidesInPeriodCount, UsersVisitedAllSlidesBeforePeriodFinishedCount = usersVisitedAllSlidesBeforePeriodFinishedCount, QuizzesAverageScore = quizzesAverageScore, ExercisesSolutionsCount = exercisesSolutionsCount, ExercisesAcceptedSolutionsCount = exercisesAcceptedSolutionsCount, VisitedUsers = visitedUsers, VisitedUsersIsMore = isMore, VisitedSlidesCountByUser = visitedSlidesCountByUser, VisitedSlidesCountByUserAllTime = visitedSlidesCountByUserAllTime, AdditionalScores = additionalScores, UsersGroupsIds = usersGroupsIds, EnabledAdditionalScoringGroupsForGroups = enabledAdditionalScoringGroupsForGroups, }; return(View(model)); }
private StudentSubmissionsModel GetStudentSubmissionsModel(string courseId, Guid slideId, string name) { const int maxUsersCount = 30; var course = courseManager.GetCourse(courseId); var slide = course.GetSlideById(slideId) as ExerciseSlide; if (slide == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } var canViewAllSubmissions = User.HasAccessFor(courseId, CourseRole.CourseAdmin) || User.HasCourseAccess(courseId, CourseAccessType.ViewAllStudentsSubmissions); var hasFilterByName = !string.IsNullOrEmpty(name); /* By default show members of `my` groups, but if filter is enabled course admin's and users with special access can view any student's submissions */ SubmissionsFilterOptions filterOptions; var slideIdInList = new List <Guid> { slideId }; var visitedUserIds = visitsRepo.GetVisitsInPeriod(new VisitsFilterOptions { CourseId = courseId, SlidesIds = slideIdInList, PeriodStart = DateTime.MinValue, PeriodFinish = DateTime.MaxValue }) .Select(v => v.UserId) .ToList(); if (hasFilterByName && canViewAllSubmissions) { /* Get all members who has visits to this slide */ filterOptions = new SubmissionsFilterOptions { CourseId = courseId, UserIds = visitedUserIds, SlideIds = slideIdInList, }; } else { /* Get members of `my` groups */ filterOptions = ControllerUtils.GetFilterOptionsByGroup <SubmissionsFilterOptions>(groupsRepo, User, courseId, groupsIds: new List <string>()); filterOptions.SlideIds = slideIdInList; /* Filter out only users with visits to this slide */ filterOptions.UserIds = filterOptions.UserIds.Intersect(visitedUserIds).ToList(); } if (hasFilterByName) { var filteredUserIds = usersRepo.FilterUsersByNamePrefix(name); filterOptions.UserIds = filterOptions.UserIds.Intersect(filteredUserIds).ToList(); } filterOptions.UserIds = filterOptions.UserIds.Take(maxUsersCount).ToList(); var submissions = userSolutionsRepo.GetAllSubmissionsByUsers(filterOptions); var submissionsByUser = submissions.GroupBy(s => s.UserId).ToDictionary(g => g.Key, g => g.ToList()).ToDefaultDictionary(); var automaticCheckingScores = slideCheckingsRepo.GetAutomaticScoresForSlide(courseId, slideId, filterOptions.UserIds); var manualCheckingScores = slideCheckingsRepo.GetManualScoresForSlide(courseId, slideId, filterOptions.UserIds); var userGroups = groupsRepo.GetUsersGroupsNamesAsStrings(courseId, filterOptions.UserIds, User).ToDefaultDictionary(); return(new StudentSubmissionsModel { CourseId = courseId, Slide = slide, Users = usersRepo.GetUsersByIds(filterOptions.UserIds).ToDictionary(u => u.Id), SubmissionsByUser = submissionsByUser, AutomaticCheckingScores = automaticCheckingScores, ManualCheckingScores = manualCheckingScores, HasFilterByName = hasFilterByName, UserGroups = userGroups, }); }
/*TODO: extract copy-paste */ private CourseStatisticPageModel GetCourseStatisticsModel(CourseStatisticsParams param, int usersLimit) { var courseId = param.CourseId; var periodStart = param.PeriodStartDate; var periodFinish = param.PeriodFinishDate; var groupsIds = Request.GetMultipleValues("group"); var isInstructor = User.HasAccessFor(courseId, CourseRole.Instructor); var isStudent = !isInstructor; var currentUserId = User.Identity.GetUserId(); if (isStudent && !CanStudentViewGroupsStatistics(currentUserId, groupsIds)) { return(null); } var realPeriodFinish = periodFinish.Add(TimeSpan.FromDays(1)); var course = courseManager.GetCourse(courseId); var slidesIds = course.Slides.Select(s => s.Id).ToList(); var filterOptions = ControllerUtils.GetFilterOptionsByGroup <VisitsFilterOptions>(groupsRepo, User, courseId, groupsIds, allowSeeGroupForAnyMember: true); filterOptions.SlidesIds = slidesIds; filterOptions.PeriodStart = periodStart; filterOptions.PeriodFinish = realPeriodFinish; var usersIds = visitsRepo.GetVisitsInPeriod(filterOptions).DistinctBy(v => v.UserId).Select(v => v.UserId); /* If we filtered out users from one or several groups show them all */ if (filterOptions.UsersIds != null && !filterOptions.IsUserIdsSupplement) { usersIds = filterOptions.UsersIds; } var visitedUsers = usersIds .Join(db.Users, v => v, u => u.Id, (v, u) => new UnitStatisticUserInfo { UserId = u.Id, UserName = u.UserName, UserVisibleName = (u.LastName + u.FirstName != "" ? u.LastName + " " + u.FirstName : u.UserName).Trim() }) .ToList(); var isMore = visitedUsers.Count > usersLimit; var unitBySlide = course.Units.SelectMany(u => u.Slides.Select(s => Tuple.Create(u.Id, s.Id))).ToDictionary(p => p.Item2, p => p.Item1); var scoringGroups = course.Settings.Scoring.Groups; var visitedSlidesCountByUserAllTime = visitsRepo.GetVisitsInPeriod(filterOptions.WithPeriodStart(DateTime.MinValue).WithPeriodFinish(DateTime.MaxValue)) .GroupBy(v => v.UserId) .ToDictionary(g => g.Key, g => g.Count()); /* Get `usersLimit` best by slides count */ visitedUsers = visitedUsers .OrderByDescending(u => visitedSlidesCountByUserAllTime.GetOrDefault(u.UserId, 0)) .Take(usersLimit) .ToList(); var visitedUsersIds = visitedUsers.Select(v => v.UserId).ToList(); var visitedUsersGroups = groupsRepo.GetUsersGroupsIds(new List <string> { courseId }, visitedUsersIds, User, 10).ToDefaultDictionary(); /* From now fetch only filtered users' statistics */ filterOptions.UsersIds = visitedUsersIds; filterOptions.IsUserIdsSupplement = false; var scoreByUserUnitScoringGroup = ((IEnumerable <Visit>)visitsRepo.GetVisitsInPeriod(filterOptions)) .GroupBy(v => Tuple.Create(v.UserId, unitBySlide[v.SlideId], course.FindSlideById(v.SlideId)?.ScoringGroup)) .ToDictionary(g => g.Key, g => g.Sum(v => v.Score)) .ToDefaultDictionary(); var shouldBeSolvedSlides = course.Slides.Where(s => s.ShouldBeSolved).ToList(); var shouldBeSolvedSlidesIds = shouldBeSolvedSlides.Select(s => s.Id).ToList(); var shouldBeSolvedSlidesByUnitScoringGroup = shouldBeSolvedSlides .GroupBy(s => Tuple.Create(unitBySlide[s.Id], s.ScoringGroup)) .ToDictionary(g => g.Key, g => g.ToList()) .ToDefaultDictionary(); var scoreByUserAndSlide = ((IEnumerable <Visit>)visitsRepo.GetVisitsInPeriod(filterOptions.WithSlidesIds(shouldBeSolvedSlidesIds))) .GroupBy(v => Tuple.Create(v.UserId, v.SlideId)) .ToDictionary(g => g.Key, g => g.Sum(v => v.Score)) .ToDefaultDictionary(); var additionalScores = additionalScoresRepo .GetAdditionalScoresForUsers(courseId, visitedUsersIds) .ToDictionary(kv => kv.Key, kv => kv.Value.Score) .ToDefaultDictionary(); var usersGroupsIds = groupsRepo.GetUsersGroupsIds(courseId, visitedUsersIds); var enabledAdditionalScoringGroupsForGroups = groupsRepo.GetEnabledAdditionalScoringGroups(courseId) .GroupBy(e => e.GroupId) .ToDictionary(g => g.Key, g => g.Select(e => e.ScoringGroupId).ToList()); /* Filter out only scoring groups which are affected in selected groups */ var additionalScoringGroupsForFilteredGroups = ControllerUtils.GetEnabledAdditionalScoringGroupsForGroups(groupsRepo, course, groupsIds, User); scoringGroups = scoringGroups .Where(kv => kv.Value.MaxNotAdditionalScore > 0 || additionalScoringGroupsForFilteredGroups.Contains(kv.Key)) .ToDictionary(kv => kv.Key, kv => kv.Value) .ToSortedDictionary(); var groups = groupsRepo.GetAvailableForUserGroups(courseId, User); if (!isInstructor) { groups = groupsRepo.GetUserGroups(courseId, currentUserId); } var model = new CourseStatisticPageModel { IsInstructor = isInstructor, Course = course, SelectedGroupsIds = groupsIds, Groups = groups, PeriodStart = periodStart, PeriodFinish = periodFinish, VisitedUsers = visitedUsers, VisitedUsersIsMore = isMore, VisitedUsersGroups = visitedUsersGroups, ShouldBeSolvedSlidesByUnitScoringGroup = shouldBeSolvedSlidesByUnitScoringGroup, ScoringGroups = scoringGroups, ScoreByUserUnitScoringGroup = scoreByUserUnitScoringGroup, ScoreByUserAndSlide = scoreByUserAndSlide, AdditionalScores = additionalScores, UsersGroupsIds = usersGroupsIds, EnabledAdditionalScoringGroupsForGroups = enabledAdditionalScoringGroupsForGroups, }; return(model); }
/* Copy&paste from AdminController */ private ManualCheckingQueueFilterOptions GetManualCheckingFilterOptionsByGroup(string courseId, List <string> groupsIds) { return(ControllerUtils.GetFilterOptionsByGroup <ManualCheckingQueueFilterOptions>(groupsRepo, User, courseId, groupsIds)); }