This is an automated email from the ASF dual-hosted git repository.

villebro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 327a281  feat: add event and interval annotation support to chart data 
ep (#11665)
327a281 is described below

commit 327a2817d35416f74902c889487d0aa6ff1969b8
Author: Ville Brofeldt <[email protected]>
AuthorDate: Fri Dec 4 14:40:31 2020 +0200

    feat: add event and interval annotation support to chart data ep (#11665)
    
    * feat: add event and interval annotation support to chart data ep
    
    * add tests + refactor fixtures
    
    * use chart dao
---
 superset-frontend/src/chart/chartAction.js |   5 +-
 superset/charts/schemas.py                 |   5 +-
 superset/common/query_context.py           | 100 +++++++++++++++++++++++++---
 superset/common/query_object.py            |  30 ++++++++-
 superset/utils/core.py                     |   7 ++
 superset/viz.py                            |  17 +++--
 tests/annotation_layers/api_tests.py       |  79 +++-------------------
 tests/annotation_layers/fixtures.py        | 101 +++++++++++++++++++++++++++++
 tests/charts/api_tests.py                  |  52 +++++++++++++--
 tests/fixtures/query_context.py            |  73 +++++++++++++++++++++
 10 files changed, 372 insertions(+), 97 deletions(-)

diff --git a/superset-frontend/src/chart/chartAction.js 
b/superset-frontend/src/chart/chartAction.js
index 16dd3f4..f6329a8 100644
--- a/superset-frontend/src/chart/chartAction.js
+++ b/superset-frontend/src/chart/chartAction.js
@@ -412,7 +412,10 @@ export function exploreJSON(
         });
       });
 
-    const annotationLayers = formData.annotation_layers || [];
+    // only retrieve annotations when calling the legacy API
+    const annotationLayers = shouldUseLegacyApi(formData)
+      ? formData.annotation_layers || []
+      : [];
     const isDashboardRequest = dashboardId > 0;
 
     return Promise.all([
diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py
index ca1497a..5e346fa 100644
--- a/superset/charts/schemas.py
+++ b/superset/charts/schemas.py
@@ -23,6 +23,7 @@ from marshmallow.validate import Length, Range
 from superset.common.query_context import QueryContext
 from superset.utils import schema as utils
 from superset.utils.core import (
+    AnnotationType,
     FilterOperator,
     PostProcessingBoxplotWhiskerType,
     PostProcessingContributionOrientation,
@@ -783,9 +784,7 @@ class ChartDataExtrasSchema(Schema):
 class AnnotationLayerSchema(Schema):
     annotationType = fields.String(
         description="Type of annotation layer",
-        validate=validate.OneOf(
-            choices=("EVENT", "FORMULA", "INTERVAL", "TIME_SERIES",)
-        ),
+        validate=validate.OneOf(choices=[ann.value for ann in AnnotationType]),
     )
     color = fields.String(description="Layer color", allow_none=True,)
     descriptionColumns = fields.List(
diff --git a/superset/common/query_context.py b/superset/common/query_context.py
index 19e6668..25113c3 100644
--- a/superset/common/query_context.py
+++ b/superset/common/query_context.py
@@ -25,14 +25,17 @@ import pandas as pd
 from flask_babel import gettext as _
 
 from superset import app, db, is_feature_enabled
+from superset.annotation_layers.dao import AnnotationLayerDAO
+from superset.charts.dao import ChartDAO
 from superset.common.query_object import QueryObject
 from superset.connectors.base.models import BaseDatasource
 from superset.connectors.connector_registry import ConnectorRegistry
-from superset.exceptions import QueryObjectValidationError
+from superset.exceptions import QueryObjectValidationError, SupersetException
 from superset.extensions import cache_manager, security_manager
 from superset.stats_logger import BaseStatsLogger
 from superset.utils import core as utils
 from superset.utils.core import DTTM_ALIAS
+from superset.views.utils import get_viz
 from superset.viz import set_and_log_cache
 
 config = app.config
@@ -157,8 +160,7 @@ class QueryContext:
             query_obj.row_offset = 0
             query_obj.columns = [o.column_name for o in 
self.datasource.columns]
         payload = self.get_df_payload(query_obj)
-        # TODO: implement
-        payload["annotation_data"] = []
+
         df = payload["df"]
         status = payload["status"]
         if status != utils.QueryStatus.FAILED:
@@ -220,7 +222,79 @@ class QueryContext:
         )
         return cache_key
 
-    def get_df_payload(  # pylint: disable=too-many-statements
+    @staticmethod
+    def get_native_annotation_data(query_obj: QueryObject) -> Dict[str, Any]:
+        annotation_data = {}
+        annotation_layers = [
+            layer
+            for layer in query_obj.annotation_layers
+            if layer["sourceType"] == "NATIVE"
+        ]
+        layer_ids = [layer["value"] for layer in annotation_layers]
+        layer_objects = {
+            layer_object.id: layer_object
+            for layer_object in AnnotationLayerDAO.find_by_ids(layer_ids)
+        }
+
+        # annotations
+        for layer in annotation_layers:
+            layer_id = layer["value"]
+            layer_name = layer["name"]
+            columns = [
+                "start_dttm",
+                "end_dttm",
+                "short_descr",
+                "long_descr",
+                "json_metadata",
+            ]
+            layer_object = layer_objects[layer_id]
+            records = [
+                {column: getattr(annotation, column) for column in columns}
+                for annotation in layer_object.annotation
+            ]
+            result = {"columns": columns, "records": records}
+            annotation_data[layer_name] = result
+        return annotation_data
+
+    @staticmethod
+    def get_viz_annotation_data(
+        annotation_layer: Dict[str, Any], force: bool
+    ) -> Dict[str, Any]:
+        chart = ChartDAO.find_by_id(annotation_layer["value"])
+        form_data = chart.form_data.copy()
+        if not chart:
+            raise QueryObjectValidationError("The chart does not exist")
+        try:
+            viz_obj = get_viz(
+                datasource_type=chart.datasource.type,
+                datasource_id=chart.datasource.id,
+                form_data=form_data,
+                force=force,
+            )
+            payload = viz_obj.get_payload()
+            return payload["data"]
+        except SupersetException as ex:
+            raise 
QueryObjectValidationError(utils.error_msg_from_exception(ex))
+
+    def get_annotation_data(self, query_obj: QueryObject) -> Dict[str, Any]:
+        """
+
+        :param query_obj:
+        :return:
+        """
+        annotation_data: Dict[str, Any] = 
self.get_native_annotation_data(query_obj)
+        for annotation_layer in [
+            layer
+            for layer in query_obj.annotation_layers
+            if layer["sourceType"] in ("line", "table")
+        ]:
+            name = annotation_layer["name"]
+            annotation_data[name] = self.get_viz_annotation_data(
+                annotation_layer, self.force
+            )
+        return annotation_data
+
+    def get_df_payload(  # pylint: disable=too-many-statements,too-many-locals
         self, query_obj: QueryObject, **kwargs: Any
     ) -> Dict[str, Any]:
         """Handles caching around the df payload retrieval"""
@@ -233,6 +307,7 @@ class QueryContext:
         cache_value = None
         status = None
         query = ""
+        annotation_data = {}
         error_message = None
         if cache_key and cache_manager.data_cache and not self.force:
             cache_value = cache_manager.data_cache.get(cache_key)
@@ -241,6 +316,7 @@ class QueryContext:
                 try:
                     df = cache_value["df"]
                     query = cache_value["query"]
+                    annotation_data = cache_value.get("annotation_data", {})
                     status = utils.QueryStatus.SUCCESS
                     is_loaded = True
                     stats_logger.incr("loaded_from_cache")
@@ -272,6 +348,8 @@ class QueryContext:
                 query = query_result["query"]
                 error_message = query_result["error_message"]
                 df = query_result["df"]
+                annotation_data = self.get_annotation_data(query_obj)
+
                 if status != utils.QueryStatus.FAILED:
                     stats_logger.incr("loaded_from_source")
                     if not self.force:
@@ -289,18 +367,20 @@ class QueryContext:
 
             if is_loaded and cache_key and status != utils.QueryStatus.FAILED:
                 set_and_log_cache(
-                    cache_key,
-                    df,
-                    query,
-                    cached_dttm,
-                    self.cache_timeout,
-                    self.datasource.uid,
+                    cache_key=cache_key,
+                    df=df,
+                    query=query,
+                    annotation_data=annotation_data,
+                    cached_dttm=cached_dttm,
+                    cache_timeout=self.cache_timeout,
+                    datasource_uid=self.datasource.uid,
                 )
         return {
             "cache_key": cache_key,
             "cached_dttm": cache_value["dttm"] if cache_value is not None else 
None,
             "cache_timeout": self.cache_timeout,
             "df": df,
+            "annotation_data": annotation_data,
             "error": error_message,
             "is_cached": cache_value is not None,
             "query": query,
diff --git a/superset/common/query_object.py b/superset/common/query_object.py
index aa2d314..6eb1231 100644
--- a/superset/common/query_object.py
+++ b/superset/common/query_object.py
@@ -106,7 +106,12 @@ class QueryObject:
         metrics = metrics or []
         extras = extras or {}
         is_sip_38 = is_feature_enabled("SIP_38_VIZ_REARCHITECTURE")
-        self.annotation_layers = annotation_layers
+        self.annotation_layers = [
+            layer
+            for layer in annotation_layers
+            # formula annotations don't affect the payload, hence can be 
dropped
+            if layer["annotationType"] != "FORMULA"
+        ]
         self.applied_time_extras = applied_time_extras or {}
         self.granularity = granularity
         self.from_dttm, self.to_dttm = utils.get_since_until(
@@ -236,10 +241,31 @@ class QueryObject:
             cache_dict["time_range"] = self.time_range
         if self.post_processing:
             cache_dict["post_processing"] = self.post_processing
+
+        annotation_fields = [
+            "annotationType",
+            "descriptionColumns",
+            "intervalEndColumn",
+            "name",
+            "overrides",
+            "sourceType",
+            "timeColumn",
+            "titleColumn",
+            "value",
+        ]
+        annotation_layers = [
+            {field: layer[field] for field in annotation_fields if field in 
layer}
+            for layer in self.annotation_layers
+        ]
+        # only add to key if there are annotations present that affect the 
payload
+        if annotation_layers:
+            cache_dict["annotation_layers"] = annotation_layers
+
         json_data = self.json_dumps(cache_dict, sort_keys=True)
         return hashlib.md5(json_data.encode("utf-8")).hexdigest()
 
-    def json_dumps(self, obj: Any, sort_keys: bool = False) -> str:
+    @staticmethod
+    def json_dumps(obj: Any, sort_keys: bool = False) -> str:
         return json.dumps(
             obj, default=utils.json_int_dttm_ser, ignore_nan=True, 
sort_keys=sort_keys
         )
diff --git a/superset/utils/core.py b/superset/utils/core.py
index b4e3ef5..2d4febe 100644
--- a/superset/utils/core.py
+++ b/superset/utils/core.py
@@ -1591,6 +1591,13 @@ class ExtraFiltersTimeColumnType(str, Enum):
     TIME_RANGE = "__time_range"
 
 
+class AnnotationType(str, Enum):
+    FORMULA = "FORMULA"
+    INTERVAL = "INTERVAL"
+    EVENT = "EVENT"
+    TIME_SERIES = "TIME_SERIES"
+
+
 def is_test() -> bool:
     return strtobool(os.environ.get("SUPERSET_TESTENV", "false"))
 
diff --git a/superset/viz.py b/superset/viz.py
index ab08fe4..e63f2c0 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -104,9 +104,12 @@ def set_and_log_cache(
     cached_dttm: str,
     cache_timeout: int,
     datasource_uid: Optional[str],
+    annotation_data: Optional[Dict[str, Any]] = None,
 ) -> None:
     try:
-        cache_value = dict(dttm=cached_dttm, df=df, query=query)
+        cache_value = dict(
+            dttm=cached_dttm, df=df, query=query, 
annotation_data=annotation_data or {}
+        )
         stats_logger.incr("set_cache_key")
         cache_manager.data_cache.set(cache_key, cache_value, 
timeout=cache_timeout)
 
@@ -587,12 +590,12 @@ class BaseViz:
 
             if is_loaded and cache_key and self.status != 
utils.QueryStatus.FAILED:
                 set_and_log_cache(
-                    cache_key,
-                    df,
-                    self.query,
-                    cached_dttm,
-                    self.cache_timeout,
-                    self.datasource.uid,
+                    cache_key=cache_key,
+                    df=df,
+                    query=self.query,
+                    cached_dttm=cached_dttm,
+                    cache_timeout=self.cache_timeout,
+                    datasource_uid=self.datasource.uid,
                 )
         return {
             "cache_key": self._any_cache_key,
diff --git a/tests/annotation_layers/api_tests.py 
b/tests/annotation_layers/api_tests.py
index 2b0df41..0ee361b 100644
--- a/tests/annotation_layers/api_tests.py
+++ b/tests/annotation_layers/api_tests.py
@@ -16,8 +16,6 @@
 # under the License.
 # isort:skip_file
 """Unit tests for Superset"""
-from datetime import datetime
-from typing import Optional
 import json
 
 import pytest
@@ -29,77 +27,17 @@ from superset import db
 from superset.models.annotations import Annotation, AnnotationLayer
 
 from tests.base_tests import SupersetTestCase
-
+from tests.annotation_layers.fixtures import (
+    create_annotation_layers,
+    get_end_dttm,
+    get_start_dttm,
+)
 
 ANNOTATION_LAYERS_COUNT = 10
 ANNOTATIONS_COUNT = 5
 
 
 class TestAnnotationLayerApi(SupersetTestCase):
-    def insert_annotation_layer(
-        self, name: str = "", descr: str = ""
-    ) -> AnnotationLayer:
-        annotation_layer = AnnotationLayer(name=name, descr=descr,)
-        db.session.add(annotation_layer)
-        db.session.commit()
-        return annotation_layer
-
-    def insert_annotation(
-        self,
-        layer: AnnotationLayer,
-        short_descr: str,
-        long_descr: str,
-        json_metadata: Optional[str] = "",
-        start_dttm: Optional[datetime] = None,
-        end_dttm: Optional[datetime] = None,
-    ) -> Annotation:
-        annotation = Annotation(
-            layer=layer,
-            short_descr=short_descr,
-            long_descr=long_descr,
-            json_metadata=json_metadata,
-            start_dttm=start_dttm,
-            end_dttm=end_dttm,
-        )
-        db.session.add(annotation)
-        db.session.commit()
-        return annotation
-
-    @pytest.fixture()
-    def create_annotation_layers(self):
-        """
-        Creates ANNOTATION_LAYERS_COUNT-1 layers with no annotations
-        and a final one with ANNOTATION_COUNT childs
-        :return:
-        """
-        with self.create_app().app_context():
-            annotation_layers = []
-            annotations = []
-            for cx in range(ANNOTATION_LAYERS_COUNT - 1):
-                annotation_layers.append(
-                    self.insert_annotation_layer(name=f"name{cx}", 
descr=f"descr{cx}")
-                )
-            layer_with_annotations = self.insert_annotation_layer(
-                "layer_with_annotations"
-            )
-            annotation_layers.append(layer_with_annotations)
-            for cx in range(ANNOTATIONS_COUNT):
-                annotations.append(
-                    self.insert_annotation(
-                        layer_with_annotations,
-                        short_descr=f"short_descr{cx}",
-                        long_descr=f"long_descr{cx}",
-                    )
-                )
-            yield annotation_layers
-
-            # rollback changes
-            for annotation_layer in annotation_layers:
-                db.session.delete(annotation_layer)
-            for annotation in annotations:
-                db.session.delete(annotation)
-            db.session.commit()
-
     @staticmethod
     def get_layer_with_annotation() -> AnnotationLayer:
         return (
@@ -421,9 +359,10 @@ class TestAnnotationLayerApi(SupersetTestCase):
         """
         Annotation API: Test get annotation
         """
+        annotation_id = 1
         annotation = (
             db.session.query(Annotation)
-            .filter(Annotation.short_descr == "short_descr1")
+            .filter(Annotation.short_descr == f"short_descr{annotation_id}")
             .one_or_none()
         )
 
@@ -436,12 +375,12 @@ class TestAnnotationLayerApi(SupersetTestCase):
 
         expected_result = {
             "id": annotation.id,
-            "end_dttm": None,
+            "end_dttm": get_end_dttm(annotation_id).isoformat(),
             "json_metadata": "",
             "layer": {"id": annotation.layer_id, "name": 
"layer_with_annotations"},
             "long_descr": annotation.long_descr,
             "short_descr": annotation.short_descr,
-            "start_dttm": None,
+            "start_dttm": get_start_dttm(annotation_id).isoformat(),
         }
 
         data = json.loads(rv.data.decode("utf-8"))
diff --git a/tests/annotation_layers/fixtures.py 
b/tests/annotation_layers/fixtures.py
new file mode 100644
index 0000000..d2960ac
--- /dev/null
+++ b/tests/annotation_layers/fixtures.py
@@ -0,0 +1,101 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# isort:skip_file
+import pytest
+from datetime import datetime
+from typing import Optional
+
+from superset import db
+from superset.models.annotations import Annotation, AnnotationLayer
+
+from tests.test_app import app
+
+
+ANNOTATION_LAYERS_COUNT = 10
+ANNOTATIONS_COUNT = 5
+
+
+def get_start_dttm(annotation_id: int) -> datetime:
+    return datetime(1990 + annotation_id, 1, 1)
+
+
+def get_end_dttm(annotation_id: int) -> datetime:
+    return datetime(1990 + annotation_id, 7, 1)
+
+
+def _insert_annotation_layer(name: str = "", descr: str = "") -> 
AnnotationLayer:
+    annotation_layer = AnnotationLayer(name=name, descr=descr,)
+    db.session.add(annotation_layer)
+    db.session.commit()
+    return annotation_layer
+
+
+def _insert_annotation(
+    layer: AnnotationLayer,
+    short_descr: str,
+    long_descr: str,
+    json_metadata: Optional[str] = "",
+    start_dttm: Optional[datetime] = None,
+    end_dttm: Optional[datetime] = None,
+) -> Annotation:
+    annotation = Annotation(
+        layer=layer,
+        short_descr=short_descr,
+        long_descr=long_descr,
+        json_metadata=json_metadata,
+        start_dttm=start_dttm,
+        end_dttm=end_dttm,
+    )
+    db.session.add(annotation)
+    db.session.commit()
+    return annotation
+
+
[email protected]()
+def create_annotation_layers():
+    """
+    Creates ANNOTATION_LAYERS_COUNT-1 layers with no annotations
+    and a final one with ANNOTATION_COUNT childs
+    :return:
+    """
+    with app.app_context():
+        annotation_layers = []
+        annotations = []
+        for cx in range(ANNOTATION_LAYERS_COUNT - 1):
+            annotation_layers.append(
+                _insert_annotation_layer(name=f"name{cx}", descr=f"descr{cx}")
+            )
+        layer_with_annotations = 
_insert_annotation_layer("layer_with_annotations")
+        annotation_layers.append(layer_with_annotations)
+        for cx in range(ANNOTATIONS_COUNT):
+            annotations.append(
+                _insert_annotation(
+                    layer_with_annotations,
+                    short_descr=f"short_descr{cx}",
+                    long_descr=f"long_descr{cx}",
+                    start_dttm=get_start_dttm(cx),
+                    end_dttm=get_end_dttm(cx),
+                )
+            )
+        yield annotation_layers
+
+        # rollback changes
+        for annotation_layer in annotation_layers:
+            db.session.delete(annotation_layer)
+        for annotation in annotations:
+            db.session.delete(annotation)
+        db.session.commit()
diff --git a/tests/charts/api_tests.py b/tests/charts/api_tests.py
index 511186d..7479769 100644
--- a/tests/charts/api_tests.py
+++ b/tests/charts/api_tests.py
@@ -30,17 +30,18 @@ import yaml
 from sqlalchemy import and_
 from sqlalchemy.sql import func
 
-from superset.connectors.sqla.models import SqlaTable
-from superset.utils.core import get_example_database
-from tests.fixtures.unicode_dashboard import load_unicode_dashboard_with_slice
 from tests.test_app import app
+from superset.connectors.sqla.models import SqlaTable
+from superset.utils.core import AnnotationType, get_example_database
 from superset.connectors.connector_registry import ConnectorRegistry
 from superset.extensions import db, security_manager
+from superset.models.annotations import AnnotationLayer
 from superset.models.core import Database, FavStar, FavStarClassName
 from superset.models.dashboard import Dashboard
 from superset.models.reports import ReportSchedule, ReportScheduleType
 from superset.models.slice import Slice
 from superset.utils import core as utils
+
 from tests.base_api_tests import ApiOwnersTestCaseMixin
 from tests.base_tests import SupersetTestCase
 from tests.fixtures.importexport import (
@@ -50,7 +51,9 @@ from tests.fixtures.importexport import (
     dataset_config,
     dataset_metadata_config,
 )
-from tests.fixtures.query_context import get_query_context
+from tests.fixtures.query_context import get_query_context, ANNOTATION_LAYERS
+from tests.fixtures.unicode_dashboard import load_unicode_dashboard_with_slice
+from tests.annotation_layers.fixtures import create_annotation_layers
 
 CHART_DATA_URI = "api/v1/chart/data"
 CHARTS_FIXTURE_COUNT = 10
@@ -1383,3 +1386,44 @@ class TestChartApi(SupersetTestCase, 
ApiOwnersTestCaseMixin):
         assert response == {
             "message": {"metadata.yaml": {"type": ["Must be equal to Slice."]}}
         }
+
+    @pytest.mark.usefixtures("create_annotation_layers")
+    def test_chart_data_annotations(self):
+        """
+        Chart data API: Test chart data query
+        """
+        self.login(username="admin")
+        table = self.get_table_by_name("birth_names")
+        request_payload = get_query_context(table.name, table.id, table.type)
+
+        annotation_layers = []
+        request_payload["queries"][0]["annotation_layers"] = annotation_layers
+
+        # formula
+        annotation_layers.append(ANNOTATION_LAYERS[AnnotationType.FORMULA])
+
+        # interval
+        interval_layer = (
+            db.session.query(AnnotationLayer)
+            .filter(AnnotationLayer.name == "name1")
+            .one()
+        )
+        interval = ANNOTATION_LAYERS[AnnotationType.INTERVAL]
+        interval["value"] = interval_layer.id
+        annotation_layers.append(interval)
+
+        # event
+        event_layer = (
+            db.session.query(AnnotationLayer)
+            .filter(AnnotationLayer.name == "name2")
+            .one()
+        )
+        event = ANNOTATION_LAYERS[AnnotationType.EVENT]
+        event["value"] = event_layer.id
+        annotation_layers.append(event)
+
+        rv = self.post_assert_metric(CHART_DATA_URI, request_payload, "data")
+        self.assertEqual(rv.status_code, 200)
+        data = json.loads(rv.data.decode("utf-8"))
+        # response should only contain interval and event data, not formula
+        self.assertEqual(len(data["result"][0]["annotation_data"]), 2)
diff --git a/tests/fixtures/query_context.py b/tests/fixtures/query_context.py
index 3881e57..21bf2b5 100644
--- a/tests/fixtures/query_context.py
+++ b/tests/fixtures/query_context.py
@@ -17,6 +17,8 @@
 import copy
 from typing import Any, Dict, List
 
+from superset.utils.core import AnnotationType
+
 QUERY_OBJECTS = {
     "birth_names": {
         "extras": {"where": "", "time_range_endpoints": ["inclusive", 
"exclusive"]},
@@ -37,6 +39,77 @@ QUERY_OBJECTS = {
     }
 }
 
+ANNOTATION_LAYERS = {
+    AnnotationType.FORMULA: {
+        "annotationType": "FORMULA",
+        "color": "#ff7f44",
+        "hideLine": False,
+        "name": "my formula",
+        "opacity": "",
+        "overrides": {"time_range": None},
+        "show": True,
+        "showMarkers": False,
+        "sourceType": "",
+        "style": "solid",
+        "value": "3+x",
+        "width": 5,
+    },
+    AnnotationType.EVENT: {
+        "name": "my event",
+        "annotationType": "EVENT",
+        "sourceType": "NATIVE",
+        "color": "#e04355",
+        "opacity": "",
+        "style": "solid",
+        "width": 5,
+        "showMarkers": False,
+        "hideLine": False,
+        "value": 1,
+        "overrides": {"time_range": None},
+        "show": True,
+        "titleColumn": "",
+        "descriptionColumns": [],
+        "timeColumn": "",
+        "intervalEndColumn": "",
+    },
+    AnnotationType.INTERVAL: {
+        "name": "my interval",
+        "annotationType": "INTERVAL",
+        "sourceType": "NATIVE",
+        "color": "#e04355",
+        "opacity": "",
+        "style": "solid",
+        "width": 1,
+        "showMarkers": False,
+        "hideLine": False,
+        "value": 1,
+        "overrides": {"time_range": None},
+        "show": True,
+        "titleColumn": "",
+        "descriptionColumns": [],
+        "timeColumn": "",
+        "intervalEndColumn": "",
+    },
+    AnnotationType.TIME_SERIES: {
+        "annotationType": "TIME_SERIES",
+        "color": None,
+        "descriptionColumns": [],
+        "hideLine": False,
+        "intervalEndColumn": "",
+        "name": "my line",
+        "opacity": "",
+        "overrides": {"time_range": None},
+        "show": True,
+        "showMarkers": False,
+        "sourceType": "line",
+        "style": "dashed",
+        "timeColumn": "",
+        "titleColumn": "",
+        "value": 837,
+        "width": 5,
+    },
+}
+
 POSTPROCESSING_OPERATIONS = {
     "birth_names": [
         {

Reply via email to