You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by GitBox <gi...@apache.org> on 2021/09/07 12:15:50 UTC

[GitHub] [superset] villebro commented on a change in pull request #14015: feat(filter-set): Add filterset resource

villebro commented on a change in pull request #14015:
URL: https://github.com/apache/superset/pull/14015#discussion_r703438821



##########
File path: superset/commands/exceptions.py
##########
@@ -31,6 +31,26 @@ def __repr__(self) -> str:
         return repr(self)
 
 
+class ObjectNotFoundError(CommandException):
+    status = 404
+    message_format = "{} {}not found."
+
+    def __init__(
+        self,
+        object_type: str,
+        object_id: Optional[str] = None,
+        exception: Optional[Exception] = None,
+    ) -> None:
+        super().__init__(
+            _(
+                self.message_format.format(
+                    object_type, '"%s" ' % object_id if object_id else ""
+                )
+            ),

Review comment:
       I doubt this will work. `_()` needs to be called with a static message, with variable pieces handled as kwargs:
   
   ```python
   _("This is my message: %(message)s", message="abc")
   ```

##########
File path: superset/commands/export.py
##########
@@ -34,7 +34,7 @@
 class ExportModelsCommand(BaseCommand):
 
     dao = BaseDAO
-    not_found = CommandException
+    not_found: Type[CommandException] = CommandException

Review comment:
       Not sure this is needed

##########
File path: superset/models/dashboard.py
##########
@@ -397,6 +427,11 @@ def get(cls, id_or_slug: str) -> Dashboard:
         qry = session.query(Dashboard).filter(id_or_slug_filter(id_or_slug))
         return qry.one_or_none()
 
+    def am_i_owner(self) -> bool:
+        if g.user is None or g.user.is_anonymous or not g.user.is_authenticated:
+            return False
+        return g.user.id in set(map(lambda user: user.id, self.owners))

Review comment:
       IMO `is_actor_owner()` would be a more appropriate name for this method.

##########
File path: superset/common/request_contexed_based.py
##########
@@ -0,0 +1,39 @@
+# 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.
+from __future__ import annotations
+
+from typing import List, TYPE_CHECKING
+
+from flask import g
+
+from superset import conf, security_manager
+
+if TYPE_CHECKING:
+    from flask_appbuilder.security.sqla.models import Role
+
+
+def get_user_roles() -> List[Role]:
+    if g.user.is_anonymous:
+        public_role = conf.get("AUTH_ROLE_PUBLIC")
+        return [security_manager.get_public_role()] if public_role else []
+    return g.user.roles
+
+
+def is_user_admin() -> bool:
+    user_roles = [role.name.lower() for role in list(get_user_roles())]

Review comment:
       nit: not sure we need to encapsulate this in `list()`
   ```suggestion
   def is_user_admin() -> bool:
       user_roles = [role.name.lower() for role in get_user_roles()]
   ```

##########
File path: superset/dashboards/filter_sets/commands/base.py
##########
@@ -0,0 +1,95 @@
+# 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.
+import logging
+from typing import cast, Optional
+
+from flask_appbuilder.models.sqla import Model
+from flask_appbuilder.security.sqla.models import User
+
+from superset.commands.base import BaseCommand
+from superset.common.not_authrized_object import NotAuthorizedException
+from superset.common.request_contexed_based import is_user_admin
+from superset.dashboards.commands.exceptions import DashboardNotFoundError
+from superset.dashboards.dao import DashboardDAO
+from superset.dashboards.filter_sets.commands.exceptions import (
+    FilterSetForbiddenError,
+    FilterSetNotFoundError,
+)
+from superset.dashboards.filter_sets.consts import USER_OWNER_TYPE
+from superset.models.dashboard import Dashboard
+from superset.models.filter_set import FilterSet
+
+logger = logging.getLogger(__name__)
+
+
+class BaseFilterSetCommand(BaseCommand):
+    # pylint: disable=C0103
+    _dashboard: Dashboard
+    _filter_set_id: Optional[int]
+    _filter_set: Optional[FilterSet]
+
+    def __init__(self, user: User, dashboard_id: int):
+        self._actor = user
+        self._is_actor_admin = is_user_admin()
+        self._dashboard_id = dashboard_id
+
+    def run(self) -> Model:
+        pass
+
+    def validate(self) -> None:
+        self._validate_filterset_dashboard_exists()
+
+    def _validate_filterset_dashboard_exists(self) -> None:
+        self._dashboard = DashboardDAO.get_by_id_or_slug(str(self._dashboard_id))
+        if not self._dashboard:
+            raise DashboardNotFoundError()

Review comment:
       I don't understand why we need a private method `_validate_filterset_dashboard_exists()` that is only called once by a one-liner wrapper `validate()`?

##########
File path: superset/models/filter_set.py
##########
@@ -0,0 +1,99 @@
+# 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.
+from __future__ import annotations
+
+import logging
+from typing import Any, Dict
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, ForeignKey, Integer, JSON, MetaData, String, Text
+from sqlalchemy.orm import relationship
+from sqlalchemy_utils import generic_relationship
+
+from superset import app, db
+from superset.models.helpers import AuditMixinNullable
+
+metadata = Model.metadata  # pylint: disable=no-member
+config = app.config
+logger = logging.getLogger(__name__)
+
+
+class FilterSet(Model, AuditMixinNullable):
+    __tablename__ = "filter_sets"
+    id = Column(Integer, primary_key=True)
+    name = Column(String(500), nullable=False, unique=True)
+    description = Column(Text, nullable=True)
+    json_metadata = Column(JSON, nullable=False)

Review comment:
       same here (change to `Text`)

##########
File path: superset/migrations/versions/3ebe0993c770_filterset_table.py
##########
@@ -0,0 +1,56 @@
+# 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 filter set model
+
+Revision ID: 3ebe0993c770
+Revises: 07071313dd52
+Create Date: 2021-03-29 11:15:48.831225
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "3ebe0993c770"
+down_revision = "07071313dd52"
+
+import sqlalchemy as sa
+from alembic import op
+
+
+def upgrade():
+    op.create_table(
+        "filter_sets",
+        sa.Column("created_on", sa.DateTime(), nullable=True),
+        sa.Column("changed_on", sa.DateTime(), nullable=True),
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("name", sa.VARCHAR(500), nullable=False),
+        sa.Column("description", sa.Text(), nullable=True),
+        sa.Column("json_metadata", sa.JSON(), nullable=False),

Review comment:
       I suggest we not use `sa.JSON()` here but rather go with `sa.Text()` as we have until now, as with `sa.JSON()` we're not sure how it behaves on various metadata engines. So let's just keep it `Text()` and assume we need to parse/format when deserializing/serializing.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org