private DayProjectStats CreateBlankDayProjectStats(DateTime occurrenceDate, string projectId, string stackId = null, bool isNew = false) { bool hasEvent = !String.IsNullOrEmpty(stackId); // store stats in 15 minute buckets var bucketStats = new Dictionary <string, EventStatsWithStackIds>(); for (int i = 0; i < 1440; i += 15) { var stat = new EventStatsWithStackIds(); if (hasEvent && i == GetTimeBucket(occurrenceDate)) { stat.Total = 1; stat.NewTotal = isNew ? 1 : 0; stat.StackIds.Add(stackId, 1); if (isNew) { stat.NewStackIds.Add(stackId); } bucketStats.Add(i.ToString("0000"), stat); } } var s = new DayProjectStats { Id = GetDayProjectStatsId(projectId, occurrenceDate), ProjectId = projectId, Total = hasEvent ? 1 : 0, NewTotal = isNew ? 1 : 0, MinuteStats = bucketStats }; if (hasEvent) { s.StackIds.Add(stackId, 1); } if (isNew) { s.NewStackIds.Add(stackId); } return(s); }
public ProjectEventStatsResult GetProjectEventStatsByMinuteBlock(string projectId, TimeSpan utcOffset, DateTime?localStartDate = null, DateTime?localEndDate = null, DateTime?retentionStartDate = null, bool includeHidden = false, bool includeFixed = false, bool includeNotFound = true) { // Round date range to blocks of 15 minutes since stats are per 15 minute block. var range = GetDateRange(localStartDate, localEndDate, utcOffset, TimeSpan.FromMinutes(15)); if (range.Item1 == range.Item2) { return(new ProjectEventStatsResult()); } DateTime utcStartDate = new DateTimeOffset(range.Item1.Ticks, utcOffset).UtcDateTime; DateTime utcEndDate = new DateTimeOffset(range.Item2.Ticks, utcOffset).UtcDateTime; var results = _dayProjectStats.GetRange(GetDayProjectStatsId(projectId, utcStartDate), GetDayProjectStatsId(projectId, utcEndDate)); if (results.Count > 0) { DayProjectStats firstWithOccurrence = results.OrderBy(r => r.Id).FirstOrDefault(r => r.MinuteStats.Any(ds => ds.Value.Total > 0)); if (firstWithOccurrence != null) { DateTime firstErrorDate = firstWithOccurrence.GetDateFromMinuteStatKey(firstWithOccurrence.MinuteStats.OrderBy(ds => Int32.Parse(ds.Key)).First(ds => ds.Value.Total > 0).Key); if (utcStartDate < firstErrorDate) { utcStartDate = firstErrorDate; } } if (!includeHidden) { // remove stats from hidden doc ids. string[] hiddenIds = _stackRepository.GetHiddenIds(projectId); if (hiddenIds.Length > 0) { DecrementDayProjectStatsByStackIds(results, hiddenIds); } } if (!includeNotFound) { // remove stats from not found doc ids. string[] notFoundIds = _stackRepository.GetNotFoundIds(projectId); if (notFoundIds.Length > 0) { DecrementDayProjectStatsByStackIds(results, notFoundIds); } } if (!includeFixed) { // remove stats from not found doc ids. string[] fixedIds = _stackRepository.GetFixedIds(projectId); if (fixedIds.Length > 0) { DecrementDayProjectStatsByStackIds(results, fixedIds); } } } var dayDocDates = new List <DateTime>(); DateTime currentDay = utcStartDate; DateTime endOfDayEndDate = utcEndDate.ToEndOfDay(); while (currentDay <= endOfDayEndDate) { dayDocDates.Add(currentDay); currentDay = currentDay.AddDays(1); } // add missing day documents foreach (DateTime dayDocDate in dayDocDates) { if (!results.Any(d => d.Id == GetDayProjectStatsId(projectId, dayDocDate))) { results.Add(CreateBlankDayProjectStats(dayDocDate, projectId)); } } // fill out missing minute blocks with blank stats foreach (DayProjectStats r in results) { const int minuteBlocksInDay = 96; for (int i = 0; i <= minuteBlocksInDay - 1; i++) { int minuteBlock = i * 15; if (!r.MinuteStats.ContainsKey(minuteBlock.ToString("0000"))) { r.MinuteStats.Add(minuteBlock.ToString("0000"), new EventStatsWithStackIds()); } } } if (retentionStartDate.HasValue) { retentionStartDate = retentionStartDate.Value.Floor(TimeSpan.FromMinutes(15)); } var minuteBlocks = new List <KeyValuePair <DateTime, EventStatsWithStackIds> >(); minuteBlocks = results.Aggregate(minuteBlocks, (current, result) => current.Concat(result.MinuteStats.ToDictionary(kvp => result.GetDateFromMinuteStatKey(kvp.Key), kvp => kvp.Value)).ToList()) .Where(kvp => kvp.Key >= utcStartDate && kvp.Key <= utcEndDate).OrderBy(kvp => kvp.Key).ToList(); int totalLimitedByPlan = retentionStartDate != null && utcStartDate < retentionStartDate ? minuteBlocks.Where(kvp => kvp.Key < retentionStartDate).SelectMany(kvp => kvp.Value.StackIds.Select(s => s.Key)).Distinct() .Except(minuteBlocks.Where(kvp => kvp.Key >= retentionStartDate).SelectMany(kvp => kvp.Value.StackIds.Select(s => s.Key)).Distinct()) .Count() : 0; if (totalLimitedByPlan > 0) { minuteBlocks = minuteBlocks.Where(kvp => kvp.Key >= retentionStartDate).ToList(); } // group data points by a timespan to limit the number of returned data points TimeSpan groupTimeSpan = TimeSpan.FromMinutes(15); if (minuteBlocks.Count > 50) { DateTime first = minuteBlocks.Min(m => m.Key); DateTime last = minuteBlocks.Max(m => m.Key); TimeSpan span = last - first; groupTimeSpan = TimeSpan.FromMinutes(((int)Math.Round(span.TotalMinutes / 50 / 15.0)) * 15); } var stats = new ProjectEventStatsResult { TotalLimitedByPlan = totalLimitedByPlan, StartDate = utcStartDate, EndDate = utcEndDate, Total = minuteBlocks.Sum(kvp => kvp.Value.Total), UniqueTotal = minuteBlocks.SelectMany(kvp => kvp.Value.StackIds.Select(s => s.Key)).Distinct().Count(), NewTotal = minuteBlocks.Sum(kvp => kvp.Value.NewTotal), MostFrequent = new PlanPagedResult <EventStackResult>(minuteBlocks.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: minuteBlocks.Count), Stats = minuteBlocks.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() }; stats.ApplyTimeOffset(utcOffset); return(stats); }