/// <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
        }
        #pragma warning restore CS0612 // Type or member is obsolete; Task to remove obsolete code #1312924

        /// <summary>
        /// Creates an <see cref="AlertProperty"/> based on an alert presentation V2 property
        /// </summary>
        /// <param name="alert">The alert</param>
        /// <param name="presentationAttribute">The attribute defining the presentation V2 of the alert property</param>
        /// <param name="propertyDefaultName">The property default name</param>
        /// <param name="propertyValue">The property value</param>
        /// <returns>An <see cref="AlertProperty"/></returns>
        private static AlertProperty CreateAlertProperty(Alert alert, AlertPresentationPropertyV2Attribute presentationAttribute, string propertyDefaultName, object propertyValue)
        {
            // Get the attribute display name
            string displayName = StringExtensions.EvaluateInterpolatedString(presentationAttribute.DisplayName, alert);

            // Get the property name
            string propertyName = string.IsNullOrWhiteSpace(presentationAttribute.PropertyName) ? propertyDefaultName : presentationAttribute.PropertyName;

            // Return the presentation property according to the property type
            switch (presentationAttribute)
            {
            case AlertPresentationChartAttribute chartAttribute:
                if (!(propertyValue is IList <ChartPoint> listValues))
                {
                    throw new ArgumentException("An AlertPresentationChartAttribute can only be applied to properties of type IList<ChartPoint>");
                }

                return(new ChartAlertProperty(
                           propertyName,
                           displayName,
                           presentationAttribute.Order,
                           ConvertChartTypeToContractsChartType(chartAttribute.ChartType),
                           ConvertChartAxisTypeToContractsChartType(chartAttribute.XAxisType),
                           ConvertChartAxisTypeToContractsChartType(chartAttribute.YAxisType),
                           listValues.Select(point => new ContractsChartPoint(point.X, point.Y)).ToList()));

            case AlertPresentationLongTextAttribute longTextAttribute:
                return(new LongTextAlertProprety(propertyName, displayName, presentationAttribute.Order, PropertyValueToString(propertyValue)));

            case AlertPresentationTextAttribute textAttribute:
                return(new TextAlertProperty(propertyName, displayName, presentationAttribute.Order, PropertyValueToString(propertyValue)));

            case AlertPresentationUrlAttribute urlAttribute:
                if (!(propertyValue is Uri uriValue))
                {
                    throw new ArgumentException("An AlertPresentationUrlAttribute can only be applied to properties of type Uri");
                }

                if (!uriValue.IsAbsoluteUri)
                {
                    throw new ArgumentException("The URI supplied must be absolute");
                }

                string linkText = StringExtensions.EvaluateInterpolatedString(urlAttribute.LinkText, alert);
                return(new TextAlertProperty(propertyName, displayName, presentationAttribute.Order, $"<a href=\"{uriValue.ToString()}\">{linkText}</a>"));

            case AlertPresentationKeyValueAttribute keyValueAttribute:
                if (!(propertyValue is IDictionary <string, string> keyValuePropertyValue))
                {
                    throw new ArgumentException("An AlertPresentationKeyValueAttribute can only be applied to properties of type IDictionary<string, string>");
                }

                if (keyValueAttribute.ShowHeaders)
                {
                    string keyHeaderName   = StringExtensions.EvaluateInterpolatedString(keyValueAttribute.KeyHeaderName, alert);
                    string valueHeaderName = StringExtensions.EvaluateInterpolatedString(keyValueAttribute.ValueHeaderName, alert);
                    return(new KeyValueAlertProperty(propertyName, displayName, presentationAttribute.Order, keyHeaderName, valueHeaderName, keyValuePropertyValue));
                }
                else
                {
                    return(new KeyValueAlertProperty(propertyName, displayName, presentationAttribute.Order, keyValuePropertyValue));
                }

            case AlertPresentationSingleColumnTableAttribute singleColumnTableAttribute:
                if (!(propertyValue is IList singleColumnTablePropertyValue))
                {
                    throw new ArgumentException("An AlertPresentationSingleColumnTableAttribute can only be applied to properties of type IList");
                }

                return(new TableAlertProperty(propertyName, displayName, presentationAttribute.Order, singleColumnTableAttribute.ShowHeaders, singleColumnTablePropertyValue));

            case AlertPresentationTableAttribute tableAttribute:
                if (!(propertyValue is IList tablePropertyValue))
                {
                    throw new ArgumentException("An AlertPresentationTableAttribute can only be applied to properties of type IList");
                }

                Type tableRowType = GetGenericListType(propertyValue.GetType());
                if (tableRowType == null)
                {
                    throw new ArgumentException("An AlertPresentationTableAttribute can only be applied to properties of type IList<>");
                }

                return(new TableAlertProperty(propertyName, displayName, presentationAttribute.Order, tableAttribute.ShowHeaders, CreateTableColumnsFromRowType(tableRowType), tablePropertyValue));

            default:
                throw new InvalidEnumArgumentException($"Unable to handle presentation attribute of type {presentationAttribute.GetType().Name}");
            }
        }