chore: improve analytics (#11714)

* chore: improve analytics

* lint

* log more events, add note in UPDATING.md

* handling base class

* more events\!

* get ref through

* right before @expose

* fix context

* touchups
This commit is contained in:
Maxime Beauchemin 2020-11-25 08:45:02 -08:00 committed by GitHub
parent 9215a31fa2
commit 0504cf1a00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 229 additions and 50 deletions

View File

@ -25,7 +25,10 @@ assists people when migrating to a new version.
## Next
- [11704](https://github.com/apache/incubator-superset/pull/11704) Breaking change: Jinja templating for SQL queries has been updated, removing default modules such as `datetime` and `random` and enforcing static template values. To restore or extend functionality, use `JINJA_CONTEXT_ADDONS` and `CUSTOM_TEMPLATE_PROCESSORS` in `superset_config.py`.
- [11714](https://github.com/apache/incubator-superset/pull/11714): Logs
significantly more analytics events (roughly double?), and when
using DBEventLogger (default) could result in stressing the metadata
database more.
- [11509](https://github.com/apache/incubator-superset/pull/11509): Config value `TABLE_NAMES_CACHE_CONFIG` has been renamed to `DATA_CACHE_CONFIG`, which will now also hold query results cache from connected datasources (previously held in `CACHE_CONFIG`), in addition to the table names. If you will set `DATA_CACHE_CONFIG` to a new cache backend different than your previous `CACHE_CONFIG`, plan for additional cache warmup to avoid degrading charting performance for the end users.
- [11575](https://github.com/apache/incubator-superset/pull/11575) The Row Level Security (RLS) config flag has been moved to a feature flag. To migrate, add `ROW_LEVEL_SECURITY: True` to the `FEATURE_FLAGS` dict in `superset_config.py`.

View File

@ -47,6 +47,7 @@ from superset.annotation_layers.schemas import (
openapi_spec_methods_override,
)
from superset.constants import RouteMethod
from superset.extensions import event_logger
from superset.models.annotations import AnnotationLayer
from superset.views.base_api import BaseSupersetModelRestApi, statsd_metrics
@ -110,6 +111,7 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@permission_name("delete")
@event_logger.log_this_with_context(log_to_statsd=False)
def delete(self, pk: int) -> Response:
"""Delete an annotation layer
---
@ -159,6 +161,7 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@permission_name("post")
@event_logger.log_this_with_context(log_to_statsd=False)
def post(self) -> Response:
"""Creates a new Annotation Layer
---
@ -218,6 +221,7 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@permission_name("put")
@event_logger.log_this_with_context(log_to_statsd=False)
def put(self, pk: int) -> Response:
"""Updates an Annotation Layer
---
@ -284,6 +288,7 @@ class AnnotationLayerRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_delete_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def bulk_delete(self, **kwargs: Any) -> Response:
"""Delete bulk Annotation layers
---

View File

@ -45,10 +45,10 @@ class CacheRestApi(BaseSupersetModelRestApi):
openapi_spec_component_schemas = (CacheInvalidationRequestSchema,)
@expose("/invalidate", methods=["POST"])
@event_logger.log_this
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def invalidate(self) -> Response:
"""
Takes a list of datasources, finds the associated cache records and

View File

@ -214,6 +214,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def post(self) -> Response:
"""Creates a new Chart
---
@ -270,6 +271,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def put(self, pk: int) -> Response:
"""Changes a Chart
---
@ -343,6 +345,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def delete(self, pk: int) -> Response:
"""Deletes a Chart
---
@ -393,6 +396,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_delete_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def bulk_delete(self, **kwargs: Any) -> Response:
"""Delete bulk Charts
---
@ -444,10 +448,10 @@ class ChartRestApi(BaseSupersetModelRestApi):
return self.response_422(message=str(ex))
@expose("/data", methods=["POST"])
@event_logger.log_this
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def data(self) -> Response:
"""
Takes a query context constructed in the client and returns payload
@ -532,6 +536,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@rison(screenshot_query_schema)
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def cache_screenshot(self, pk: int, **kwargs: Dict[str, bool]) -> WerkzeugResponse:
"""
---
@ -604,6 +609,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@rison(screenshot_query_schema)
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def screenshot(self, pk: int, digest: str) -> WerkzeugResponse:
"""Get Chart screenshot
---
@ -657,6 +663,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@rison(thumbnail_query_schema)
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def thumbnail(
self, pk: int, digest: str, **kwargs: Dict[str, bool]
) -> WerkzeugResponse:
@ -730,6 +737,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_export_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def export(self, **kwargs: Any) -> Response:
"""Export charts
---
@ -787,6 +795,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_fav_star_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def favorite_status(self, **kwargs: Any) -> Response:
"""Favorite stars for Charts
---

View File

@ -33,6 +33,7 @@ from superset.css_templates.schemas import (
get_delete_ids_schema,
openapi_spec_methods_override,
)
from superset.extensions import event_logger
from superset.models.core import CssTemplate
from superset.views.base_api import BaseSupersetModelRestApi, statsd_metrics
@ -87,6 +88,7 @@ class CssTemplateRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_delete_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def bulk_delete(self, **kwargs: Any) -> Response:
"""Delete bulk CSS Templates
---

View File

@ -63,6 +63,7 @@ from superset.dashboards.schemas import (
openapi_spec_methods_override,
thumbnail_query_schema,
)
from superset.extensions import event_logger
from superset.models.dashboard import Dashboard
from superset.tasks.thumbnails import cache_dashboard_thumbnail
from superset.utils.screenshots import DashboardScreenshot
@ -209,6 +210,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def post(self) -> Response:
"""Creates a new Dashboard
---
@ -267,6 +269,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def put(self, pk: int) -> Response:
"""Changes a Dashboard
---
@ -337,6 +340,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def delete(self, pk: int) -> Response:
"""Deletes a Dashboard
---
@ -387,6 +391,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_delete_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def bulk_delete(self, **kwargs: Any) -> Response:
"""Delete bulk Dashboards
---
@ -444,6 +449,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_export_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def export(self, **kwargs: Any) -> Response:
"""Export dashboards
---
@ -519,6 +525,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@rison(thumbnail_query_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def thumbnail(
self, pk: int, digest: str, **kwargs: Dict[str, bool]
) -> WerkzeugResponse:
@ -606,6 +613,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_fav_star_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def favorite_status(self, **kwargs: Any) -> Response:
"""Favorite Stars for Dashboards
---

View File

@ -185,6 +185,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def post(self) -> Response:
"""Creates a new Database
---
@ -247,6 +248,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def put( # pylint: disable=too-many-return-statements, arguments-differ
self, pk: int
) -> Response:
@ -320,6 +322,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def delete(self, pk: int) -> Response: # pylint: disable=arguments-differ
"""Deletes a Database
---
@ -370,6 +373,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@safe
@rison(database_schemas_query_schema)
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def schemas(self, pk: int, **kwargs: Any) -> FlaskResponse:
"""Get all schemas from a database
---
@ -423,8 +427,8 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@protect()
@check_datasource_access
@safe
@event_logger.log_this
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def table_metadata(
self, database: Database, table_name: str, schema_name: str
) -> FlaskResponse:
@ -480,8 +484,8 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@protect()
@check_datasource_access
@safe
@event_logger.log_this
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def select_star(
self, database: Database, table_name: str, schema_name: Optional[str] = None
) -> FlaskResponse:
@ -537,8 +541,8 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@expose("/test_connection", methods=["POST"])
@protect()
@safe
@event_logger.log_this
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def test_connection( # pylint: disable=too-many-return-statements
self,
) -> FlaskResponse:
@ -618,6 +622,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def related_objects(self, pk: int) -> Response:
"""Get charts and dashboards count associated to a database
---
@ -676,6 +681,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_export_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def export(self, **kwargs: Any) -> Response:
"""Export database(s) with associated datasets
---

View File

@ -27,7 +27,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_babel import ngettext
from marshmallow import ValidationError
from superset import is_feature_enabled
from superset import event_logger, is_feature_enabled
from superset.commands.exceptions import CommandInvalidError
from superset.connectors.sqla.models import SqlaTable
from superset.constants import RouteMethod
@ -182,6 +182,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def post(self) -> Response:
"""Creates a new Dataset
---
@ -238,6 +239,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def put(self, pk: int) -> Response:
"""Changes a Dataset
---
@ -308,6 +310,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def delete(self, pk: int) -> Response:
"""Deletes a Dataset
---
@ -358,6 +361,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_export_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def export(self, **kwargs: Any) -> Response:
"""Export datasets
---
@ -433,6 +437,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def refresh(self, pk: int) -> Response:
"""Refresh a Dataset
---
@ -482,6 +487,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
@protect()
@safe
@statsd_metrics
@event_logger.log_this_with_context(log_to_statsd=False)
def related_objects(self, pk: int) -> Response:
"""Get charts and dashboards count associated to a dataset
---
@ -540,6 +546,7 @@ class DatasetRestApi(BaseSupersetModelRestApi):
@safe
@statsd_metrics
@rison(get_delete_ids_schema)
@event_logger.log_this_with_context(log_to_statsd=False)
def bulk_delete(self, **kwargs: Any) -> Response:
"""Delete bulk Datasets
---

View File

@ -0,0 +1,45 @@
# 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.
"""Add path to logs
Revision ID: a8173232b786
Revises: 49b5a32daba5
Create Date: 2020-11-15 16:08:24.580764
"""
# revision identifiers, used by Alembic.
revision = "a8173232b786"
down_revision = "49b5a32daba5"
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import mysql
def upgrade():
op.add_column("logs", sa.Column("path", sa.String(length=256), nullable=True))
op.add_column(
"logs", sa.Column("path_no_int", sa.String(length=256), nullable=True)
)
op.add_column("logs", sa.Column("ref", sa.String(length=256), nullable=True))
def downgrade():
op.drop_column("logs", "path")
op.drop_column("logs", "path_no_int")
op.drop_column("logs", "ref")

View File

@ -715,6 +715,9 @@ class Log(Model): # pylint: disable=too-few-public-methods
dttm = Column(DateTime, default=datetime.utcnow)
duration_ms = Column(Integer)
referrer = Column(String(1024))
path = Column(String(256))
path_no_int = Column(String(256))
ref = Column(String(256))
class FavStarClassName(str, Enum):

View File

@ -30,15 +30,35 @@ from sqlalchemy.exc import SQLAlchemyError
from superset.stats_logger import BaseStatsLogger
def strip_int_from_path(path: Optional[str]) -> str:
"""Simple function to remove ints from '/' separated paths"""
if path:
return "/".join(["<int>" if s.isdigit() else s for s in path.split("/")])
return ""
class AbstractEventLogger(ABC):
@abstractmethod
def log(
self, user_id: Optional[int], action: str, *args: Any, **kwargs: Any
def log( # pylint: disable=too-many-arguments
self,
user_id: Optional[int],
action: str,
dashboard_id: Optional[int],
duration_ms: Optional[int],
slice_id: Optional[int],
path: Optional[str],
path_no_int: Optional[str],
ref: Optional[str],
referrer: Optional[str],
*args: Any,
**kwargs: Any,
) -> None:
pass
@contextmanager
def log_context(self, action: str) -> Iterator[Callable[..., None]]:
def log_context(
self, action: str, ref: Optional[str] = None, log_to_statsd: bool = True,
) -> Iterator[Callable[..., None]]:
"""
Log an event while reading information from the request context.
`kwargs` will be appended directly to the log payload.
@ -69,7 +89,8 @@ class AbstractEventLogger(ABC):
except (TypeError, ValueError):
slice_id = 0
self.stats_logger.incr(action)
if log_to_statsd:
self.stats_logger.incr(action)
# bulk insert
try:
@ -86,18 +107,38 @@ class AbstractEventLogger(ABC):
slice_id=slice_id,
duration_ms=round((time.time() - start_time) * 1000),
referrer=referrer,
path=request.path,
path_no_int=strip_int_from_path(request.path),
ref=ref,
)
def log_this(self, f: Callable[..., Any]) -> Callable[..., Any]:
def _wrapper(
self, f: Callable[..., Any], **wrapper_kwargs: Any
) -> Callable[..., Any]:
action_str = wrapper_kwargs.get("action") or f.__name__
ref = f.__qualname__ if hasattr(f, "__qualname__") else None
@functools.wraps(f)
def wrapper(*args: Any, **kwargs: Any) -> Any:
with self.log_context(f.__name__) as log:
with self.log_context(action_str, ref, **wrapper_kwargs) as log:
value = f(*args, **kwargs)
log(**kwargs)
return value
return wrapper
def log_this(self, f: Callable[..., Any]) -> Callable[..., Any]:
"""Decorator that uses the function name as the action"""
return self._wrapper(f)
def log_this_with_context(self, **kwargs: Any) -> Callable[..., Any]:
"""Decorator that can override kwargs of log_context"""
def func(f: Callable[..., Any]) -> Callable[..., Any]:
return self._wrapper(f, **kwargs)
return func
def log_manually(self, f: Callable[..., Any]) -> Callable[..., Any]:
"""Allow a function to manually update"""
@ -162,16 +203,23 @@ def get_event_logger_from_cfg_value(cfg_value: Any) -> AbstractEventLogger:
class DBEventLogger(AbstractEventLogger):
"""Event logger that commits logs to Superset DB"""
def log( # pylint: disable=too-many-locals
self, user_id: Optional[int], action: str, *args: Any, **kwargs: Any
def log( # pylint: disable=too-many-arguments,too-many-locals
self,
user_id: Optional[int],
action: str,
dashboard_id: Optional[int],
duration_ms: Optional[int],
slice_id: Optional[int],
path: Optional[str],
path_no_int: Optional[str],
ref: Optional[str],
referrer: Optional[str],
*args: Any,
**kwargs: Any,
) -> None:
from superset.models.core import Log
records = kwargs.get("records", list())
dashboard_id = kwargs.get("dashboard_id")
slice_id = kwargs.get("slice_id")
duration_ms = kwargs.get("duration_ms")
referrer = kwargs.get("referrer")
logs = list()
for record in records:
@ -188,6 +236,9 @@ class DBEventLogger(AbstractEventLogger):
duration_ms=duration_ms,
referrer=referrer,
user_id=user_id,
path=path,
path_no_int=path_no_int,
ref=ref,
)
logs.append(log)
try:

View File

@ -31,7 +31,7 @@ from marshmallow import fields, Schema
from sqlalchemy import and_, distinct, func
from sqlalchemy.orm.query import Query
from superset.extensions import db, security_manager
from superset.extensions import db, event_logger, security_manager
from superset.models.core import FavStar
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
@ -49,6 +49,7 @@ get_related_schema = {
"filter": {"type": "string"},
},
}
log_context = event_logger.log_context
class RelatedResultResponseSchema(Schema):
@ -312,49 +313,61 @@ class BaseSupersetModelRestApi(ModelRestApi):
"""
Add statsd metrics to builtin FAB _info endpoint
"""
duration, response = time_function(super().info_headless, **kwargs)
self.send_stats_metrics(response, self.info.__name__, duration)
return response
ref = f"{self.__class__.__name__}.info"
with log_context(ref, ref, log_to_statsd=False):
duration, response = time_function(super().info_headless, **kwargs)
self.send_stats_metrics(response, self.info.__name__, duration)
return response
def get_headless(self, pk: int, **kwargs: Any) -> Response:
"""
Add statsd metrics to builtin FAB GET endpoint
"""
duration, response = time_function(super().get_headless, pk, **kwargs)
self.send_stats_metrics(response, self.get.__name__, duration)
return response
ref = f"{self.__class__.__name__}.get"
with log_context(ref, ref, log_to_statsd=False):
duration, response = time_function(super().get_headless, pk, **kwargs)
self.send_stats_metrics(response, self.get.__name__, duration)
return response
def get_list_headless(self, **kwargs: Any) -> Response:
"""
Add statsd metrics to builtin FAB GET list endpoint
"""
duration, response = time_function(super().get_list_headless, **kwargs)
self.send_stats_metrics(response, self.get_list.__name__, duration)
return response
ref = f"{self.__class__.__name__}.get_list"
with log_context(ref, ref, log_to_statsd=False):
duration, response = time_function(super().get_list_headless, **kwargs)
self.send_stats_metrics(response, self.get_list.__name__, duration)
return response
def post_headless(self) -> Response:
"""
Add statsd metrics to builtin FAB POST endpoint
"""
duration, response = time_function(super().post_headless)
self.send_stats_metrics(response, self.post.__name__, duration)
return response
ref = f"{self.__class__.__name__}.post"
with log_context(ref, ref, log_to_statsd=False):
duration, response = time_function(super().post_headless)
self.send_stats_metrics(response, self.post.__name__, duration)
return response
def put_headless(self, pk: int) -> Response:
"""
Add statsd metrics to builtin FAB PUT endpoint
"""
duration, response = time_function(super().put_headless, pk)
self.send_stats_metrics(response, self.put.__name__, duration)
return response
ref = f"{self.__class__.__name__}.put"
with log_context(ref, ref, log_to_statsd=False):
duration, response = time_function(super().put_headless, pk)
self.send_stats_metrics(response, self.put.__name__, duration)
return response
def delete_headless(self, pk: int) -> Response:
"""
Add statsd metrics to builtin FAB DELETE endpoint
"""
duration, response = time_function(super().delete_headless, pk)
self.send_stats_metrics(response, self.delete.__name__, duration)
return response
ref = f"{self.__class__.__name__}.delete"
with log_context(ref, ref, log_to_statsd=False):
duration, response = time_function(super().delete_headless, pk)
self.send_stats_metrics(response, self.delete.__name__, duration)
return response
@expose("/related/<column_name>", methods=["GET"])
@protect()

View File

@ -155,6 +155,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
logger = logging.getLogger(__name__)
@has_access_api
@event_logger.log_this
@expose("/datasources/")
def datasources(self) -> FlaskResponse:
return self.json_response(
@ -169,6 +170,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
)
@has_access_api
@event_logger.log_this
@expose("/override_role_permissions/", methods=["POST"])
def override_role_permissions(self) -> FlaskResponse:
"""Updates the role with the give datasource permissions.
@ -220,8 +222,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
{"granted": granted_perms, "requested": list(db_ds_names)}, status=201
)
@event_logger.log_this
@has_access
@event_logger.log_this
@expose("/request_access/")
def request_access(self) -> FlaskResponse:
datasources = set()
@ -263,8 +265,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
datasource_names=", ".join([o.name for o in datasources]),
)
@event_logger.log_this
@has_access
@event_logger.log_this
@expose("/approve")
def approve(self) -> FlaskResponse: # pylint: disable=too-many-locals,no-self-use
def clean_fulfilled_requests(session: Session) -> None:
@ -368,6 +370,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
return redirect("/accessrequestsmodelview/list/")
@has_access
@event_logger.log_this
@expose("/slice/<int:slice_id>/")
def slice(self, slice_id: int) -> FlaskResponse: # pylint: disable=no-self-use
_, slc = get_form_data(slice_id, use_slice_data=True)
@ -450,9 +453,9 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
except SupersetException as ex:
return json_error_response(utils.error_msg_from_exception(ex))
@event_logger.log_this
@api
@has_access_api
@event_logger.log_this
@expose("/annotation_json/<int:layer_id>")
def annotation_json( # pylint: disable=no-self-use
self, layer_id: int
@ -484,10 +487,10 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
if not is_feature_enabled("ENABLE_EXPLORE_JSON_CSRF_PROTECTION"):
EXPLORE_JSON_METHODS.append("GET")
@event_logger.log_this
@api
@has_access_api
@handle_api_exception
@event_logger.log_this
@expose(
"/explore_json/<datasource_type>/<int:datasource_id>/",
methods=EXPLORE_JSON_METHODS,
@ -535,8 +538,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
except SupersetException as ex:
return json_error_response(utils.error_msg_from_exception(ex), 400)
@event_logger.log_this
@has_access
@event_logger.log_this
@expose("/import_dashboards", methods=["GET", "POST"])
def import_dashboards(self) -> FlaskResponse:
"""Overrides the dashboards using json instances from the file."""
@ -578,8 +581,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
"superset/import_dashboards.html", databases=databases
)
@event_logger.log_this
@has_access
@event_logger.log_this
@expose("/explore/<datasource_type>/<int:datasource_id>/", methods=["GET", "POST"])
@expose("/explore/", methods=["GET", "POST"])
def explore( # pylint: disable=too-many-locals,too-many-return-statements
@ -733,6 +736,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@handle_api_exception
@has_access_api
@event_logger.log_this
@expose("/filter/<datasource_type>/<int:datasource_id>/<column>/")
def filter( # pylint: disable=no-self-use
self, datasource_type: str, datasource_id: int, column: str
@ -881,6 +885,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/schemas/<int:db_id>/")
@expose("/schemas/<int:db_id>/<force_refresh>/")
def schemas( # pylint: disable=no-self-use
@ -905,6 +910,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/tables/<int:db_id>/<schema>/<substr>/")
@expose("/tables/<int:db_id>/<schema>/<substr>/<force_refresh>/")
def tables( # pylint: disable=too-many-locals,no-self-use
@ -1014,6 +1020,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/copy_dash/<int:dashboard_id>/", methods=["GET", "POST"])
def copy_dash( # pylint: disable=no-self-use
self, dashboard_id: int
@ -1063,6 +1070,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/save_dash/<int:dashboard_id>/", methods=["GET", "POST"])
def save_dash( # pylint: disable=no-self-use
self, dashboard_id: int
@ -1101,6 +1109,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/add_slices/<int:dashboard_id>/", methods=["POST"])
def add_slices( # pylint: disable=no-self-use
self, dashboard_id: int
@ -1119,6 +1128,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/testconn", methods=["POST", "GET"])
def testconn( # pylint: disable=too-many-return-statements,no-self-use
self,
@ -1199,6 +1209,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/recent_activity/<int:user_id>/", methods=["GET"])
def recent_activity( # pylint: disable=no-self-use
self, user_id: int
@ -1297,6 +1308,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/csrf_token/", methods=["GET"])
def csrf_token(self) -> FlaskResponse:
return Response(
@ -1305,6 +1317,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/available_domains/", methods=["GET"])
def available_domains(self) -> FlaskResponse: # pylint: disable=no-self-use
"""
@ -1318,6 +1331,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/fave_dashboards_by_username/<username>/", methods=["GET"])
def fave_dashboards_by_username(self, username: str) -> FlaskResponse:
"""This lets us use a user's username to pull favourite dashboards"""
@ -1326,6 +1340,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/fave_dashboards/<int:user_id>/", methods=["GET"])
def fave_dashboards( # pylint: disable=no-self-use
self, user_id: int
@ -1360,6 +1375,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/created_dashboards/<int:user_id>/", methods=["GET"])
def created_dashboards( # pylint: disable=no-self-use
self, user_id: int
@ -1388,6 +1404,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/user_slices", methods=["GET"])
@expose("/user_slices/<int:user_id>/", methods=["GET"])
def user_slices( # pylint: disable=no-self-use
@ -1439,6 +1456,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/created_slices", methods=["GET"])
@expose("/created_slices/<int:user_id>/", methods=["GET"])
def created_slices( # pylint: disable=no-self-use
@ -1466,6 +1484,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/fave_slices", methods=["GET"])
@expose("/fave_slices/<int:user_id>/", methods=["GET"])
def fave_slices( # pylint: disable=no-self-use
@ -1596,6 +1615,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
return json_success(json.dumps(result))
@has_access_api
@event_logger.log_this
@expose("/favstar/<class_name>/<int:obj_id>/<action>/")
def favstar( # pylint: disable=no-self-use
self, class_name: str, obj_id: int, action: str
@ -1629,6 +1649,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/dashboard/<int:dashboard_id>/published/", methods=("GET", "POST"))
def publish( # pylint: disable=no-self-use
self, dashboard_id: int
@ -1775,7 +1796,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
)
@api
@event_logger.log_this
@has_access
@expose("/log/", methods=["POST"])
def log(self) -> FlaskResponse: # pylint: disable=no-self-use
@ -2118,8 +2138,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
return self.json_response("OK")
@has_access_api
@expose("/validate_sql_json/", methods=["POST", "GET"])
@event_logger.log_this
@expose("/validate_sql_json/", methods=["POST", "GET"])
def validate_sql_json( # pylint: disable=too-many-locals,too-many-return-statements,no-self-use
self,
) -> FlaskResponse:
@ -2304,8 +2324,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@has_access_api
@handle_api_exception
@expose("/sql_json/", methods=["POST"])
@event_logger.log_this
@expose("/sql_json/", methods=["POST"])
def sql_json(self) -> FlaskResponse:
log_params = {
"user_agent": cast(Optional[str], request.headers.get("USER_AGENT"))
@ -2438,8 +2458,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
)
@has_access
@expose("/csv/<client_id>")
@event_logger.log_this
@expose("/csv/<client_id>")
def csv(self, client_id: str) -> FlaskResponse: # pylint: disable=no-self-use
"""Download the query results as csv."""
logger.info("Exporting CSV file [%s]", client_id)
@ -2495,8 +2515,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@handle_api_exception
@has_access
@expose("/fetch_datasource_metadata")
@event_logger.log_this
@expose("/fetch_datasource_metadata")
def fetch_datasource_metadata(self) -> FlaskResponse: # pylint: disable=no-self-use
"""
Fetch the datasource metadata.
@ -2517,6 +2537,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
return json_success(json.dumps(datasource.data))
@has_access_api
@event_logger.log_this
@expose("/queries/<float:last_updated_ms>")
@expose("/queries/<int:last_updated_ms>")
def queries(self, last_updated_ms: Union[float, int]) -> FlaskResponse:
@ -2550,8 +2571,8 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
return json_success(json.dumps(dict_queries, default=utils.json_int_dttm_ser))
@has_access
@expose("/search_queries")
@event_logger.log_this
@expose("/search_queries")
def search_queries(self) -> FlaskResponse: # pylint: disable=no-self-use
"""
Search for previously run sqllab queries. Used for Sqllab Query Search
@ -2621,6 +2642,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
500,
)
@event_logger.log_this
@expose("/welcome")
def welcome(self) -> FlaskResponse:
"""Personalized welcome page"""
@ -2651,6 +2673,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
)
@has_access
@event_logger.log_this
@expose("/profile/<username>/")
def profile(self, username: str) -> FlaskResponse:
"""User profile page"""
@ -2721,6 +2744,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
}
@has_access
@event_logger.log_this
@expose("/sqllab", methods=["GET", "POST"])
def sqllab(self) -> FlaskResponse:
"""SQL Editor"""
@ -2747,7 +2771,9 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
)
@has_access
@event_logger.log_this
@expose("/sqllab/history/", methods=["GET"])
@event_logger.log_this
def sqllab_search(self) -> FlaskResponse:
if not (
is_feature_enabled("ENABLE_REACT_CRUD_VIEWS")
@ -2759,6 +2785,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
@api
@has_access_api
@event_logger.log_this
@expose("/schemas_access_for_csv_upload")
def schemas_access_for_csv_upload(self) -> FlaskResponse:
"""