public ProjectEventStatsResult GetProjectEventStatsByDay(string projectId, TimeSpan utcOffset, DateTime?localStartDate = null, DateTime?localEndDate = null, DateTime?retentionStartDate = null, bool includeHidden = false, bool includeFixed = false, bool includeNotFound = true) { // TODO: Check to see if we have day stats available for the project, if not, then use minute stats and queue new project offset to be created in a job // Round date range to be full days since stats are per day. var range = GetDateRange(localStartDate, localEndDate, utcOffset, TimeSpan.FromDays(1)); if (range.Item1 == range.Item2) { return(new ProjectEventStatsResult()); } localStartDate = range.Item1; localEndDate = range.Item2; var results = _monthProjectStats.GetRange(GetMonthProjectStatsId(localStartDate.Value, utcOffset, projectId), GetMonthProjectStatsId(localEndDate.Value, utcOffset, projectId)); if (results.Count > 0) { var firstWithOccurrence = results.OrderBy(r => r.Id).FirstOrDefault(r => r.DayStats.Any(ds => ds.Value.Total > 0)); if (firstWithOccurrence != null) { var firstErrorDate = firstWithOccurrence.GetDateFromDayStatKey(firstWithOccurrence.DayStats.OrderBy(ds => Int32.Parse(ds.Key)).First(ds => ds.Value.Total > 0).Key); if (localStartDate < firstErrorDate) { localStartDate = firstErrorDate; } } if (!includeHidden) { // Remove stats from hidden doc ids. var hiddenIds = _stackRepository.GetHiddenIds(projectId); if (hiddenIds.Length > 0) { DecrementMonthProjectStatsByStackIds(results, hiddenIds); } } if (!includeNotFound) { // Remove stats from not found doc ids. var notFoundIds = _stackRepository.GetNotFoundIds(projectId); if (notFoundIds.Length > 0) { DecrementMonthProjectStatsByStackIds(results, notFoundIds); } } if (!includeFixed) { // Remove stats from not found doc ids. var fixedIds = _stackRepository.GetFixedIds(projectId); if (fixedIds.Length > 0) { DecrementMonthProjectStatsByStackIds(results, fixedIds); } } } if (retentionStartDate.HasValue) { retentionStartDate = retentionStartDate.Value.Floor(TimeSpan.FromDays(1)); } // Use finer grained minute blocks if the range is less than 5 days. if (localEndDate.Value.Subtract(localStartDate.Value).TotalDays < 5) { return(GetProjectEventStatsByMinuteBlock(projectId, utcOffset, localStartDate, localEndDate, retentionStartDate, includeHidden, includeFixed, includeNotFound)); } var monthDocIds = new List <Tuple <int, int> >(); DateTime currentDay = localStartDate.Value; var endOfMonthEndDate = new DateTime(localEndDate.Value.Year, localEndDate.Value.Month, DateTime.DaysInMonth(localEndDate.Value.Year, localEndDate.Value.Month)); while (currentDay <= endOfMonthEndDate) { monthDocIds.Add(Tuple.Create(currentDay.Year, currentDay.Month)); currentDay = currentDay.AddMonths(1); } // Add missing month documents. foreach (var monthDocId in monthDocIds) { if (!results.Any(d => d.Id == GetMonthProjectStatsId(monthDocId.Item1, monthDocId.Item2, utcOffset, projectId))) { results.Add(CreateBlankMonthProjectStats(utcOffset, new DateTime(monthDocId.Item1, monthDocId.Item2, 1), projectId)); } } // Fill out missing days with blank stats. foreach (MonthProjectStats r in results) { DateTime date = r.GetDateFromDayStatKey("1"); int daysInMonth = DateTime.DaysInMonth(date.Year, date.Month); for (int i = 1; i <= daysInMonth; i++) { if (!r.DayStats.ContainsKey(i.ToString())) { r.DayStats.Add(i.ToString(), new EventStatsWithStackIds()); } } } var days = new List <KeyValuePair <DateTime, EventStatsWithStackIds> >(); days = results.Aggregate(days, (current, result) => current.Concat(result.DayStats.ToDictionary(kvp => result.GetDateFromDayStatKey(kvp.Key), kvp => kvp.Value)).ToList()) .Where(kvp => kvp.Key >= localStartDate.Value && kvp.Key <= localEndDate.Value).OrderBy(kvp => kvp.Key).ToList(); int totalLimitedByPlan = retentionStartDate != null && localStartDate < retentionStartDate ? days.Where(kvp => kvp.Key < retentionStartDate).SelectMany(kvp => kvp.Value.StackIds.Select(s => s.Key)).Distinct() .Except(days.Where(kvp => kvp.Key >= retentionStartDate).SelectMany(kvp => kvp.Value.StackIds.Select(s => s.Key)).Distinct()) .Count() : 0; if (totalLimitedByPlan > 0) { days = days.Where(kvp => kvp.Key >= retentionStartDate).ToList(); } // Group data points by a time span to limit the number of returned data points. TimeSpan groupTimeSpan = TimeSpan.FromDays(1); if (days.Count > 50) { DateTime first = days.Min(m => m.Key); DateTime last = days.Max(m => m.Key); TimeSpan span = last - first; groupTimeSpan = TimeSpan.FromDays(((int)Math.Round((span.TotalDays / 50) / 1.0)) * 1); } var stats = new ProjectEventStatsResult { TotalLimitedByPlan = totalLimitedByPlan, StartDate = localStartDate.Value, EndDate = localEndDate.Value, Total = days.Sum(kvp => kvp.Value.Total), UniqueTotal = days.SelectMany(kvp => kvp.Value.StackIds.Select(s => s.Key)).Distinct().Count(), NewTotal = days.Sum(kvp => kvp.Value.NewTotal), MostFrequent = new PlanPagedResult <EventStackResult>(days.SelectMany(kvp => kvp.Value.StackIds).GroupBy(kvp => kvp.Key) .Select(v => new EventStackResult { Id = v.Key, Total = v.Sum(kvp => kvp.Value) }).OrderByDescending(s => s.Total).ToList(), totalLimitedByPlan: totalLimitedByPlan, totalCount: days.Count), Stats = days.GroupBy(s => s.Key.Floor(groupTimeSpan)).Select(kvp => new DateProjectStatsResult { Date = kvp.Key, Total = kvp.Sum(b => b.Value.Total), NewTotal = kvp.Sum(b => b.Value.NewTotal), UniqueTotal = kvp.Select(b => b.Value.StackIds).Aggregate((current, result) => Merge(result, current)).Count() }).ToList() }; return(stats); }