/// <summary> /// Extract all alert properties from the specified object /// </summary> /// <param name="propertiesOwner">The object from which to extract the properties</param> /// <param name="order">The order to use</param> /// <param name="parentPropertyName">The parent property name</param> /// <param name="propertiesToIgnore"> /// A list of property names to ignore. Properties with matching names will not be included in the result. /// </param> /// <returns>The extracted properties</returns> private static List <AlertProperty> ExtractProperties(object propertiesOwner, Order order, string parentPropertyName, HashSet <string> propertiesToIgnore) { if (order == null) { throw new ArgumentNullException(nameof(order)); } // The null object has no properties if (propertiesOwner == null) { return(new List <AlertProperty>()); } // Collect all object properties, and sort them by order var orderedProperties = propertiesOwner.GetType().GetProperties().Select(property => { // Get the property value object propertyValue = property.GetValue(propertiesOwner); // Skip the property if it is empty if (propertyValue == null || (propertyValue is string stringValue && string.IsNullOrWhiteSpace(stringValue)) || (propertyValue is ICollection collectionValue && collectionValue.Count == 0)) { return(null); } // Get the presentation attribute AlertPresentationPropertyAttribute presentationAttribute = property.GetCustomAttribute <AlertPresentationPropertyAttribute>(); // Return the presentation attribute, property, and value return(new { PresentationAttribute = presentationAttribute, Property = property, Value = propertyValue }); }) .Where(x => x != null) .OrderBy(p => p.PresentationAttribute?.Order ?? -1) .ThenBy(p => p.Property.Name); // Process the properties, in order List <AlertProperty> alertProperties = new List <AlertProperty>(); foreach (var p in orderedProperties) { if (p.PresentationAttribute != null) { alertProperties.AddRange(CreateAlertProperties(propertiesOwner, p.Property, p.PresentationAttribute, p.Value, order, parentPropertyName)); } else if (!propertiesToIgnore.Contains(p.Property.Name)) { // Get the raw alert property - a property with no presentation alertProperties.Add(new RawAlertProperty(CombinePropertyNames(parentPropertyName, p.Property.Name), p.Value)); } } return(alertProperties); }
/// <summary> /// Creates an <see cref="AlertPropertyLegacy"/> based on an alert presentation V1 property /// </summary> /// <param name="alert">The alert</param> /// <param name="presentationAttribute">The attribute defining the presentation V1 of the alert property</param> /// <param name="queryRunInfo">The query run information</param> /// <param name="propertyStringValue">The property string value</param> /// <returns>An <see cref="AlertPropertyLegacy"/></returns> #pragma warning disable CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 private static AlertPropertyLegacy CreateAlertPropertyLegacy(Alert alert, AlertPresentationPropertyAttribute presentationAttribute, QueryRunInfo queryRunInfo, string propertyStringValue) { // Verify that if the entity is a chart or query, then query run information was provided if (queryRunInfo == null && (presentationAttribute.Section == AlertPresentationSection.Chart || presentationAttribute.Section == AlertPresentationSection.AdditionalQuery)) { throw new InvalidAlertPresentationException($"The presentation contains an item for the {presentationAttribute.Section} section, but no telemetry data client was provided"); } // Get the attribute title and information balloon - support interpolated strings string attributeTitle = StringExtensions.EvaluateInterpolatedString(presentationAttribute.Title, alert); string attributeInfoBalloon = StringExtensions.EvaluateInterpolatedString(presentationAttribute.InfoBalloon, alert); // Add the presentation property return(new AlertPropertyLegacy() { Name = attributeTitle, Value = propertyStringValue, DisplayCategory = GetDisplayCategoryFromPresentationSection(presentationAttribute.Section), InfoBalloon = attributeInfoBalloon, Order = presentationAttribute.Order }); }
/// <summary> /// Creates one or more <see cref="AlertProperty"/> objects based on an alert presentation property /// </summary> /// <param name="propertyOwner">The object that has the property</param> /// <param name="property">The property info of the property to create</param> /// <param name="presentationAttribute">The attribute defining the presentation of the alert property</param> /// <param name="propertyValue">The property value</param> /// <param name="order">The order to use</param> /// <param name="parentPropertyName">The parent property name</param> /// <returns>The <see cref="AlertProperty"/> objects</returns> private static IEnumerable <AlertProperty> CreateAlertProperties(object propertyOwner, PropertyInfo property, AlertPresentationPropertyAttribute presentationAttribute, object propertyValue, Order order, string parentPropertyName) { // Get the attribute display name string displayName = presentationAttribute.DisplayName.EvaluateInterpolatedString(propertyOwner); // Get the property name (and add the parent property name as prefix, if provided) string propertyName = string.IsNullOrWhiteSpace(presentationAttribute.PropertyName) ? property.Name : presentationAttribute.PropertyName; propertyName = CombinePropertyNames(parentPropertyName, propertyName); // Try converting the property value to PropertyReference var propertyReferenceValue = propertyValue as PropertyReference; // Return the presentation property according to the property type switch (presentationAttribute) { case ChartPropertyAttribute chartAttribute: yield return(CreateChartAlertProperty(propertyValue, propertyName, displayName, chartAttribute, order)); break; case MetricChartPropertyAttribute _: if (!(propertyValue is MetricChart metricChart)) { throw new ArgumentException($"A {nameof(MetricChartPropertyAttribute)} can only be applied to properties of type {nameof(MetricChart)}"); } yield return(CreateMetricChartAlertProperty(propertyName, displayName, order, metricChart)); break; case LongTextPropertyAttribute longTextPropertyAttribute: if (propertyReferenceValue != null) { if (!string.IsNullOrEmpty(longTextPropertyAttribute.FormatString)) { throw new ArgumentException($"A {nameof(LongTextPropertyAttribute)} applied to properties of type {nameof(PropertyReference)} cannot have format string"); } yield return(new LongTextReferenceAlertProperty(propertyName, displayName, order.Next(), propertyReferenceValue.ReferencePath, propertyReferenceValue.IsOptional, propertyReferenceValue.IsPropertySerialized)); } else { yield return(new LongTextAlertProperty(propertyName, displayName, order.Next(), PropertyValueToString(propertyOwner, property, propertyValue, longTextPropertyAttribute.FormatString))); } break; case TextPropertyAttribute textPropertyAttribute: if (propertyReferenceValue != null) { if (!string.IsNullOrEmpty(textPropertyAttribute.FormatString)) { throw new ArgumentException($"A {nameof(TextPropertyAttribute)} applied to properties of type {nameof(PropertyReference)} cannot have format string"); } yield return(new TextReferenceAlertProperty(propertyName, displayName, order.Next(), propertyReferenceValue.ReferencePath, propertyReferenceValue.IsOptional, propertyReferenceValue.IsPropertySerialized)); } else { yield return(new TextAlertProperty(propertyName, displayName, order.Next(), PropertyValueToString(propertyOwner, property, propertyValue, textPropertyAttribute.FormatString))); } break; case ListPropertyAttribute _: if (!(propertyValue is IList list)) { throw new ArgumentException($"A {nameof(ListPropertyAttribute)} can only be applied to properties of type IList"); } foreach (AlertProperty p in CreateAlertPropertiesFromList(propertyName, list, order)) { yield return(p); } break; case KeyValuePropertyAttribute keyValueAttribute: yield return(CreateKeyValueAlertProperty(propertyOwner, propertyValue, propertyName, displayName, keyValueAttribute, order)); break; case TablePropertyAttribute tableAttribute: yield return(CreateTableAlertProperty(propertyValue, propertyName, displayName, tableAttribute, order)); break; case AzureResourceManagerRequestPropertyAttribute armRequestAttribute: if (!(propertyValue is AzureResourceManagerRequest armRequest)) { throw new ArgumentException($"A {nameof(AzureResourceManagerRequestPropertyAttribute)} can only be applied to properties of type {nameof(AzureResourceManagerRequest)}"); } List <AlertProperty> properties = armRequest.ExtractProperties(AzureResourceManagerRequestBaseClassPropertiesNames); if (properties.Any(prop => !(prop is IReferenceAlertProperty || prop is RawAlertProperty))) { // We support raw properties because they can contain values used in interpolated display name string throw new ArgumentException($"An {nameof(AzureResourceManagerRequest)} can only have reference and raw alert properties"); } List <AlertProperty> propertiesToDisplay = properties.Where(prop => prop is IReferenceAlertProperty).ToList(); yield return(new AzureResourceManagerRequestAlertProperty(propertyName, order.Next(), armRequest.RequestUri, propertiesToDisplay.Cast <DisplayableAlertProperty>().ToList(), armRequest.IsOptional)); break; default: throw new InvalidEnumArgumentException($"Unable to handle presentation attribute of type {presentationAttribute.GetType().Name}"); } }
/// <summary> /// Creates a presentation from a alert /// </summary> /// <param name="alert">The alert</param> /// <param name="request">The Smart Detector request</param> /// <param name="smartDetectorName">The Smart Detector name</param> /// <param name="queryRunInfo">The query run information</param> /// <returns>The presentation</returns> public static ContractsAlert CreateContractsAlert(this Alert alert, SmartDetectorExecutionRequest request, string smartDetectorName, QueryRunInfo queryRunInfo) { // A null alert has null presentation if (alert == null) { return(null); } // Create presentation elements for each alert property Dictionary <string, string> predicates = new Dictionary <string, string>(); List <AlertProperty> alertProperties = new List <AlertProperty>(); Dictionary <string, string> rawProperties = new Dictionary <string, string>(); foreach (PropertyInfo property in alert.GetType().GetProperties()) { // Get the property value string propertyValue = PropertyValueToString(property.GetValue(alert)); if (string.IsNullOrWhiteSpace(propertyValue)) { // not accepting empty properties continue; } rawProperties[property.Name] = propertyValue; // Check if this property is a predicate if (property.GetCustomAttribute <AlertPredicatePropertyAttribute>() != null) { predicates[property.Name] = propertyValue; } // Get the presentation attribute AlertPresentationPropertyAttribute attribute = property.GetCustomAttribute <AlertPresentationPropertyAttribute>(); if (attribute != null) { // Verify that if the entity is a chart or query, then query run information was provided if (queryRunInfo == null && (attribute.Section == AlertPresentationSection.Chart || attribute.Section == AlertPresentationSection.AdditionalQuery)) { throw new InvalidAlertPresentationException($"The presentation contains an item for the {attribute.Section} section, but no telemetry data client was provided"); } // Get the attribute title and information balloon - support interpolated strings string attributeTitle = Smart.Format(attribute.Title, alert); string attributeInfoBalloon = Smart.Format(attribute.InfoBalloon, alert); // Add the presentation property alertProperties.Add(new AlertProperty { Name = attributeTitle, Value = propertyValue, DisplayCategory = GetDisplayCategoryFromPresentationSection(attribute.Section), InfoBalloon = attributeInfoBalloon }); } } string id = string.Join("##", alert.GetType().FullName, JsonConvert.SerializeObject(request), JsonConvert.SerializeObject(alert)).Hash(); string resourceId = alert.ResourceIdentifier.ToResourceId(); string correlationHash = string.Join("##", predicates.OrderBy(x => x.Key).Select(x => x.Key + "|" + x.Value)).Hash(); // Return the presentation object return(new ContractsAlert { Id = id, Title = alert.Title, ResourceId = resourceId, CorrelationHash = correlationHash, SmartDetectorId = request.SmartDetectorId, SmartDetectorName = smartDetectorName, AnalysisTimestamp = DateTime.UtcNow, AnalysisWindowSizeInMinutes = (int)request.Cadence.TotalMinutes, Properties = alertProperties, RawProperties = rawProperties, QueryRunInfo = queryRunInfo }); }
/// <summary> /// Creates a presentation from an alert /// </summary> /// <param name="alert">The alert</param> /// <param name="request">The Smart Detector request</param> /// <param name="smartDetectorName">The Smart Detector name</param> /// <param name="queryRunInfo">The query run information</param> /// <param name="usedLogAnalysisClient">Indicates whether a log analysis client was used to create the alert</param> /// <param name="usedMetricClient">Indicates whether a metric client was used to create the alert</param> /// <returns>The presentation</returns> public static ContractsAlert CreateContractsAlert(this Alert alert, SmartDetectorExecutionRequest request, string smartDetectorName, QueryRunInfo queryRunInfo, bool usedLogAnalysisClient, bool usedMetricClient) { // A null alert has null presentation if (alert == null) { return(null); } // Create presentation elements for each alert property Dictionary <string, string> predicates = new Dictionary <string, string>(); #pragma warning disable CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 List <AlertPropertyLegacy> alertPropertiesLegacy = new List <AlertPropertyLegacy>(); #pragma warning restore CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 List <AlertProperty> alertProperties = new List <AlertProperty>(); Dictionary <string, string> rawProperties = new Dictionary <string, string>(); List <string> alertBaseClassPropertiesNames = typeof(Alert).GetProperties().Select(p => p.Name).ToList(); foreach (PropertyInfo property in alert.GetType().GetProperties()) { // Get the property value object propertyValue = property.GetValue(alert); string propertyStringValue = PropertyValueToString(propertyValue); if (string.IsNullOrWhiteSpace(propertyStringValue) || (propertyValue is ICollection value && value.Count == 0)) { // not accepting empty properties continue; } rawProperties[property.Name] = propertyStringValue; // Check if this property is a predicate if (property.GetCustomAttribute <AlertPredicatePropertyAttribute>() != null) { predicates[property.Name] = propertyStringValue; } // Get the v1 presentation attribute AlertPresentationPropertyAttribute presentationAttribute = property.GetCustomAttribute <AlertPresentationPropertyAttribute>(); if (presentationAttribute != null) { alertPropertiesLegacy.Add(CreateAlertPropertyLegacy(alert, presentationAttribute, queryRunInfo, propertyStringValue)); } // Get the v2 presentation attribute AlertPresentationPropertyV2Attribute presentationV2Attribute = property.GetCustomAttribute <AlertPresentationPropertyV2Attribute>(); if (presentationV2Attribute != null) { alertProperties.Add(CreateAlertProperty(alert, presentationV2Attribute, property.Name, propertyValue)); } else if (!alertBaseClassPropertiesNames.Contains(property.Name)) { // Get the raw alert property - a property with no presentation alertProperties.Add(new RawAlertProperty(property.Name, propertyValue)); } } string id = string.Join("##", alert.GetType().FullName, JsonConvert.SerializeObject(request), JsonConvert.SerializeObject(alert)).Hash(); string resourceId = alert.ResourceIdentifier.ToResourceId(); string correlationHash = string.Join("##", predicates.OrderBy(x => x.Key).Select(x => x.Key + "|" + x.Value)).Hash(); // Get the alert's signal type based on the clients used to create the alert SignalType signalType = GetSignalType(usedLogAnalysisClient, usedMetricClient); // Return the presentation object #pragma warning disable CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 return(new ContractsAlert { Id = id, State = (alert.State == AlertState.Active) ? ContractsAlertState.Active : ContractsAlertState.Resolved, Title = alert.Title, ResourceId = resourceId, CorrelationHash = correlationHash, SmartDetectorId = request.SmartDetectorId, SmartDetectorName = smartDetectorName, AnalysisTimestamp = DateTime.UtcNow, AnalysisWindowSizeInMinutes = (int)request.Cadence.TotalMinutes, Properties = alertPropertiesLegacy, AlertProperties = alertProperties, RawProperties = rawProperties, QueryRunInfo = queryRunInfo, SignalType = signalType }); #pragma warning restore CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924 }