public IComponent Export() { using (_logger.Scope("Exporting active entities to component.")) { var rootComponent = _factory.Create(); // Apply the active entities to the component tree. var activeEvents = _table .GetActiveEntities <EventEntity>() .ToList() .Where(e => _table .GetChildEntities <MessageEntity, EventEntity>(e) .ToList() .Any()) .ToList(); _logger.LogInformation("Found {EventCount} active events with messages.", activeEvents.Count); var activeIncidentGroups = activeEvents .SelectMany(e => _table .GetChildEntities <IncidentGroupEntity, EventEntity>(e) .Where(i => i.IsActive) .ToList()) .ToList(); _logger.LogInformation("Found {GroupCount} active incident groups linked to active events with messages.", activeIncidentGroups.Count); var activeEntities = activeIncidentGroups .Concat <IComponentAffectingEntity>(activeEvents) // Only apply entities with a non-Up status. .Where(e => e.AffectedComponentStatus != (int)ComponentStatus.Up) // If multiple events are affecting a single region, the event with the highest severity should affect the component. .GroupBy(e => e.AffectedComponentPath) .Select(g => g.OrderByDescending(e => e.AffectedComponentStatus).First()) .ToList(); _logger.LogInformation("Active entities affect {PathCount} distinct subcomponents.", activeEntities.Count); foreach (var activeEntity in activeEntities) { using (_logger.Scope("Applying active entity affecting {AffectedComponentPath} of severity {AffectedComponentStatus} at {StartTime} to root component", activeEntity.AffectedComponentPath, (ComponentStatus)activeEntity.AffectedComponentStatus, activeEntity.StartTime)) { var currentComponent = rootComponent.GetByPath(activeEntity.AffectedComponentPath); if (currentComponent == null) { throw new InvalidOperationException($"Couldn't find component with path {activeEntity.AffectedComponentPath} corresponding to active entities."); } currentComponent.Status = (ComponentStatus)activeEntity.AffectedComponentStatus; } } return(rootComponent); } }
public async Task <bool> CanBeAggregatedByAsync(ParsedIncident input, TAggregationEntity aggregationEntity) { using (_logger.Scope("Determining if entity can be linked to aggregation {AggregationRowKey}", aggregationEntity.RowKey)) { if (!_table.GetChildEntities <TChildEntity, TAggregationEntity>(aggregationEntity).ToList().Any()) { // A manually created aggregation will have no children. We cannot use an aggregation that was manually created. // It is also possible that some bug or data issue has broken this aggregation. If that is the case, we cannot use it either. _logger.LogInformation("Cannot link entity to aggregation because it is not linked to any children."); return(false); } // To guarantee that the aggregation reflects the latest information and is actually active, we must update it. await _aggregationUpdater.UpdateAsync(aggregationEntity, input.StartTime); if (!aggregationEntity.IsActive && input.IsActive) { _logger.LogInformation("Cannot link entity to aggregation because it has been deactivated and the incident has not been."); return(false); } _logger.LogInformation("Entity can be linked to aggregation."); return(true); } }
public Event Export(EventEntity eventEntity) { using (_logger.Scope("Exporting event {EventRowKey}.", eventEntity.RowKey)) { var messages = _table.GetChildEntities <MessageEntity, EventEntity>(eventEntity) .ToList() // Don't show empty messages. .Where(m => !string.IsNullOrEmpty(m.Contents)) .ToList(); _logger.LogInformation("Event has {MessageCount} messages that are not empty.", messages.Count); if (!messages.Any()) { return(null); } return(new Event( eventEntity.AffectedComponentPath, eventEntity.StartTime, eventEntity.EndTime, messages .OrderBy(m => m.Time) .Select(m => new Message(m.Time, m.Contents)))); } }
public bool CanPostMessages(IncidentGroupEntity group, DateTime cursor) { var duration = (group.EndTime ?? cursor) - group.StartTime; if (duration < _eventStartMessageDelay) { _logger.LogInformation("Incident group has not been active for longer than the messaging delay."); return(false); } var linkedIncidentsQuery = _table.GetChildEntities <IncidentEntity, IncidentGroupEntity>(group); var activeIncidents = linkedIncidentsQuery .Where(i => i.IsActive) .ToList(); var incidentsActiveAfterDelay = linkedIncidentsQuery .Where(i => i.EndTime >= group.StartTime + _eventStartMessageDelay) .ToList(); _logger.LogInformation("Incident group is linked to {ActiveIncidentsCount} active incidents and {DelayActiveIncidentsCount} incidents that were active after the messaging delay.", activeIncidents.Count, incidentsActiveAfterDelay.Count); var hasBeenActiveLongerThanDelay = activeIncidents.Any() || incidentsActiveAfterDelay.Any(); return(hasBeenActiveLongerThanDelay); }
public IEnumerable <MessageChangeEvent> Get(EventEntity eventEntity, DateTime cursor) { var linkedGroups = _table.GetChildEntities <IncidentGroupEntity, EventEntity>(eventEntity).ToList(); var events = new List <MessageChangeEvent>(); _logger.LogInformation("Event has {IncidentGroupsCount} linked incident groups.", linkedGroups.Count); foreach (var linkedGroup in linkedGroups) { using (_logger.Scope("Getting status changes from incident group {IncidentGroupRowKey}.", linkedGroup.RowKey)) { if (!_filter.CanPostMessages(linkedGroup, cursor)) { _logger.LogInformation("Incident group did not pass filter. Cannot post messages about it."); continue; } var path = linkedGroup.AffectedComponentPath; var status = (ComponentStatus)linkedGroup.AffectedComponentStatus; var startTime = linkedGroup.StartTime; _logger.LogInformation("Incident group started at {StartTime}.", startTime); events.Add(new MessageChangeEvent(startTime, path, status, MessageType.Start)); if (!linkedGroup.IsActive) { var endTime = linkedGroup.EndTime.Value; _logger.LogInformation("Incident group ended at {EndTime}.", endTime); events.Add(new MessageChangeEvent(endTime, path, status, MessageType.End)); } } } return(events); }
public async Task UpdateAsync(TAggregationEntity aggregationEntity, DateTime cursor) { aggregationEntity = aggregationEntity ?? throw new ArgumentNullException(nameof(aggregationEntity)); using (_logger.Scope("Updating aggregation {AggregationRowKey} given cursor {Cursor}.", aggregationEntity.RowKey, cursor)) { if (!aggregationEntity.IsActive) { _logger.LogInformation("Aggregation is inactive, cannot update."); return; } var hasActiveOrRecentChildren = false; var children = _table .GetChildEntities <TChildEntity, TAggregationEntity>(aggregationEntity) .ToList(); if (children.Any()) { _logger.LogInformation("Aggregation has {ChildrenCount} children. Updating each child.", children.Count); foreach (var child in children) { await _aggregatedEntityUpdater.UpdateAsync(child, cursor); hasActiveOrRecentChildren = hasActiveOrRecentChildren || child.IsActive || child.EndTime > cursor - _groupEndDelay; } } else { _logger.LogInformation("Aggregation has no children and must have been created manually, cannot update."); return; } if (!hasActiveOrRecentChildren) { _logger.LogInformation("Deactivating aggregation because its children are inactive and too old."); var lastEndTime = children.Max(i => i.EndTime.Value); aggregationEntity.EndTime = lastEndTime; await _table.ReplaceAsync(aggregationEntity); } else { _logger.LogInformation("Aggregation has active or recent children so it will not be deactivated."); } } }