Repository: weaveworks/grafanalib

Issue:
AlertCondition object missing useNewAlerts attribute
looking at https://github.com/weaveworks/grafanalib/blob/c9a77d481da8d76a91ad92efb1a155d6dae8a34d/grafanalib/core.py#L1192
and https://github.com/weaveworks/grafanalib/blob/c9a77d481da8d76a91ad92efb1a155d6dae8a34d/grafanalib/core.py#L1215 its possible to see there is use of self.useNewAlerts but AlertExpression don't have this attribute 

Discussion:
ali-essam: @JamesGibo can you please review this 🙏 ?

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 7b18f65..2da345a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,7 +2,6 @@
 Changelog
 =========
 
-
 0.x.x (?)
 ==================
 
@@ -11,6 +10,7 @@ Changelog
 * Added Maximum option for Timeseries
 * Added Number of decimals displays option for Timeseries* Added Bar_Chart_ panel support
 * Extended SqlTarget to support parsing queries from files
+* Fix AlertCondition backwards compatibility (``useNewAlerts`` default to ``False``)
 
 .. _Bar_Chart: basehttps://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-chart/
 
diff --git a/grafanalib/core.py b/grafanalib/core.py
index e10552a..aeb1cb3 100644
--- a/grafanalib/core.py
+++ b/grafanalib/core.py
@@ -1197,6 +1197,9 @@ class AlertCondition(object):
         RTYPE_DIFF = 'diff'
         RTYPE_PERCENT_DIFF = 'percent_diff'
         RTYPE_COUNT_NON_NULL = 'count_non_null'
+    :param useNewAlerts: Whether or not the alert condition is used as part of the Grafana 8.x alerts.
+        Defaults to False for compatibility with old Grafana alerts, but automatically overridden to true
+        when used inside ``AlertExpression`` or ``AlertRulev8``
     :param type: CTYPE_*
     """
 
@@ -1205,6 +1208,7 @@ class AlertCondition(object):
     timeRange = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(TimeRange)))
     operator = attr.ib(default=OP_AND)
     reducerType = attr.ib(default=RTYPE_LAST)
+    useNewAlerts = attr.ib(default=False)
 
     type = attr.ib(default=CTYPE_QUERY, kw_only=True)
 
diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py
index a01c531..9726983 100644
--- a/grafanalib/elasticsearch.py
+++ b/grafanalib/elasticsearch.py
@@ -2,7 +2,7 @@
 
 import attr
 import itertools
-from attr.validators import instance_of
+from attr.validators import in_, instance_of
 from grafanalib.core import AlertCondition
 
 DATE_HISTOGRAM_DEFAULT_FIELD = 'time_iso8601'
@@ -498,3 +498,49 @@ class PercentilesMetricAgg(object):
             'inlineScript': self.inline,
             'settings': self.settings,
         }
+
+
+@attr.s
+class RateMetricAgg(object):
+    """An aggregator that provides the rate of the values.
+    https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-rate-aggregation.html
+    :param field: name of elasticsearch field to provide the sum over
+    :param hide: show/hide the metric in the final panel display
+    :param id: id of the metric
+    :param unit: calendar interval to group by
+        supported calendar intervals
+        https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html#calendar_intervals
+        "minute"
+        "hour"
+        "day"
+        "week"
+        "month"
+        "quarter"
+        "year"
+    :param mode: sum or count the values
+    :param script: script to apply to the data, using '_value'
+    """
+
+    field = attr.ib(default="", validator=instance_of(str))
+    id = attr.ib(default=0, validator=instance_of(int))
+    hide = attr.ib(default=False, validator=instance_of(bool))
+    unit = attr.ib(default="", validator=instance_of(str))
+    mode = attr.ib(default="", validator=in_(["", "value_count", "sum"]))
+    script = attr.ib(default="", validator=instance_of(str))
+
+    def to_json_data(self):
+        self.settings = {}
+
+        if self.mode:
+            self.settings["mode"] = self.mode
+
+        if self.script:
+            self.settings["script"] = self.script
+
+        return {
+            "id": str(self.id),
+            "hide": self.hide,
+            "field": self.field,
+            "settings": self.settings,
+            "type": "rate",
+        }


Test patch:
diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py
index f8178e8..2b03610 100644
--- a/grafanalib/tests/test_core.py
+++ b/grafanalib/tests/test_core.py
@@ -954,6 +954,33 @@ def test_alertfilefasedfrovisioning():
     assert data['groups'] == groups
 
 
+def test_alertCondition_useNewAlerts_default():
+    alert_condition = G.AlertCondition(
+        G.Target(refId="A"),
+        G.Evaluator('a', 'b'),
+        G.TimeRange('5', '6'),
+        'd',
+        'e'
+    )
+    data = alert_condition.to_json_data()
+    assert data['query']['model'] is not None
+    assert len(data['query']['params']) == 3
+
+
+def test_alertCondition_useNewAlerts_true():
+    alert_condition = G.AlertCondition(
+        G.Target(refId="A"),
+        G.Evaluator('a', 'b'),
+        G.TimeRange('5', '6'),
+        'd',
+        'e',
+        useNewAlerts=True
+    )
+    data = alert_condition.to_json_data()
+    assert 'model' not in data['query']
+    assert len(data['query']['params']) == 1
+
+
 def test_worldmap():
     data_source = 'dummy data source'
     targets = ['dummy_prom_query']