}                                                                                                                                                               // string.IsNullOrEmpty ensures response is not null

        /// <summary>
        /// Computes a summary status report based on <see cref="Properties"/>, <see cref="NatureOfSystem"/>, and <see cref="Children"/>, when a node-level report is not available.
        /// </summary>
        /// <param name="includeHtmlTag">Whether or not to include the html and body tags.</param>
        /// <param name="ignoreRatingsBetterThan">A value indicating which reports to completely ignore.</param>
        /// <param name="ignorePendingRatings">Whether or not to ignore pending ratings.</param>
        /// <param name="notificationTimeZone">An optional <see cref="TimeZoneInfo"/> that will be used to convert the notification time.  If not specified, UTC will be used.</param>
        /// <returns>A <see cref="StatusAuditAlert"/> summarizing the overall state of the system.</returns>
        public StatusAuditAlert GetSummaryAlerts(bool includeHtmlTag, float ignoreRatingsBetterThan, bool ignorePendingRatings, TimeZoneInfo?notificationTimeZone = null)
        {
            DateTime start = AmbientClock.UtcNow;
            StatusResultsOrganizer organized = new StatusResultsOrganizer(this);

            organized.ComputeOverallRatingAndSort();

            DateTime notificationTime       = TimeZoneInfo.ConvertTimeFromUtc(organized.MostRecentTime, notificationTimeZone ?? TimeZoneInfo.Utc);
            StatusNotificationWriter writer = new StatusNotificationWriter(notificationTime);

            // build HTML style and header for the indicated rating and rating range
            float overallRating = organized.SortRating;

            if (includeHtmlTag)
            {
                writer.EnterHtmlAndBody(overallRating);
            }
            writer.EnterStatusRange(overallRating);
            StatusRatingRange ratingRange = StatusRating.FindRange(overallRating);

            // filter irrelevant top-level reports
            AggregatedAlert?aggregatedAlert = null;

            foreach (StatusResultsOrganizer child in organized.Children)
            {
                // use the specified child rating, or okay if one is not specified
                float childRating = child.SortRating;
                // is this one better than the cutoff?  stop now because all the subsequent reports are better than this one! (because they're sorted by rating)
                if (childRating > ignoreRatingsBetterThan || (ignorePendingRatings && float.IsNaN(childRating)))
                {
                    break;
                }
                StatusRatingRange childRatingRange = StatusRating.FindRange(childRating);
                if (childRatingRange != ratingRange)
                {
                    if (aggregatedAlert != null)
                    {
                        writer.WriteAggregatedAlert(aggregatedAlert);
                        aggregatedAlert = null;
                    }
                    writer.LeaveStatusRange();
                    writer.EnterStatusRange(childRating);
                    ratingRange = childRatingRange;
                }
                Aggregate(ref aggregatedAlert, writer, start, child, ignoreRatingsBetterThan);
            }
            if (aggregatedAlert != null)
            {
                writer.WriteAggregatedAlert(aggregatedAlert);
                aggregatedAlert = null;
            }
            writer.LeaveStatusRange();
            if (includeHtmlTag)
            {
                writer.LeaveBodyAndHtml();
            }
            StatusAuditAlert alert = new StatusAuditAlert(overallRating, string.Empty, writer.Terse, writer.Details);

            return(alert);
        }
 private void Aggregate(ref AggregatedAlert?aggregatedAlert, StatusNotificationWriter writer, DateTime start, StatusResultsOrganizer child, float ignoreRatingsBetterThan)
 {
     // does this child have children?
     if (child.ChildrenCount > 0)
     {
         System.Diagnostics.Debug.Assert(child.NatureOfSystem == StatusNatureOfSystem.ChildrenIrrelevant || child.ChildrenCount > 1);
         // recurse for this level too
         RenderTargetedResults(writer, start, child, ignoreRatingsBetterThan);
     }
     else
     {
         // can we aggregate this one?
         if (aggregatedAlert != null && aggregatedAlert.CanBeAggregated(child.Target, child.OverallReport))
         {
             aggregatedAlert.Aggregate(child.Source, child.Target, child.MostRecentTime, child.OverallReport);
         }
         else // this one can't be aggregated with previous ones, so we need to flush the previously-aggregated alerts and start a new aggregation
         {
             if (aggregatedAlert != null)
             {
                 writer.WriteAggregatedAlert(aggregatedAlert);
             }
             aggregatedAlert = new AggregatedAlert(child.Source, child.Target, child.MostRecentTime, child.OverallReport);
         }
     }
 }
        private void RenderTargetedResults(StatusNotificationWriter writer, DateTime start, StatusResultsOrganizer results, float ignoreRatingsBetterThan)
        {
            float rating = results.SortRating;

            writer.EnterTarget(results.Target, rating);
            AggregatedAlert?aggregatedAlert = null;

            foreach (StatusResultsOrganizer child in results.Children)
            {
                // is this one better than the cutoff?  stop now because all the subsequent reports are better than this one!
                if (child.OverallRating > ignoreRatingsBetterThan)
                {
                    break;
                }
                Aggregate(ref aggregatedAlert, writer, start, child, ignoreRatingsBetterThan);
            }
            if (aggregatedAlert != null)
            {
                writer.WriteAggregatedAlert(aggregatedAlert);
            }
            writer.LeaveTarget();
        }