diff --git a/caravel/assets/javascripts/dashboard/Dashboard.jsx b/caravel/assets/javascripts/dashboard/Dashboard.jsx index 15bdfbf8f5db7..2507e21ecbec5 100644 --- a/caravel/assets/javascripts/dashboard/Dashboard.jsx +++ b/caravel/assets/javascripts/dashboard/Dashboard.jsx @@ -45,7 +45,6 @@ function dashboardContainer(dashboardData) { filters: {}, init() { this.initDashboardView(); - this.firstLoad = true; px.initFavStars(); const sliceObjects = []; const dash = this; @@ -64,7 +63,7 @@ function dashboardContainer(dashboardData) { this.slices = sliceObjects; this.refreshTimer = null; this.loadPreSelectFilters(); - this.startPeriodicRender(0); + this.startPeriodicRender(); this.bindResizeToWindowResize(); }, loadPreSelectFilters() { @@ -131,14 +130,12 @@ function dashboardContainer(dashboardData) { const maxRandomDelay = Math.min(interval * 0.2, 5000); const refreshAll = function () { dash.slices.forEach(function (slice) { - const force = !dash.firstLoad; setTimeout(function () { slice.render(force); }, // Randomize to prevent all widgets refreshing at the same time maxRandomDelay * Math.random()); }); - dash.firstLoad = false; }; const fetchAndRender = function () { @@ -149,7 +146,9 @@ function dashboardContainer(dashboardData) { }, interval); } }; - fetchAndRender(); + if (interval > 0){ + fetchAndRender(); + } }, refreshExcept(sliceId) { const immune = this.metadata.filter_immune_slices || []; @@ -244,6 +243,8 @@ function dashboardContainer(dashboardData) { positions, css: this.editor.getValue(), expanded_slices: expandedSlices, + autorefresh_seconds: dashboard.autorefresh_seconds, + autorefresh_from_cache: dashboard.autorefresh_from_cache }; $.ajax({ type: 'POST', @@ -323,9 +324,10 @@ function dashboardContainer(dashboardData) { dashboard.readFilters(), }); }); - $('#refresh_dash_interval').on('change', function () { - const interval = $(this).find('option:selected').val() * 1000; - dashboard.startPeriodicRender(interval); + $("#refresh_dash_apply").click(function () { + dashboard.autorefresh_seconds = $("#refresh_dash_interval").val(); + dashboard.autorefresh_from_cache = $("#refresh_dach_from_cache").is(':checked'); + dashboard.startPeriodicRender(); }); $('#refresh_dash').click(() => { dashboard.slices.forEach((slice) => { diff --git a/caravel/migrations/versions/27ae655e4247_make_creator_owners.py b/caravel/migrations/versions/27ae655e4247_make_creator_owners.py index 71c627305dbc7..c4add0eec9835 100644 --- a/caravel/migrations/versions/27ae655e4247_make_creator_owners.py +++ b/caravel/migrations/versions/27ae655e4247_make_creator_owners.py @@ -11,18 +11,67 @@ down_revision = 'd8bc074f7aad' from alembic import op -from caravel import db, models +from caravel import db + +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship +from sqlalchemy import ( + Column, Integer, ForeignKey, Table) + +Base = declarative_base() + +slice_user = Table( + 'slice_user', Base.metadata, + Column('id', Integer, primary_key=True), + Column('user_id', Integer, ForeignKey('ab_user.id')), + Column('slice_id', Integer, ForeignKey('slices.id')) +) + +dashboard_user = Table( + 'dashboard_user', Base.metadata, + Column('id', Integer, primary_key=True), + Column('user_id', Integer, ForeignKey('ab_user.id')), + Column('dashboard_id', Integer, ForeignKey('dashboards.id')) +) + + +class User(Base): + + """Declarative class to do query in upgrade""" + + __tablename__ = 'ab_user' + id = Column(Integer, primary_key=True) + + +class Slice(Base): + + """Declarative class to do query in upgrade""" + + __tablename__ = 'slices' + id = Column(Integer, primary_key=True) + owners = relationship("User", secondary=slice_user) + created_by_fk = Column(Integer) + + +class Dashboard(Base): + + """Declarative class to do query in upgrade""" + + __tablename__ = 'dashboards' + id = Column(Integer, primary_key=True) + owners = relationship("User", secondary=dashboard_user) + created_by_fk = Column(Integer) def upgrade(): bind = op.get_bind() session = db.Session(bind=bind) - objects = session.query(models.Slice).all() - objects += session.query(models.Dashboard).all() + objects = session.query(Slice).all() + objects += session.query(Dashboard).all() for obj in objects: - if obj.created_by and obj.created_by not in obj.owners: - obj.owners.append(obj.created_by) + if obj.created_by_fk and obj.created_by_fk not in obj.owners: + obj.owners.append(obj.created_by_fk) session.commit() session.close() diff --git a/caravel/migrations/versions/79aef3baedae_adding_columns_for_dashboard_refresh_.py b/caravel/migrations/versions/79aef3baedae_adding_columns_for_dashboard_refresh_.py new file mode 100644 index 0000000000000..bb35bc83aaccd --- /dev/null +++ b/caravel/migrations/versions/79aef3baedae_adding_columns_for_dashboard_refresh_.py @@ -0,0 +1,29 @@ +"""Adding Columns for Dashboard Refresh Properties + +Revision ID: 79aef3baedae +Revises: f162a1dea4c4 +Create Date: 2016-07-02 14:51:00.106192 + +""" + +# revision identifiers, used by Alembic. +revision = '79aef3baedae' +down_revision = 'f162a1dea4c4' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + try: + op.add_column('dashboards', sa.Column('autorefresh_from_cache', sa.Boolean(), nullable=False, server_default='True')) + except: + # To pick up databases (like some MySQL variants) without a true Boolean value + op.add_column('dashboards', sa.Column('autorefresh_from_cache', sa.Boolean(), nullable=False, server_default='1')) + + op.add_column('dashboards', sa.Column('autorefresh_seconds', sa.Integer(), nullable=False, server_default='0')) + + +def downgrade(): + op.drop_column('dashboards', 'autorefresh_seconds') + op.drop_column('dashboards', 'autorefresh_from_cache') diff --git a/caravel/models.py b/caravel/models.py index 444a4da6b7e13..8a8e909ee040a 100644 --- a/caravel/models.py +++ b/caravel/models.py @@ -291,6 +291,9 @@ class Dashboard(Model, AuditMixinNullable): slices = relationship( 'Slice', secondary=dashboard_slices, backref='dashboards') owners = relationship("User", secondary=dashboard_user) + # A zero for autorefresh seconds implies no autorefresh + autorefresh_seconds = Column(Integer, default=0, nullable=False) + autorefresh_from_cache = Column(Boolean, default=True, nullable=False) def __repr__(self): return self.dashboard_title @@ -315,6 +318,7 @@ def dashboard_link(self): @property def json_data(self): + """Returns the configuration data for the dashboard as json""" d = { 'id': self.id, 'metadata': self.metadata_dejson, @@ -322,6 +326,8 @@ def json_data(self): 'slug': self.slug, 'slices': [slc.data for slc in self.slices], 'position_json': json.loads(self.position_json) if self.position_json else [], + 'autorefresh_seconds': self.autorefresh_seconds, + 'autorefresh_from_cache': self.autorefresh_from_cache, } return json.dumps(d) diff --git a/caravel/templates/caravel/dashboard.html b/caravel/templates/caravel/dashboard.html index afdf144114d8d..28b49e17c048a 100644 --- a/caravel/templates/caravel/dashboard.html +++ b/caravel/templates/caravel/dashboard.html @@ -44,21 +44,22 @@
Styling applies to this dashboard only
@@ -84,7 +85,7 @@

diff --git a/caravel/views.py b/caravel/views.py index db4b3a02ba57e..b2d35181c44b8 100755 --- a/caravel/views.py +++ b/caravel/views.py @@ -628,12 +628,22 @@ class DashboardModelView(CaravelModelView, DeleteMixin): # noqa datamodel = SQLAInterface(models.Dashboard) list_columns = ['dashboard_link', 'creator', 'modified'] edit_columns = [ - 'dashboard_title', 'slug', 'slices', 'owners', 'position_json', 'css', - 'json_metadata'] + 'dashboard_title', 'slug', 'slices', 'owners', + 'autorefresh_seconds', 'autorefresh_from_cache', + 'position_json', 'css', 'json_metadata'] show_columns = edit_columns + ['table_names'] add_columns = edit_columns base_order = ('changed_on', 'desc') description_columns = { + 'autorefresh_seconds': _( + "The number of seconds between automatic refreshes " + "of the dashboard. The default value of 0 means the " + "dashboard will not automatically refresh."), + 'autorefresh_from_cache': _( + "If checked the dashboard will use cached values when " + "refreshing. To force the dashboard to always use fresh " + "values then uncheck this option. Performance with many " + "users will be lower if unchecked."), 'position_json': _( "This json object describes the positioning of the widgets in " "the dashboard. It is dynamically generated when adjusting " @@ -660,6 +670,8 @@ class DashboardModelView(CaravelModelView, DeleteMixin): # noqa 'owners': _("Owners"), 'creator': _("Creator"), 'modified': _("Modified"), + 'autorefresh_seconds': _("Dashboard Refresh Frequency"), + 'autorefresh_from_cache': _("Refresh From Cache"), 'position_json': _("Position JSON"), 'css': _("CSS"), 'json_metadata': _("JSON Metadata"), @@ -1066,6 +1078,8 @@ def save_dash(self, dashboard_id): md['expanded_slices'] = data['expanded_slices'] dash.json_metadata = json.dumps(md, indent=4) dash.css = data['css'] + dash.autorefresh_seconds = data['autorefresh_seconds'] + dash.autorefresh_from_cache = data['autorefresh_from_cache'] session.merge(dash) session.commit() session.close() diff --git a/tests/core_tests.py b/tests/core_tests.py index 40e206c237972..b0e2110b053d7 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -196,6 +196,8 @@ def test_save_dash(self, username='admin'): 'css': '', 'expanded_slices': {}, 'positions': positions, + 'autorefresh_seconds': 60, + 'autorefresh_from_cache': True, } url = '/caravel/save_dash/{}/'.format(dash.id) resp = self.client.post(url, data=dict(data=json.dumps(data)))