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
This commit is contained in:
Beto Dealmeida 2018-08-07 17:39:15 -07:00 committed by Maxime Beauchemin
parent 39ff9deb57
commit 222b79df7e
6 changed files with 123 additions and 1 deletions

View File

@ -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

View File

@ -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')

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import core # noqa
from . import sql_lab # noqa
from . import user_attributes # noqa

View File

@ -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"""

View File

@ -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')

View File

@ -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(),