superset-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From maximebeauche...@apache.org
Subject [incubator-superset] branch master updated: Template dashboard (#5550)
Date Wed, 08 Aug 2018 00:39:18 GMT
This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin 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 222b79d  Template dashboard (#5550)
222b79d is described below

commit 222b79df7e194313ca7c011cacad999b07bf2332
Author: Beto Dealmeida <roberto@dealmeida.net>
AuthorDate: Tue Aug 7 17:39:15 2018 -0700

    Template dashboard (#5550)
    
    * Template dashboard
    
    * Fix MySQL test
    
    * Model for user attributes
    
    * Redirect to welcome dash
    
    * Fix lint
    
    * Add missing file
    
    * Add migration script
    
    * Fix lint
    
    * Fix more lint
---
 superset/config.py                                 |  3 ++
 .../0c5070e96b57_add_user_attributes_table.py      | 35 +++++++++++++++++++
 superset/models/__init__.py                        |  1 +
 superset/models/core.py                            | 39 +++++++++++++++++++++-
 superset/models/user_attributes.py                 | 36 ++++++++++++++++++++
 superset/views/core.py                             | 10 ++++++
 6 files changed, 123 insertions(+), 1 deletion(-)

diff --git a/superset/config.py b/superset/config.py
index ca9fcbd..a5e4f29 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -412,6 +412,9 @@ HIVE_POLL_INTERVAL = 5
 # an XSS security vulnerability
 ENABLE_JAVASCRIPT_CONTROLS = False
 
+# The id of a template dashboard that should be copied to every new user
+DASHBOARD_TEMPLATE_ID = None
+
 # A callable that allows altering the database conneciton URL and params
 # on the fly, at runtime. This allows for things like impersonation or
 # arbitrary logic. For instance you can wire different users to
diff --git a/superset/migrations/versions/0c5070e96b57_add_user_attributes_table.py b/superset/migrations/versions/0c5070e96b57_add_user_attributes_table.py
new file mode 100644
index 0000000..69eba1b
--- /dev/null
+++ b/superset/migrations/versions/0c5070e96b57_add_user_attributes_table.py
@@ -0,0 +1,35 @@
+"""add user attributes table
+
+Revision ID: 0c5070e96b57
+Revises: 7fcdcde0761c
+Create Date: 2018-08-06 14:38:18.965248
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '0c5070e96b57'
+down_revision = '7fcdcde0761c'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.create_table('user_attribute',
+    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('user_id', sa.Integer(), nullable=True),
+    sa.Column('welcome_dashboard_id', sa.Integer(), nullable=True),
+    sa.Column('created_by_fk', sa.Integer(), nullable=True),
+    sa.Column('changed_by_fk', sa.Integer(), nullable=True),
+    sa.ForeignKeyConstraint(['changed_by_fk'], ['ab_user.id'], ),
+    sa.ForeignKeyConstraint(['created_by_fk'], ['ab_user.id'], ),
+    sa.ForeignKeyConstraint(['user_id'], ['ab_user.id'], ),
+    sa.ForeignKeyConstraint(['welcome_dashboard_id'], ['dashboards.id'], ),
+    sa.PrimaryKeyConstraint('id')
+    )
+
+
+def downgrade():
+    op.drop_table('user_attribute')
diff --git a/superset/models/__init__.py b/superset/models/__init__.py
index 18df0e6..2084aee 100644
--- a/superset/models/__init__.py
+++ b/superset/models/__init__.py
@@ -1,3 +1,4 @@
 # -*- coding: utf-8 -*-
 from . import core  # noqa
 from . import sql_lab  # noqa
+from . import user_attributes  # noqa
diff --git a/superset/models/core.py b/superset/models/core.py
index 51e11b1..f3b18f8 100644
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -17,6 +17,7 @@ import textwrap
 from flask import escape, g, Markup, request
 from flask_appbuilder import Model
 from flask_appbuilder.models.decorators import renders
+from flask_appbuilder.security.sqla.models import User
 from future.standard_library import install_aliases
 import numpy
 import pandas as pd
@@ -28,7 +29,7 @@ from sqlalchemy import (
 )
 from sqlalchemy.engine import url
 from sqlalchemy.engine.url import make_url
-from sqlalchemy.orm import relationship, subqueryload
+from sqlalchemy.orm import relationship, sessionmaker, subqueryload
 from sqlalchemy.orm.session import make_transient
 from sqlalchemy.pool import NullPool
 from sqlalchemy.schema import UniqueConstraint
@@ -39,6 +40,7 @@ from superset import app, db, db_engine_specs, security_manager, utils
 from superset.connectors.connector_registry import ConnectorRegistry
 from superset.legacy import update_time_range
 from superset.models.helpers import AuditMixinNullable, ImportMixin, set_perm
+from superset.models.user_attributes import UserAttribute
 from superset.viz import viz_types
 install_aliases()
 from urllib import parse  # noqa
@@ -59,6 +61,41 @@ def set_related_perm(mapper, connection, target):  # noqa
             target.perm = ds.perm
 
 
+def copy_dashboard(mapper, connection, target):
+    dashboard_id = config.get('DASHBOARD_TEMPLATE_ID')
+    if dashboard_id is None:
+        return
+
+    Session = sessionmaker(autoflush=False)
+    session = Session(bind=connection)
+    new_user = session.query(User).filter_by(id=target.id).first()
+
+    # copy template dashboard to user
+    template = session.query(Dashboard).filter_by(id=int(dashboard_id)).first()
+    dashboard = Dashboard(
+        dashboard_title=template.dashboard_title,
+        position_json=template.position_json,
+        description=template.description,
+        css=template.css,
+        json_metadata=template.json_metadata,
+        slices=template.slices,
+        owners=[new_user],
+    )
+    session.add(dashboard)
+    session.commit()
+
+    # set dashboard as the welcome dashboard
+    extra_attributes = UserAttribute(
+        user_id=target.id,
+        welcome_dashboard_id=dashboard.id,
+    )
+    session.add(extra_attributes)
+    session.commit()
+
+
+sqla.event.listen(User, 'after_insert', copy_dashboard)
+
+
 class Url(Model, AuditMixinNullable):
     """Used for the short url feature"""
 
diff --git a/superset/models/user_attributes.py b/superset/models/user_attributes.py
new file mode 100644
index 0000000..faf4127
--- /dev/null
+++ b/superset/models/user_attributes.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+from flask_appbuilder import Model
+from sqlalchemy import Column, ForeignKey, Integer
+from sqlalchemy.orm import relationship
+
+from superset import security_manager
+from superset.models.helpers import AuditMixinNullable
+
+
+class UserAttribute(Model, AuditMixinNullable):
+
+    """
+    Custom attributes attached to the user.
+
+    Extending the user attribute is tricky due to its dependency on the
+    authentication typew an circular dependencies in Superset. Instead, we use
+    a custom model for adding attributes.
+
+    """
+
+    __tablename__ = 'user_attribute'
+    id = Column(Integer, primary_key=True)  # pylint: disable=invalid-name
+    user_id = Column(Integer, ForeignKey('ab_user.id'))
+    user = relationship(
+        security_manager.user_model,
+        backref='extra_attributes',
+        foreign_keys=[user_id],
+    )
+
+    welcome_dashboard_id = Column(Integer, ForeignKey('dashboards.id'))
+    welcome_dashboard = relationship('Dashboard')
diff --git a/superset/views/core.py b/superset/views/core.py
index ab0f686..e6f2903 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -45,6 +45,7 @@ from superset.jinja_context import get_template_processor
 from superset.legacy import cast_form_data, update_time_range
 import superset.models.core as models
 from superset.models.sql_lab import Query
+from superset.models.user_attributes import UserAttribute
 from superset.sql_parse import SupersetQuery
 from superset.utils import (
     merge_extra_filters, merge_request_params, QueryStatus,
@@ -2648,6 +2649,15 @@ class Superset(BaseSupersetView):
         if not g.user or not g.user.get_id():
             return redirect(appbuilder.get_url_for_login)
 
+        welcome_dashboard_id = (
+            db.session
+            .query(UserAttribute.welcome_dashboard_id)
+            .filter_by(user_id=g.user.get_id())
+            .scalar()
+        )
+        if welcome_dashboard_id:
+            return self.dashboard(str(welcome_dashboard_id))
+
         payload = {
             'user': bootstrap_user_data(),
             'common': self.common_bootsrap_payload(),


Mime
View raw message