< Summary

Information
Class: AmbientServices.StatusNotificationWriter
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Status/StatusNotificationWriter.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 123
Uncovered lines: 0
Coverable lines: 123
Total lines: 268
Line coverage: 100%
Branch coverage
100%
Covered branches: 46
Total branches: 46
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
RenderLevelString(...)100%22100%
RenderTerse(...)100%11100%
RenderDetails(...)100%11100%
get_Details()100%11100%
get_Terse()100%11100%
.ctor(...)100%22100%
OpenHeader(...)100%66100%
CloseHeader(...)100%22100%
EnterTabLevel()100%11100%
LeaveTabLevel()100%11100%
EnterHtmlAndBody(...)100%22100%
LeaveBodyAndHtml()100%11100%
EnterStatusRange(...)100%44100%
LeaveStatusRange()100%22100%
EnterTarget(...)100%22100%
LeaveTarget()100%22100%
WriteAggregatedAlert(...)100%2222100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Status/StatusNotificationWriter.cs

#LineLine coverage
 1using AmbientServices.Extensions;
 2using System;
 3using System.Collections.Generic;
 4using System.Text;
 5
 6namespace AmbientServices;
 7
 8/// <summary>
 9/// A class that manages writing status notifications.
 10/// </summary>
 11/// <remarks>
 12/// <pitch>Renders an organized status tree into two notifications at once: a terse plain-text form fit for SMS and a de
 13/// <pledge>
 14/// Callers drive it as a strictly-nested protocol: optionally enter html/body, then enter a status range, then optional
 15/// <see cref="Terse"/> and <see cref="Details"/> may be read at any point and reflect everything written so far.  The n
 16/// </pledge>
 17/// <plan>
 18/// Two <see cref="StringBuilder"/>s built in parallel: the terse form uses indentation and the <see cref="StatusRating"
 19/// </plan>
 20/// </remarks>
 21internal class StatusNotificationWriter
 22{
 23#if RAWRATINGS
 24    private const string DebugRatingFloatFormat = "0.00";
 25#endif
 26    private const string HtmlTabStart = "<div style=\"margin-left:10px\">";
 27    private const string HtmlTabEnd = "</div>";
 228    private static readonly string[] RenderLevelStrings = new string[] { "h0", "h1", "h2", "h3", "h4" };    // note that
 29    private static string RenderLevelString(int level)
 30    {
 231        return (level >= RenderLevelStrings.Length) ? "div" : RenderLevelStrings[level];
 32    }
 233    private static string RenderTerse(string terse) { return terse; }
 234    private static string RenderDetails(string details) { return details; }
 35
 36    private readonly StringBuilder _details;
 37    private readonly StringBuilder _terse;
 38    private int _tabLevel;
 39    private StatusRatingRange _currentSectionRatingRange;
 40    private DateTime? _notificationTime;
 41
 42    /// <summary>
 43    /// Get the detailed notification (so far).
 44    /// </summary>
 245    public string Details => _details.ToString();
 46    /// <summary>
 47    /// Get the terse notification (so far).
 48    /// </summary>
 249    public string Terse => _terse.ToString();
 50
 51    /// <summary>
 52    /// Constructs a status notification writer with the specified notification time.
 53    /// </summary>
 54    /// <param name="notificationTime">The notification time.  Defaults to the current time.</param>
 255    public StatusNotificationWriter(DateTime? notificationTime = null)
 56    {
 257        _tabLevel = 1;
 258        _currentSectionRatingRange = (StatusRatingRange)(-1);
 259        _details = new StringBuilder();
 260        _terse = new StringBuilder();
 261        _notificationTime = notificationTime ?? AmbientClock.UtcNow;
 262    }
 63
 64    private static void OpenHeader(StringBuilder details, StringBuilder terse, int tabLevel, StatusRatingRange range = (
 65    {
 266        if (terse != null)
 67        {
 268            terse.Append(new string(' ', tabLevel - 1));
 269            if (range != (StatusRatingRange)(-1))
 70            {
 271                string symbolPrefix = StatusRating.GetRangeSymbol(range) + " ";
 272                terse.Append(symbolPrefix);
 73            }
 74        }
 275        details.Append('<');
 276        details.Append(RenderLevelString(tabLevel));
 277        if (!string.IsNullOrEmpty(color)) details.Append(" style=\"color:" + color + "\"");
 278        details.Append('>');
 279    }
 80    private static void CloseHeader(StringBuilder details, StringBuilder terse, int tabLevel)
 81    {
 282        if (terse != null) terse.AppendLine();
 283        details.Append("</");
 284        details.Append(RenderLevelString(tabLevel));
 285        details.Append('>');
 286    }
 87    private void EnterTabLevel()
 88    {
 289        _details.Append(HtmlTabStart);
 290        ++_tabLevel;
 291    }
 92    private void LeaveTabLevel()
 93    {
 294        --_tabLevel;
 295        _details.Append(HtmlTabEnd);
 296    }
 97    /// <summary>
 98    /// Enters the html and body elements, with a background color set if the overall rating is specified.
 99    /// Should be matched by a subsequent call to <see cref="LeaveBodyAndHtml"/>.
 100    /// </summary>
 101    /// <param name="overallRating">The overall rating to use for the background color.</param>
 102    public void EnterHtmlAndBody(float? overallRating = null)
 103    {
 2104        _details.Append(
 2105@"<html>
 2106    <head>");
 2107        if (overallRating != null)
 108        {
 2109            string backgroundColor = StatusRating.GetRangeBackgroundColor(overallRating.Value);
 2110            _details.Append(@"
 2111    <style>");
 2112            _details.Append(StatusRating.StyleDefinition);
 2113            _details.Append(@"
 2114    body{background-color:" + backgroundColor + @";}
 2115    </style>");
 116        }
 2117        _details.Append(@"
 2118    </head>
 2119    <body>
 2120");
 2121    }
 122    /// <summary>
 123    /// Leaves the body and html tags entered in a previous call to <see cref="EnterHtmlAndBody"/>.
 124    /// </summary>
 125    public void LeaveBodyAndHtml()
 126    {
 2127        _details.Append(@"
 2128    </body>
 2129</html>
 2130");
 2131    }
 132    /// <summary>
 133    /// Enters a status range section for the range indicated by the rating.
 134    /// Should be matched by a subsequent call to <see cref="LeaveStatusRange"/>.
 135    /// </summary>
 136    /// <param name="rating">The status rating.</param>
 137    public void EnterStatusRange(float rating)
 138    {
 2139        StatusRatingRange range = StatusRating.FindRange(rating);
 2140        string rangeName = StatusRating.GetRangeName(range);
 2141        string rangeColor = StatusRating.GetRangeForegroundColor(range);
 142
 2143        if (_tabLevel > 1) throw new InvalidOperationException("All targets and status ranges must be closed before ente
 144
 2145        _details.Append("<div class=\"");
 146#pragma warning disable CA1308 // this is to convert the range name from the C# style casing (Pascal) to the HTML style 
 2147        _details.Append(rangeName.ToLowerInvariant());
 148#pragma warning restore CA1308
 2149        _details.Append("-range\">");
 150
 2151        OpenHeader(_details, _terse, _tabLevel, range, rangeColor);
 2152        _currentSectionRatingRange = range;
 153
 2154        _terse.Append(rangeName.ToUpperInvariant());
 2155        _details.Append(StatusRating.GetRangeSymbol(range));
 2156        _details.Append(' ');
 2157        _details.Append(rangeName);
 158
 2159        if (_notificationTime != null)
 160        {
 2161            _terse.Append(" @");
 2162            _terse.Append(_notificationTime.Value.ToShortTimeString());
 2163            _details.Append(" at ");
 2164            _details.Append(_notificationTime.Value.ToLongTimeString());
 2165            _notificationTime = null;
 166        }
 167
 2168        CloseHeader(_details, _terse, _tabLevel);
 169
 2170        EnterTabLevel();
 2171    }
 172    /// <summary>
 173    /// Leaves a status range started by a call to <see cref="EnterStatusRange"/>.
 174    /// </summary>
 175    public void LeaveStatusRange()
 176    {
 2177        if (_tabLevel != 2) throw new InvalidOperationException("There is no status range to leave!");
 2178        LeaveTabLevel();
 2179        _details.Append("</div>");
 2180    }
 181    /// <summary>
 182    /// Enters a target section.  Should be matched by a subsequent call to <see cref="LeaveTarget"/>.
 183    /// </summary>
 184    /// <param name="target">The name of the target owning this section.</param>
 185    /// <param name="rating">The overall status rating for this target.</param>
 186    public void EnterTarget(string target, float rating)
 187    {
 2188        if (_tabLevel < 2) throw new InvalidOperationException("A status range must be entered before a target is!");
 189
 2190        string rgbColor = StatusRating.GetRatingRgbForegroundColor(rating);
 2191        StatusRatingRange range = StatusRating.FindRange(rating);
 2192        OpenHeader(_details, _terse, _tabLevel, (StatusRatingRange)(-1), rgbColor);
 2193        _terse.Append(target);
 2194        _details.Append(target);
 2195        CloseHeader(_details, _terse, _tabLevel);
 2196        EnterTabLevel();
 2197    }
 198
 199    /// <summary>
 200    /// Leaves a target section entered by a previous call to <see cref="EnterTarget"/>.
 201    /// </summary>
 202    public void LeaveTarget()
 203    {
 2204        if (_tabLevel < 3) throw new InvalidOperationException("There is no target to leave!");
 205
 2206        LeaveTabLevel();
 2207    }
 208    /// <summary>
 209    /// Writes a notification for the specified <see cref="AggregatedAlert"/>.
 210    /// </summary>
 211    /// <param name="aggregatedAlert">An <see cref="AggregatedAlert"/> that should be written to the notification.</para
 212    public void WriteAggregatedAlert(AggregatedAlert aggregatedAlert)
 213    {
 2214        if (_tabLevel <= 1) throw new InvalidOperationException("A status range must be entered before aggregated alerts
 215
 2216        StatusAuditAlert auditAlert = aggregatedAlert.CommonAlert ?? StatusAuditAlert.None;
 2217        StatusRatingRange range = StatusRating.FindRange(auditAlert.Rating);
 2218        string rangeName = StatusRating.GetRangeName(range);
 2219        string rangeColor = StatusRating.GetRangeForegroundColor(auditAlert.Rating);
 2220        string rgbColor = StatusRating.GetRatingRgbForegroundColor(aggregatedAlert.RatingSum / aggregatedAlert.Sources.C
 221
 2222        List<StatusPropertyRange> propertyRanges = aggregatedAlert.PropertyRanges;
 223
 2224        OpenHeader(_details, _terse, _tabLevel, (StatusRatingRange)(-1), rgbColor);
 225
 2226        _terse.Append(aggregatedAlert.Target + ": " + aggregatedAlert.TerseSources + "->");
 227
 228#if RAWRATINGS
 229        _details.Append("[RATING=" + auditAlert.Rating.ToString(DebugRatingFloatFormat) + "] ");
 230#endif
 2231        _details.Append(aggregatedAlert.Target + ": " + aggregatedAlert.DetailsSources + " reporting: ");
 232
 233        // multi-line alert details or properties to list?
 2234        if ((aggregatedAlert.CommonAlert?.Details != null && (aggregatedAlert.CommonAlert.Details.Contains("<br/>", Stri
 235        {
 2236            CloseHeader(_details, _terse, _tabLevel);
 237
 2238            EnterTabLevel();
 2239            OpenHeader(_details, _terse, _tabLevel, (StatusRatingRange)(-1), rgbColor);
 240
 2241            _terse.Append(RenderTerse(auditAlert.Terse).Replace("\n", "\n" + new string(' ', _tabLevel), StringCompariso
 2242            _details.AppendLine(RenderDetails(auditAlert.Details));
 243
 2244            CloseHeader(_details, _terse, _tabLevel);
 245
 246            // are there properties?
 2247            if (propertyRanges != null && propertyRanges.Count > 0)
 248            {
 2249                _details.Append(" because");
 2250                foreach (StatusPropertyRange propertyRange in aggregatedAlert.PropertyRanges)
 251                {
 2252                    OpenHeader(_details, _terse, _tabLevel, (StatusRatingRange)(-1), rgbColor);
 2253                    _terse.Append(propertyRange.ToString());
 2254                    _details.AppendLine(propertyRange.ToString());
 2255                    CloseHeader(_details, _terse, _tabLevel);
 256                }
 257            }
 2258            LeaveTabLevel();
 259        }
 260        else
 261        {
 2262            _terse.Append(RenderTerse(auditAlert.Terse));
 2263            _details.Append(' ');
 2264            _details.Append(RenderDetails(auditAlert.Details));
 2265            CloseHeader(_details, _terse, _tabLevel);
 266        }
 2267    }
 268}