/// <summary> /// Gets all information needed and merges the data through the Lava /// template to get the final content to be displayed. /// </summary> /// <param name="rockContext">The rock context.</param> /// <param name="filter">The filter options.</param> /// <returns>The content to be displayed by the client.</returns> private string GetResultContent(RockContext rockContext, GroupFinderFilter filter) { var mergeFields = RequestContext.GetCommonMergeFields(); var groups = GetGroups(rockContext, filter) .Include(g => g.GroupLocations.Select(gl => gl.Location)) .ToList(); // If they provided a location then attempt to filter and sort by // distance. if (filter.Location?.Latitude != null && filter.Location?.Longitude != null) { var distances = GetDistances(groups, GroupTypesLocationType, filter.Location.Latitude.Value, filter.Location.Longitude.Value); // Only show groups with a known location, and sort those by distance. // Filtering is done to keep in parity with the Web version. groups = groups.Where(a => distances.ContainsKey(a.Id)) .OrderBy(a => distances[a.Id]) .ThenBy(a => a.Name) .ToList(); mergeFields.AddOrReplace("Distances", distances); } if (MaxResults > 0) { groups = groups.Take(MaxResults).ToList(); } mergeFields.AddOrReplace("DetailPage", DetailPageGuid); mergeFields.AddOrReplace("Groups", groups); return(Template.ResolveMergeFields(mergeFields)); }
public BlockActionResult GetGroups(GroupFinderFilter filter) { using (var rockContext = new RockContext()) { return(ActionOk(new GetGroupsResult { Content = GetResultContent(rockContext, filter) })); } }
/// <summary> /// Gets the groups that match the filter options. /// </summary> /// <param name="rockContext">The rock context.</param> /// <param name="filter">The filter options.</param> /// <returns>A queryable of matching groups.</returns> private IQueryable <Group> GetGroups(RockContext rockContext, GroupFinderFilter filter) { var groupService = new GroupService(rockContext); var validAttributes = GetValidGroupAttributes(rockContext); if (!GroupTypeGuids.Any()) { return(groupService.Queryable().Where(g => false)); } var groupTypeGuids = GroupTypeGuids; var daysOfWeek = filter.DayOfWeek.HasValue ? new List <DayOfWeek> { filter.DayOfWeek.Value } : null; var timePeriodsOfDay = filter.TimePeriodOfDay.HasValue ? new List <TimePeriodOfDay> { filter.TimePeriodOfDay.Value } : null; var campuses = filter.CampusGuid.HasValue ? new List <Guid> { filter.CampusGuid.Value } : null; var requiredSpotsAvailable = HideOvercapacityGroups ? ( int? )1 : null; var attributeFilters = filter.Attributes .Select(f => new { Attribute = validAttributes.FirstOrDefault(a => a.Key == f.Key), f.Value }) .Where(a => a.Attribute != null && a.Value != null) .ToDictionary(a => a.Attribute, a => a.Value); // -- Everything below this line is common logic that can be moved to // the service layer. // Initial query only includes active and public groups. var groupQry = groupService.Queryable() .Include(g => g.GroupLocations.Select(l => l.Location)) .Where(g => g.IsActive && g.IsPublic); // If any group types were specified then filter to only groups // that match one of the group types. if (groupTypeGuids != null && groupTypeGuids.Any()) { groupQry = groupQry.Where(g => groupTypeGuids.Contains(g.GroupType.Guid)); } // If any days of the week were specified then filter to only groups // that match one of those days of the week. if (daysOfWeek != null && daysOfWeek.Any()) { groupQry = groupQry .Where(g => g.Schedule.WeeklyDayOfWeek.HasValue && daysOfWeek.Contains(g.Schedule.WeeklyDayOfWeek.Value)); } // If any time periods were specified then filter to only groups // that match one of those time periods. if (timePeriodsOfDay != null && timePeriodsOfDay.Any()) { groupQry = groupQry.WhereTimePeriodIsOneOf(timePeriodsOfDay, g => g.Schedule.WeeklyTimeOfDay.Value); } // If any campuses were specified then filter to only groups // that belong to one of those campuses. if (campuses != null && campuses.Any()) { groupQry = groupQry.Where(g => campuses.Contains(g.Campus.Guid)); } // If it has been requested to hide groups that are already over // capacity then do so. if (requiredSpotsAvailable.HasValue) { // Filter by the overall group size. groupQry = groupQry.Where(g => !g.GroupCapacity.HasValue || g.GroupType.GroupCapacityRule == GroupCapacityRule.None || (g.Members.Where(m => m.GroupMemberStatus == GroupMemberStatus.Active).Count() + requiredSpotsAvailable.Value) <= g.GroupCapacity); // Filter by the default role that new members would be placed into. groupQry = groupQry.Where(g => g.GroupType == null || g.GroupType.GroupCapacityRule == GroupCapacityRule.None || g.GroupType.DefaultGroupRole == null || g.GroupType.DefaultGroupRole.MaxCount == null || (g.Members.Where(m => m.GroupRoleId == g.GroupType.DefaultGroupRoleId && m.GroupMemberStatus == GroupMemberStatus.Active).Count() + requiredSpotsAvailable.Value) <= g.GroupType.DefaultGroupRole.MaxCount); } // Filter query by any configured attribute filters if (attributeFilters != null && attributeFilters.Any()) { var processedAttributes = new HashSet <string>(); /* * 07/01/2021 - MSB * * This section of code creates an expression for each attribute in the search. The attributes from the same * Group Type get grouped and &&'d together. Then the grouped Expressions will get ||'d together so that results * will be returned across Group Types. * * If we don't do this, when the Admin adds attributes from two different Group Types and then the user enters data * for both attributes they would get no results because Attribute A from Group Type A doesn't exists in Group Type B. * * Reason: Queries across Group Types */ var filters = new Dictionary <string, Expression>(); var parameterExpression = groupService.ParameterExpression; foreach (var attributeFilter in attributeFilters) { var attribute = attributeFilter.Key; var values = attributeFilter.Value; var queryKey = $"{attribute.EntityTypeQualifierColumn}_{attribute.EntityTypeQualifierValue}"; Expression leftExpression = null; if (filters.ContainsKey(queryKey)) { leftExpression = filters[queryKey]; } var expression = Rock.Utility.ExpressionHelper.BuildExpressionFromFieldType <Group>(values.ToList(), attribute, groupService, parameterExpression); if (expression != null) { if (leftExpression == null) { filters[queryKey] = expression; } else { filters[queryKey] = Expression.And(leftExpression, expression); } } } // If we have a single filter group then just filter by the // expression. If we have more than one then OR each group // together and apply the resulting expression. if (filters.Count == 1) { groupQry = groupQry.Where(parameterExpression, filters.FirstOrDefault().Value); } else if (filters.Count > 1) { var keys = filters.Keys.ToList(); var expression = filters[keys[0]]; for (var i = 1; i < filters.Count; i++) { expression = Expression.Or(expression, filters[keys[i]]); } groupQry = groupQry.Where(parameterExpression, expression); } } return(groupQry); }