Browse Source

Revert "feat(dashboards) Adapt API to new behavior (#22039)" (#22088)

This reverts commit 0a4d3a3e50843420c781aec06c3dc7811bd29d19.
The getsentry/master builds are failing on travis (but not GitHub
Actions) and I don't want to hold up master.
Mark Story 4 years ago
parent
commit
ed3938a462

+ 1 - 1
migrations_lockfile.txt

@@ -10,7 +10,7 @@ auth: 0008_alter_user_username_max_length
 contenttypes: 0002_remove_content_type_name
 contenttypes: 0002_remove_content_type_name
 jira_ac: 0001_initial
 jira_ac: 0001_initial
 nodestore: 0001_initial
 nodestore: 0001_initial
-sentry: 0128_change_dashboards
+sentry: 0127_backfill_platformexternalissue_project_id
 sessions: 0001_initial
 sessions: 0001_initial
 sites: 0002_alter_domain_unique
 sites: 0002_alter_domain_unique
 social_auth: 0001_initial
 social_auth: 0001_initial

+ 3 - 3
src/sentry/api/bases/dashboard.py

@@ -2,7 +2,7 @@ from __future__ import absolute_import
 
 
 from sentry.api.bases.organization import OrganizationEndpoint
 from sentry.api.bases.organization import OrganizationEndpoint
 from sentry.api.exceptions import ResourceDoesNotExist
 from sentry.api.exceptions import ResourceDoesNotExist
-from sentry.models import Dashboard, DashboardWidget
+from sentry.models import Dashboard, Widget
 
 
 
 
 class OrganizationDashboardEndpoint(OrganizationEndpoint):
 class OrganizationDashboardEndpoint(OrganizationEndpoint):
@@ -32,10 +32,10 @@ class OrganizationDashboardWidgetEndpoint(OrganizationDashboardEndpoint):
             kwargs["widget"] = self._get_widget(
             kwargs["widget"] = self._get_widget(
                 request, kwargs["organization"], dashboard_id, widget_id
                 request, kwargs["organization"], dashboard_id, widget_id
             )
             )
-        except DashboardWidget.DoesNotExist:
+        except Widget.DoesNotExist:
             raise ResourceDoesNotExist
             raise ResourceDoesNotExist
 
 
         return (args, kwargs)
         return (args, kwargs)
 
 
     def _get_widget(self, request, organization, dashboard_id, widget_id):
     def _get_widget(self, request, organization, dashboard_id, widget_id):
-        return DashboardWidget.objects.get(id=widget_id, dashboard_id=dashboard_id)
+        return Widget.objects.get(id=widget_id, dashboard_id=dashboard_id)

+ 15 - 6
src/sentry/api/endpoints/organization_dashboard_details.py

@@ -11,7 +11,7 @@ from sentry.api.serializers.rest_framework import (
     ListField,
     ListField,
     ValidationError,
     ValidationError,
 )
 )
-from sentry.models import ObjectStatus, DashboardWidget
+from sentry.models import ObjectStatus, Widget
 
 
 
 
 def remove_widgets(dashboard_widgets, widget_data):
 def remove_widgets(dashboard_widgets, widget_data):
@@ -31,9 +31,12 @@ def reorder_widgets(dashboard_id, widget_data):
     i.e if order of widgets is 1, 2, 3
     i.e if order of widgets is 1, 2, 3
         the reordered widgets will have order 4, 5, 6
         the reordered widgets will have order 4, 5, 6
     """
     """
-    dashboard_widgets = DashboardWidget.objects.filter(dashboard_id=dashboard_id)
+    dashboard_widgets = Widget.objects.filter(dashboard_id=dashboard_id)
     dashboard_widgets = list(remove_widgets(dashboard_widgets, widget_data))
     dashboard_widgets = list(remove_widgets(dashboard_widgets, widget_data))
 
 
+    # dashboard_widgets and widget_data should now have the same widgets
+    widget_data.sort(key=lambda x: x["order"])
+
     next_order = get_next_dashboard_order(dashboard_id)
     next_order = get_next_dashboard_order(dashboard_id)
     for index, data in enumerate(widget_data):
     for index, data in enumerate(widget_data):
         for widget in dashboard_widgets:
         for widget in dashboard_widgets:
@@ -43,17 +46,23 @@ def reorder_widgets(dashboard_id, widget_data):
                 break
                 break
 
 
 
 
-class DashboardWidgetSerializer(serializers.Serializer):
+class WidgetSerializer(serializers.Serializer):
+    order = serializers.IntegerField(min_value=0, required=True)
     id = serializers.IntegerField(min_value=0, required=True)
     id = serializers.IntegerField(min_value=0, required=True)
 
 
 
 
 class DashboardWithWidgetsSerializer(serializers.Serializer):
 class DashboardWithWidgetsSerializer(serializers.Serializer):
     title = serializers.CharField(required=False, allow_blank=True, allow_null=True)
     title = serializers.CharField(required=False, allow_blank=True, allow_null=True)
-    widgets = ListField(child=DashboardWidgetSerializer(), required=False, allow_null=True)
+    widgets = ListField(child=WidgetSerializer(), required=False, allow_null=True)
 
 
     def validate_widgets(self, widgets):
     def validate_widgets(self, widgets):
-        widgets_count = DashboardWidget.objects.filter(
-            id__in=[w["id"] for w in widgets], dashboard_id=self.context["dashboard_id"],
+        if len(widgets) != len(set([w["order"] for w in widgets])):
+            raise ValidationError("Widgets must not have duplicate order values.")
+
+        widgets_count = Widget.objects.filter(
+            id__in=[w["id"] for w in widgets],
+            dashboard_id=self.context["dashboard_id"],
+            status=ObjectStatus.VISIBLE,
         ).count()
         ).count()
 
 
         if len(widgets) != widgets_count:
         if len(widgets) != widgets_count:

+ 13 - 14
src/sentry/api/endpoints/organization_dashboard_widget_details.py

@@ -5,8 +5,8 @@ from rest_framework.response import Response
 
 
 from sentry.api.bases.dashboard import OrganizationDashboardWidgetEndpoint
 from sentry.api.bases.dashboard import OrganizationDashboardWidgetEndpoint
 from sentry.api.serializers import serialize
 from sentry.api.serializers import serialize
-from sentry.api.serializers.rest_framework import DashboardWidgetSerializer
-from sentry.models import DashboardWidgetQuery
+from sentry.api.serializers.rest_framework import WidgetSerializer
+from sentry.models import WidgetDataSource
 
 
 
 
 class OrganizationDashboardWidgetDetailsEndpoint(OrganizationDashboardWidgetEndpoint):
 class OrganizationDashboardWidgetDetailsEndpoint(OrganizationDashboardWidgetEndpoint):
@@ -47,9 +47,8 @@ class OrganizationDashboardWidgetDetailsEndpoint(OrganizationDashboardWidgetEndp
                                 and replaced with the input provided.
                                 and replaced with the input provided.
         :auth: required
         :auth: required
         """
         """
-        serializer = DashboardWidgetSerializer(
-            data=request.data, context={"organization": organization}
-        )
+        # TODO(lb): better document displayType, displayOptions, and dataSources.
+        serializer = WidgetSerializer(data=request.data, context={"organization": organization})
         if not serializer.is_valid():
         if not serializer.is_valid():
             return Response(serializer.errors, status=400)
             return Response(serializer.errors, status=400)
 
 
@@ -59,17 +58,17 @@ class OrganizationDashboardWidgetDetailsEndpoint(OrganizationDashboardWidgetEndp
             widget.update(
             widget.update(
                 title=data.get("title", widget.title),
                 title=data.get("title", widget.title),
                 display_type=data.get("displayType", widget.display_type),
                 display_type=data.get("displayType", widget.display_type),
+                display_options=data.get("displayOptions", widget.display_options),
             )
             )
 
 
-            if "queries" in data:
-                DashboardWidgetQuery.objects.filter(widget_id=widget.id).delete()
-            for i, query in enumerate(data.get("queries", [])):
-                DashboardWidgetQuery.objects.create(
-                    name=query["name"],
-                    fields=query["fields"],
-                    conditions=query["conditions"],
-                    interval=query["interval"],
-                    order=i,
+            if "dataSources" in data:
+                WidgetDataSource.objects.filter(widget_id=widget.id).delete()
+            for widget_data in data.get("dataSources", []):
+                WidgetDataSource.objects.create(
+                    name=widget_data["name"],
+                    data=widget_data["data"],
+                    type=widget_data["type"],
+                    order=widget_data["order"],
                     widget_id=widget.id,
                     widget_id=widget.id,
                 )
                 )
 
 

+ 11 - 16
src/sentry/api/endpoints/organization_dashboard_widgets.py

@@ -5,11 +5,8 @@ from rest_framework.response import Response
 
 
 from sentry.api.bases.dashboard import OrganizationDashboardEndpoint
 from sentry.api.bases.dashboard import OrganizationDashboardEndpoint
 from sentry.api.serializers import serialize
 from sentry.api.serializers import serialize
-from sentry.api.serializers.rest_framework import (
-    get_next_dashboard_order,
-    DashboardWidgetSerializer,
-)
-from sentry.models import DashboardWidget, DashboardWidgetQuery
+from sentry.api.serializers.rest_framework import get_next_dashboard_order, WidgetSerializer
+from sentry.models import Widget, WidgetDataSource
 
 
 
 
 class OrganizationDashboardWidgetsEndpoint(OrganizationDashboardEndpoint):
 class OrganizationDashboardWidgetsEndpoint(OrganizationDashboardEndpoint):
@@ -29,9 +26,7 @@ class OrganizationDashboardWidgetsEndpoint(OrganizationDashboardEndpoint):
         :auth: required
         :auth: required
         """
         """
 
 
-        serializer = DashboardWidgetSerializer(
-            data=request.data, context={"organization": organization}
-        )
+        serializer = WidgetSerializer(data=request.data, context={"organization": organization})
 
 
         if not serializer.is_valid():
         if not serializer.is_valid():
             return Response(serializer.errors, status=400)
             return Response(serializer.errors, status=400)
@@ -40,19 +35,19 @@ class OrganizationDashboardWidgetsEndpoint(OrganizationDashboardEndpoint):
 
 
         try:
         try:
             with transaction.atomic():
             with transaction.atomic():
-                widget = DashboardWidget.objects.create(
+                widget = Widget.objects.create(
                     display_type=result["displayType"],
                     display_type=result["displayType"],
+                    display_options=result.get("displayOptions", {}),
                     title=result["title"],
                     title=result["title"],
                     order=get_next_dashboard_order(dashboard.id),
                     order=get_next_dashboard_order(dashboard.id),
                     dashboard_id=dashboard.id,
                     dashboard_id=dashboard.id,
                 )
                 )
-                for i, query in enumerate(result.get("queries", [])):
-                    DashboardWidgetQuery.objects.create(
-                        name=query["name"],
-                        fields=query["fields"],
-                        conditions=query["conditions"],
-                        interval=query["interval"],
-                        order=i,
+                for widget_data in result.get("dataSources", []):
+                    WidgetDataSource.objects.create(
+                        name=widget_data["name"],
+                        data=widget_data["data"],
+                        type=widget_data["type"],
+                        order=widget_data["order"],
                         widget_id=widget.id,
                         widget_id=widget.id,
                     )
                     )
         except IntegrityError:
         except IntegrityError:

+ 15 - 28
src/sentry/api/serializers/models/organization_dashboard.py

@@ -3,54 +3,47 @@ from __future__ import absolute_import
 import six
 import six
 
 
 from sentry.api.serializers import Serializer, register, serialize
 from sentry.api.serializers import Serializer, register, serialize
-from sentry.models import (
-    Dashboard,
-    DashboardWidget,
-    DashboardWidgetQuery,
-    DashboardWidgetDisplayTypes,
-)
+from sentry.models import Dashboard, Widget, WidgetDataSource, WidgetDisplayTypes
 
 
 
 
-@register(DashboardWidget)
-class DashboardWidgetSerializer(Serializer):
+@register(Widget)
+class WidgetSerializer(Serializer):
     def get_attrs(self, item_list, user):
     def get_attrs(self, item_list, user):
         result = {}
         result = {}
         data_sources = serialize(
         data_sources = serialize(
-            list(
-                DashboardWidgetQuery.objects.filter(
-                    widget_id__in=[i.id for i in item_list]
-                ).order_by("order")
-            )
+            list(WidgetDataSource.objects.filter(widget_id__in=[i.id for i in item_list]))
         )
         )
 
 
         for widget in item_list:
         for widget in item_list:
             widget_data_sources = [
             widget_data_sources = [
                 d for d in data_sources if d["widgetId"] == six.text_type(widget.id)
                 d for d in data_sources if d["widgetId"] == six.text_type(widget.id)
             ]
             ]
-            result[widget] = {"queries": widget_data_sources}
+            result[widget] = {"dataSources": widget_data_sources}
 
 
         return result
         return result
 
 
     def serialize(self, obj, attrs, user, **kwargs):
     def serialize(self, obj, attrs, user, **kwargs):
         return {
         return {
             "id": six.text_type(obj.id),
             "id": six.text_type(obj.id),
+            "order": six.text_type(obj.order),
             "title": obj.title,
             "title": obj.title,
-            "displayType": DashboardWidgetDisplayTypes.get_type_name(obj.display_type),
+            "displayType": WidgetDisplayTypes.get_type_name(obj.display_type),
+            "displayOptions": obj.display_options,
             "dateCreated": obj.date_added,
             "dateCreated": obj.date_added,
             "dashboardId": six.text_type(obj.dashboard_id),
             "dashboardId": six.text_type(obj.dashboard_id),
-            "queries": attrs["queries"],
+            "dataSources": attrs["dataSources"],
         }
         }
 
 
 
 
-@register(DashboardWidgetQuery)
-class DashboardWidgetQuerySerializer(Serializer):
+@register(WidgetDataSource)
+class WidgetDataSourceSerializer(Serializer):
     def serialize(self, obj, attrs, user, **kwargs):
     def serialize(self, obj, attrs, user, **kwargs):
         return {
         return {
             "id": six.text_type(obj.id),
             "id": six.text_type(obj.id),
+            "type": obj.type,
             "name": obj.name,
             "name": obj.name,
-            "fields": obj.fields,
-            "conditions": six.text_type(obj.conditions),
-            "interval": six.text_type(obj.interval),
+            "data": obj.data,
+            "order": six.text_type(obj.order),
             "widgetId": six.text_type(obj.widget_id),
             "widgetId": six.text_type(obj.widget_id),
         }
         }
 
 
@@ -60,13 +53,7 @@ class DashboardWithWidgetsSerializer(Serializer):
     def get_attrs(self, item_list, user):
     def get_attrs(self, item_list, user):
         result = {}
         result = {}
 
 
-        widgets = serialize(
-            list(
-                DashboardWidget.objects.filter(dashboard_id__in=[i.id for i in item_list]).order_by(
-                    "order"
-                )
-            )
-        )
+        widgets = serialize(list(Widget.objects.filter(dashboard_id__in=[i.id for i in item_list])))
 
 
         for dashboard in item_list:
         for dashboard in item_list:
             dashboard_widgets = [
             dashboard_widgets = [

+ 0 - 37
src/sentry/api/serializers/rest_framework/dashboard_widget.py

@@ -1,37 +0,0 @@
-from __future__ import absolute_import
-
-from django.db.models import Max
-from rest_framework import serializers
-
-from sentry.api.serializers.rest_framework import ListField
-from sentry.models import DashboardWidget, DashboardWidgetDisplayTypes
-
-
-def get_next_dashboard_order(dashboard_id):
-    max_order = DashboardWidget.objects.filter(dashboard_id=dashboard_id).aggregate(Max("order"))[
-        "order__max"
-    ]
-
-    return max_order + 1 if max_order else 1
-
-
-class DashboardWidgetQuerySerializer(serializers.Serializer):
-    name = serializers.CharField(required=True)
-    fields = ListField(child=serializers.CharField(), required=True)
-    conditions = serializers.CharField(required=True)
-    interval = serializers.CharField()
-
-    # TODO validate fields, conditions and interval values.
-
-
-class DashboardWidgetSerializer(serializers.Serializer):
-    title = serializers.CharField(required=True)
-    displayType = serializers.ChoiceField(
-        choices=DashboardWidgetDisplayTypes.as_text_choices(), required=True
-    )
-    queries = ListField(
-        child=DashboardWidgetQuerySerializer(required=False), required=False, allow_null=True
-    )
-
-    def validate_displayType(self, display_type):
-        return DashboardWidgetDisplayTypes.get_id_for_type_name(display_type)

+ 54 - 0
src/sentry/api/serializers/rest_framework/widget.py

@@ -0,0 +1,54 @@
+from __future__ import absolute_import
+
+from django.db.models import Max
+from rest_framework import serializers
+
+from sentry.discover.endpoints.serializers import DiscoverSavedQuerySerializer
+from sentry.api.serializers.rest_framework import JSONField, ListField, ValidationError
+from sentry.models import Widget, WidgetDisplayTypes, WidgetDataSourceTypes
+
+
+def get_next_dashboard_order(dashboard_id):
+    max_order = Widget.objects.filter(dashboard_id=dashboard_id).aggregate(Max("order"))[
+        "order__max"
+    ]
+
+    return max_order + 1 if max_order else 1
+
+
+class WidgetDataSourceSerializer(serializers.Serializer):
+    name = serializers.CharField(required=True)
+    data = JSONField(required=True)
+    type = serializers.CharField(required=True)
+    order = serializers.IntegerField(required=True)
+
+    def validate_type(self, type):
+        if type not in WidgetDataSourceTypes.TYPE_NAMES:
+            raise ValidationError("Widget data source type %s not recognized." % type)
+        type = WidgetDataSourceTypes.get_id_for_type_name(type)
+        return type
+
+    def validate(self, data):
+        super(WidgetDataSourceSerializer, self).validate(data)
+        if data["type"] == WidgetDataSourceTypes.DISCOVER_SAVED_SEARCH:
+            serializer = DiscoverSavedQuerySerializer(data=data["data"], context=self.context)
+            if not serializer.is_valid():
+                raise ValidationError("Error validating DiscoverSavedQuery: %s" % serializer.errors)
+        else:
+            raise ValidationError("Widget data source type %s not recognized." % data["type"])
+        return data
+
+
+class WidgetSerializer(serializers.Serializer):
+    displayType = serializers.CharField(required=True)
+    displayOptions = JSONField(required=False)
+    title = serializers.CharField(required=True)
+    dataSources = ListField(
+        child=WidgetDataSourceSerializer(required=False), required=False, allow_null=True
+    )
+
+    def validate_displayType(self, display_type):
+        if display_type not in WidgetDisplayTypes.TYPE_NAMES:
+            raise ValidationError("Widget displayType %s not recognized." % display_type)
+
+        return WidgetDisplayTypes.get_id_for_type_name(display_type)

+ 0 - 102
src/sentry/migrations/0128_change_dashboards.py

@@ -1,102 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.29 on 2020-11-13 20:33
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.db.models.deletion
-import django.utils.timezone
-import sentry.db.models.fields.array
-import sentry.db.models.fields.bounded
-import sentry.db.models.fields.foreignkey
-
-
-class Migration(migrations.Migration):
-    # This flag is used to mark that a migration shouldn't be automatically run in
-    # production. We set this to True for operations that we think are risky and want
-    # someone from ops to run manually and monitor.
-    # General advice is that if in doubt, mark your migration as `is_dangerous`.
-    # Some things you should always mark as dangerous:
-    # - Large data migrations. Typically we want these to be run manually by ops so that
-    #   they can be monitored. Since data migrations will now hold a transaction open
-    #   this is even more important.
-    # - Adding columns to highly active tables, even ones that are NULL.
-    is_dangerous = False
-
-    # This flag is used to decide whether to run this migration in a transaction or not.
-    # By default we prefer to run in a transaction, but for migrations where you want
-    # to `CREATE INDEX CONCURRENTLY` this needs to be set to False. Typically you'll
-    # want to create an index concurrently when adding one to an existing table.
-    atomic = True
-
-
-    dependencies = [
-        ('sentry', '0127_backfill_platformexternalissue_project_id'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='DashboardWidget',
-            fields=[
-                ('id', sentry.db.models.fields.bounded.BoundedBigAutoField(primary_key=True, serialize=False)),
-                ('order', sentry.db.models.fields.bounded.BoundedPositiveIntegerField()),
-                ('title', models.CharField(max_length=255)),
-                ('display_type', sentry.db.models.fields.bounded.BoundedPositiveIntegerField(choices=[(0, 'line'), (1, 'area'), (2, 'stacked_area'), (3, 'bar'), (4, 'table')])),
-                ('date_added', models.DateTimeField(default=django.utils.timezone.now)),
-                ('dashboard', sentry.db.models.fields.foreignkey.FlexibleForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sentry.Dashboard')),
-            ],
-            options={
-                'db_table': 'sentry_dashboardwidget',
-            },
-        ),
-        migrations.CreateModel(
-            name='DashboardWidgetQuery',
-            fields=[
-                ('id', sentry.db.models.fields.bounded.BoundedBigAutoField(primary_key=True, serialize=False)),
-                ('name', models.CharField(max_length=255)),
-                ('fields', sentry.db.models.fields.array.ArrayField(null=True)),
-                ('conditions', models.TextField()),
-                ('interval', models.CharField(max_length=10)),
-                ('order', sentry.db.models.fields.bounded.BoundedPositiveIntegerField()),
-                ('date_added', models.DateTimeField(default=django.utils.timezone.now)),
-                ('widget', sentry.db.models.fields.foreignkey.FlexibleForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sentry.DashboardWidget')),
-            ],
-            options={
-                'db_table': 'sentry_dashboardwidgetquery',
-            },
-        ),
-        migrations.AlterUniqueTogether(
-            name='dashboardwidgetquery',
-            unique_together=set([('widget', 'order'), ('widget', 'name')]),
-        ),
-        migrations.AlterUniqueTogether(
-            name='dashboardwidget',
-            unique_together=set([('dashboard', 'title'), ('dashboard', 'order')]),
-        ),
-        migrations.SeparateDatabaseAndState(
-            database_operations=[],
-            state_operations=[
-                migrations.AlterUniqueTogether(
-                    name='widget',
-                    unique_together=set([]),
-                ),
-                migrations.RemoveField(
-                    model_name='widget',
-                    name='dashboard',
-                ),
-                migrations.AlterUniqueTogether(
-                    name='widgetdatasource',
-                    unique_together=set([]),
-                ),
-                migrations.RemoveField(
-                    model_name='widgetdatasource',
-                    name='widget',
-                ),
-                migrations.DeleteModel(
-                    name='Widget',
-                ),
-                migrations.DeleteModel(
-                    name='WidgetDataSource',
-                ),
-            ]
-        )
-    ]

+ 32 - 17
src/sentry/models/dashboard_widget.py → src/sentry/models/widget.py

@@ -4,10 +4,11 @@ import six
 from django.db import models
 from django.db import models
 from django.utils import timezone
 from django.utils import timezone
 
 
+from sentry.constants import ObjectStatus
 from sentry.db.models import (
 from sentry.db.models import (
     BoundedPositiveIntegerField,
     BoundedPositiveIntegerField,
     FlexibleForeignKey,
     FlexibleForeignKey,
-    ArrayField,
+    JSONField,
     Model,
     Model,
     sane_repr,
     sane_repr,
 )
 )
@@ -20,10 +21,6 @@ class TypesClass(object):
     def as_choices(cls):
     def as_choices(cls):
         return [(k, six.text_type(v)) for k, v in cls.TYPES]
         return [(k, six.text_type(v)) for k, v in cls.TYPES]
 
 
-    @classmethod
-    def as_text_choices(cls):
-        return [(six.text_type(v), six.text_type(v)) for _, v in cls.TYPES]
-
     @classmethod
     @classmethod
     def get_type_name(cls, num):
     def get_type_name(cls, num):
         for id, name in cls.TYPES:
         for id, name in cls.TYPES:
@@ -37,46 +34,60 @@ class TypesClass(object):
                 return id
                 return id
 
 
 
 
-class DashboardWidgetDisplayTypes(TypesClass):
+class WidgetDisplayTypes(TypesClass):
     LINE_CHART = 0
     LINE_CHART = 0
     AREA_CHART = 1
     AREA_CHART = 1
     STACKED_AREA_CHART = 2
     STACKED_AREA_CHART = 2
     BAR_CHART = 3
     BAR_CHART = 3
-    TABLE = 4
+    PIE_CHART = 4
+    TABLE = 5
+    WORLD_MAP = 6
+    PERCENTAGE_AREA_CHART = 7
     TYPES = [
     TYPES = [
         (LINE_CHART, "line"),
         (LINE_CHART, "line"),
         (AREA_CHART, "area"),
         (AREA_CHART, "area"),
         (STACKED_AREA_CHART, "stacked_area"),
         (STACKED_AREA_CHART, "stacked_area"),
         (BAR_CHART, "bar"),
         (BAR_CHART, "bar"),
+        (PIE_CHART, "pie"),
         (TABLE, "table"),
         (TABLE, "table"),
+        (WORLD_MAP, "world_map"),
+        (PERCENTAGE_AREA_CHART, "percentage_area_chart"),
     ]
     ]
     TYPE_NAMES = [t[1] for t in TYPES]
     TYPE_NAMES = [t[1] for t in TYPES]
 
 
 
 
-class DashboardWidgetQuery(Model):
+class WidgetDataSourceTypes(TypesClass):
+    DISCOVER_SAVED_SEARCH = 0
+    TYPES = [(DISCOVER_SAVED_SEARCH, "discover_saved_search")]
+    TYPE_NAMES = [t[1] for t in TYPES]
+
+
+class WidgetDataSource(Model):
     """
     """
-    A query in a dashboard widget.
+    A dashboard widget.
     """
     """
 
 
     __core__ = True
     __core__ = True
 
 
-    widget = FlexibleForeignKey("sentry.DashboardWidget")
+    widget = FlexibleForeignKey("sentry.Widget")
+    type = BoundedPositiveIntegerField(choices=WidgetDataSourceTypes.as_choices())
     name = models.CharField(max_length=255)
     name = models.CharField(max_length=255)
-    fields = ArrayField()
-    conditions = models.TextField()
-    interval = models.CharField(max_length=10)
+    data = JSONField(default={})  # i.e. saved discover query
     order = BoundedPositiveIntegerField()
     order = BoundedPositiveIntegerField()
     date_added = models.DateTimeField(default=timezone.now)
     date_added = models.DateTimeField(default=timezone.now)
+    status = BoundedPositiveIntegerField(
+        default=ObjectStatus.VISIBLE, choices=ObjectStatus.as_choices()
+    )
 
 
     class Meta:
     class Meta:
         app_label = "sentry"
         app_label = "sentry"
-        db_table = "sentry_dashboardwidgetquery"
+        db_table = "sentry_widgetdatasource"
         unique_together = (("widget", "name"), ("widget", "order"))
         unique_together = (("widget", "name"), ("widget", "order"))
 
 
     __repr__ = sane_repr("widget", "type", "name")
     __repr__ = sane_repr("widget", "type", "name")
 
 
 
 
-class DashboardWidget(Model):
+class Widget(Model):
     """
     """
     A dashboard widget.
     A dashboard widget.
     """
     """
@@ -86,12 +97,16 @@ class DashboardWidget(Model):
     dashboard = FlexibleForeignKey("sentry.Dashboard")
     dashboard = FlexibleForeignKey("sentry.Dashboard")
     order = BoundedPositiveIntegerField()
     order = BoundedPositiveIntegerField()
     title = models.CharField(max_length=255)
     title = models.CharField(max_length=255)
-    display_type = BoundedPositiveIntegerField(choices=DashboardWidgetDisplayTypes.as_choices())
+    display_type = BoundedPositiveIntegerField(choices=WidgetDisplayTypes.as_choices())
+    display_options = JSONField(default={})
     date_added = models.DateTimeField(default=timezone.now)
     date_added = models.DateTimeField(default=timezone.now)
+    status = BoundedPositiveIntegerField(
+        default=ObjectStatus.VISIBLE, choices=ObjectStatus.as_choices()
+    )
 
 
     class Meta:
     class Meta:
         app_label = "sentry"
         app_label = "sentry"
-        db_table = "sentry_dashboardwidget"
+        db_table = "sentry_widget"
         unique_together = (("dashboard", "order"), ("dashboard", "title"))
         unique_together = (("dashboard", "order"), ("dashboard", "title"))
 
 
     __repr__ = sane_repr("dashboard", "title")
     __repr__ = sane_repr("dashboard", "title")

Some files were not shown because too many files changed in this diff