From 65b9e1774e0dd95e90756ca7b12897125dbb863d Mon Sep 17 00:00:00 2001 From: Rafael Wendel Date: Thu, 12 Nov 2020 23:18:23 -0300 Subject: [PATCH 01/32] added paramOrder prop --- client/app/components/Parameters.jsx | 13 ++++++++----- client/app/pages/dashboards/DashboardPage.jsx | 14 +++++++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/client/app/components/Parameters.jsx b/client/app/components/Parameters.jsx index 9f9240c99a..114cab28b4 100644 --- a/client/app/components/Parameters.jsx +++ b/client/app/components/Parameters.jsx @@ -1,4 +1,4 @@ -import { size, filter, forEach, extend } from "lodash"; +import { size, filter, forEach, extend, sortBy } from "lodash"; import React from "react"; import PropTypes from "prop-types"; import { SortableContainer, SortableElement, DragHandle } from "@redash/viz/lib/components/sortable"; @@ -85,7 +85,7 @@ export default class Parameters extends React.Component { if (oldIndex !== newIndex) { this.setState(({ parameters }) => { parameters.splice(newIndex, 0, parameters.splice(oldIndex, 1)[0]); - onParametersEdit(); + onParametersEdit(parameters); return { parameters }; }); } @@ -110,7 +110,7 @@ export default class Parameters extends React.Component { this.setState(({ parameters }) => { const updatedParameter = extend(parameter, updated); parameters[index] = createParameter(updatedParameter, updatedParameter.parentQueryId); - onParametersEdit(); + onParametersEdit(parameters); return { parameters }; }); }); @@ -122,6 +122,7 @@ export default class Parameters extends React.Component {
+ {/* TODO: add a prop to allow conditional show of this decoupled from sort */} {editable && ( )} - {showRefreshButton && } + {showRefreshButton && } {showFullscreenButton && ( )} - {showMoreOptionsButton && } + {showMoreOptionsButton && } )}
@@ -218,12 +218,17 @@ function DashboardControl({ dashboardOptions, headerExtra }) { } DashboardControl.propTypes = { - dashboardOptions: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types headerExtra: PropTypes.node, }; -function DashboardEditControl({ dashboardOptions, headerExtra }) { - const { setEditingLayout, doneBtnClickedWhileSaving, dashboardStatus, retrySaveDashboardLayout } = dashboardOptions; +function DashboardEditControl({ dashboardConfiguration, headerExtra }) { + const { + setEditingLayout, + doneBtnClickedWhileSaving, + dashboardStatus, + retrySaveDashboardLayout, + } = dashboardConfiguration; let status; if (dashboardStatus === DashboardStatusEnum.SAVED) { status = Saved; @@ -258,23 +263,23 @@ function DashboardEditControl({ dashboardOptions, headerExtra }) { } DashboardEditControl.propTypes = { - dashboardOptions: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types headerExtra: PropTypes.node, }; -export default function DashboardHeader({ dashboardOptions, headerExtra }) { - const { editingLayout } = dashboardOptions; +export default function DashboardHeader({ dashboardConfiguration, headerExtra }) { + const { editingLayout } = dashboardConfiguration; const DashboardControlComponent = editingLayout ? DashboardEditControl : DashboardControl; return (
- - + +
); } DashboardHeader.propTypes = { - dashboardOptions: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types headerExtra: PropTypes.node, }; diff --git a/client/app/services/dashboard.js b/client/app/services/dashboard.js index 261be9085e..4455a74ad8 100644 --- a/client/app/services/dashboard.js +++ b/client/app/services/dashboard.js @@ -142,6 +142,8 @@ function transformSingle(dashboard) { dashboard.widgets = prepareDashboardWidgets(dashboard.widgets); } dashboard.publicAccessEnabled = dashboard.public_url !== undefined; + // Temporary + dashboard.options = { globalParamOrder: [] }; return dashboard; } @@ -208,12 +210,24 @@ Dashboard.prototype.getParametersDefs = function getParametersDefs() { }); } }); - return _.values( + const resultingGlobalParams = _.values( _.each(globalParams, param => { param.setValue(param.value); // apply global param value to all locals param.fromUrlParams(queryParams); // try to initialize from url (may do nothing) }) ); + this.options.tmp = 13; + + if (!_.isArray(this.options.globalParamOrder)) { + this.options.globalParamOrder = _.map(resultingGlobalParams, "name"); + } + + // order dashboard params using paramOrder + return _.sortBy(resultingGlobalParams, param => + _.includes(this.options.globalParamOrder, param.name) + ? _.indexOf(this.options.globalParamOrder, param.name) + : _.size(this.options.globalParamOrder) + ); }; Dashboard.prototype.addWidget = function addWidget(textOrVisualization, options = {}) { From 41ae2ce4755a6fa03fd76d900819b11016919275 Mon Sep 17 00:00:00 2001 From: Rafael Wendel Date: Tue, 24 Nov 2020 03:09:09 -0300 Subject: [PATCH 10/32] Added backend logic for dashboard options --- migrations/versions/dfeaacdd7624_.py | 35 ++++++++++++++++++++++++++++ redash/handlers/dashboards.py | 2 ++ redash/models/__init__.py | 1 + redash/serializers/__init__.py | 2 ++ 4 files changed, 40 insertions(+) create mode 100644 migrations/versions/dfeaacdd7624_.py diff --git a/migrations/versions/dfeaacdd7624_.py b/migrations/versions/dfeaacdd7624_.py new file mode 100644 index 0000000000..7feec38188 --- /dev/null +++ b/migrations/versions/dfeaacdd7624_.py @@ -0,0 +1,35 @@ +"""empty message + +Revision ID: dfeaacdd7624 +Revises: e5c7a4e2df4d +Create Date: 2020-11-20 19:11:29.497508 + +""" +import json +from alembic import op +import sqlalchemy as sa + +from redash.models import MutableDict, PseudoJSON + +# revision identifiers, used by Alembic. +revision = 'dfeaacdd7624' +down_revision = 'e5c7a4e2df4d' +branch_labels = None +depends_on = None + + +def upgrade(): + # create "dashboard options" column as a dict type + op.add_column( + "dashboards", + sa.Column( + "options", + MutableDict.as_mutable(PseudoJSON), + nullable=False, + server_default=json.dumps({}), + ) + ) + + +def downgrade(): + op.drop_column("dashboards", "options") diff --git a/redash/handlers/dashboards.py b/redash/handlers/dashboards.py index 5aace124d7..2dd9262922 100644 --- a/redash/handlers/dashboards.py +++ b/redash/handlers/dashboards.py @@ -135,6 +135,7 @@ def get(self, dashboard_id=None): :>json boolean is_draft: Whether this dashboard is a draft or not. :>json array layout: Array of arrays containing widget IDs, corresponding to the rows and columns the widgets are displayed in :>json array widgets: Array of arrays containing :ref:`widget ` data + :>json object options: Dashboard options .. _widget-response-label: @@ -205,6 +206,7 @@ def post(self, dashboard_id): "is_draft", "is_archived", "dashboard_filters_enabled", + "options" ), ) diff --git a/redash/models/__init__.py b/redash/models/__init__.py index 68f0075434..537ef72dc6 100644 --- a/redash/models/__init__.py +++ b/redash/models/__init__.py @@ -1099,6 +1099,7 @@ class Dashboard(ChangeTrackingMixin, TimestampMixin, BelongsToOrgMixin, db.Model tags = Column( "tags", MutableList.as_mutable(postgresql.ARRAY(db.Unicode)), nullable=True ) + options = Column(db.Text) __tablename__ = "dashboards" __mapper_args__ = {"version_id_col": version} diff --git a/redash/serializers/__init__.py b/redash/serializers/__init__.py index 83b1fa266b..10f53d99b4 100644 --- a/redash/serializers/__init__.py +++ b/redash/serializers/__init__.py @@ -217,6 +217,7 @@ def serialize_alert(alert, full=True): def serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state=True): layout = json_loads(obj.layout) + options = json_loads(obj.options) widgets = [] @@ -256,6 +257,7 @@ def serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state= }, "layout": layout, "dashboard_filters_enabled": obj.dashboard_filters_enabled, + "options": options, "widgets": widgets, "is_archived": obj.is_archived, "is_draft": obj.is_draft, From 9e01b2d30ffc021b01fbf6259829e5757da24c51 Mon Sep 17 00:00:00 2001 From: Rafael Wendel Date: Tue, 24 Nov 2020 03:13:41 -0300 Subject: [PATCH 11/32] Removed testing leftovers --- client/app/services/dashboard.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/app/services/dashboard.js b/client/app/services/dashboard.js index 4455a74ad8..00a0a447db 100644 --- a/client/app/services/dashboard.js +++ b/client/app/services/dashboard.js @@ -216,7 +216,6 @@ Dashboard.prototype.getParametersDefs = function getParametersDefs() { param.fromUrlParams(queryParams); // try to initialize from url (may do nothing) }) ); - this.options.tmp = 13; if (!_.isArray(this.options.globalParamOrder)) { this.options.globalParamOrder = _.map(resultingGlobalParams, "name"); From 163fb6fef5ecf7ec9924d5ff2dddcb4d889caab8 Mon Sep 17 00:00:00 2001 From: Rafael Wendel Date: Tue, 22 Dec 2020 09:10:32 -0300 Subject: [PATCH 12/32] removed appending sortable to parent component behavior --- viz-lib/src/components/sortable/index.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/viz-lib/src/components/sortable/index.jsx b/viz-lib/src/components/sortable/index.jsx index edd90d579a..60386fecb4 100644 --- a/viz-lib/src/components/sortable/index.jsx +++ b/viz-lib/src/components/sortable/index.jsx @@ -29,12 +29,6 @@ export function SortableContainer({ disabled, containerComponent, containerProps } else { // Enabled state: - // EXPERIMENTAL: having the helper attached to document body eliminates mispositioning - // - use container element as a default helper element - // wrapperProps.helperContainer = wrap(wrapperProps.helperContainer, helperContainer => - // isFunction(helperContainer) ? helperContainer(containerRef.current) : containerRef.current - // ); - // - hook drag start/end events wrapperProps.updateBeforeSortStart = wrap(wrapperProps.updateBeforeSortStart, (updateBeforeSortStart, ...args) => { setIsDragging(true); From 026608a9b600c228fff0e906996d6aaa2ad6e6bf Mon Sep 17 00:00:00 2001 From: Rafael Wendel Date: Tue, 22 Dec 2020 13:42:14 -0300 Subject: [PATCH 13/32] Revert "Added backend logic for dashboard options" This reverts commit 41ae2ce4755a6fa03fd76d900819b11016919275. --- migrations/versions/dfeaacdd7624_.py | 35 ---------------------------- redash/handlers/dashboards.py | 2 -- redash/models/__init__.py | 1 - redash/serializers/__init__.py | 2 -- 4 files changed, 40 deletions(-) delete mode 100644 migrations/versions/dfeaacdd7624_.py diff --git a/migrations/versions/dfeaacdd7624_.py b/migrations/versions/dfeaacdd7624_.py deleted file mode 100644 index 7feec38188..0000000000 --- a/migrations/versions/dfeaacdd7624_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: dfeaacdd7624 -Revises: e5c7a4e2df4d -Create Date: 2020-11-20 19:11:29.497508 - -""" -import json -from alembic import op -import sqlalchemy as sa - -from redash.models import MutableDict, PseudoJSON - -# revision identifiers, used by Alembic. -revision = 'dfeaacdd7624' -down_revision = 'e5c7a4e2df4d' -branch_labels = None -depends_on = None - - -def upgrade(): - # create "dashboard options" column as a dict type - op.add_column( - "dashboards", - sa.Column( - "options", - MutableDict.as_mutable(PseudoJSON), - nullable=False, - server_default=json.dumps({}), - ) - ) - - -def downgrade(): - op.drop_column("dashboards", "options") diff --git a/redash/handlers/dashboards.py b/redash/handlers/dashboards.py index 2dd9262922..5aace124d7 100644 --- a/redash/handlers/dashboards.py +++ b/redash/handlers/dashboards.py @@ -135,7 +135,6 @@ def get(self, dashboard_id=None): :>json boolean is_draft: Whether this dashboard is a draft or not. :>json array layout: Array of arrays containing widget IDs, corresponding to the rows and columns the widgets are displayed in :>json array widgets: Array of arrays containing :ref:`widget ` data - :>json object options: Dashboard options .. _widget-response-label: @@ -206,7 +205,6 @@ def post(self, dashboard_id): "is_draft", "is_archived", "dashboard_filters_enabled", - "options" ), ) diff --git a/redash/models/__init__.py b/redash/models/__init__.py index 537ef72dc6..68f0075434 100644 --- a/redash/models/__init__.py +++ b/redash/models/__init__.py @@ -1099,7 +1099,6 @@ class Dashboard(ChangeTrackingMixin, TimestampMixin, BelongsToOrgMixin, db.Model tags = Column( "tags", MutableList.as_mutable(postgresql.ARRAY(db.Unicode)), nullable=True ) - options = Column(db.Text) __tablename__ = "dashboards" __mapper_args__ = {"version_id_col": version} diff --git a/redash/serializers/__init__.py b/redash/serializers/__init__.py index 10f53d99b4..83b1fa266b 100644 --- a/redash/serializers/__init__.py +++ b/redash/serializers/__init__.py @@ -217,7 +217,6 @@ def serialize_alert(alert, full=True): def serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state=True): layout = json_loads(obj.layout) - options = json_loads(obj.options) widgets = [] @@ -257,7 +256,6 @@ def serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state= }, "layout": layout, "dashboard_filters_enabled": obj.dashboard_filters_enabled, - "options": options, "widgets": widgets, "is_archived": obj.is_archived, "is_draft": obj.is_draft, From 036b9ed9311d64ac4ee259d6e8b8ef0c6d8e8ee2 Mon Sep 17 00:00:00 2001 From: Rafael Wendel Date: Wed, 23 Dec 2020 18:49:51 -0300 Subject: [PATCH 14/32] Re-structured backend options --- migrations/versions/0ec979123ba4_.py | 28 ++++++++++++++++++++++++++++ redash/handlers/dashboards.py | 2 ++ redash/models/__init__.py | 4 +++- redash/serializers/__init__.py | 1 + 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/0ec979123ba4_.py diff --git a/migrations/versions/0ec979123ba4_.py b/migrations/versions/0ec979123ba4_.py new file mode 100644 index 0000000000..4dfbe1ba15 --- /dev/null +++ b/migrations/versions/0ec979123ba4_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 0ec979123ba4 +Revises: e5c7a4e2df4d +Create Date: 2020-12-23 21:35:32.766354 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '0ec979123ba4' +down_revision = 'e5c7a4e2df4d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('dashboards', sa.Column('options', postgresql.JSON(astext_type=sa.Text()), server_default='{}', nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('dashboards', 'options') + # ### end Alembic commands ### diff --git a/redash/handlers/dashboards.py b/redash/handlers/dashboards.py index 5aace124d7..33691bb729 100644 --- a/redash/handlers/dashboards.py +++ b/redash/handlers/dashboards.py @@ -135,6 +135,7 @@ def get(self, dashboard_id=None): :>json boolean is_draft: Whether this dashboard is a draft or not. :>json array layout: Array of arrays containing widget IDs, corresponding to the rows and columns the widgets are displayed in :>json array widgets: Array of arrays containing :ref:`widget ` data + :>json object options: Dashboard options .. _widget-response-label: @@ -205,6 +206,7 @@ def post(self, dashboard_id): "is_draft", "is_archived", "dashboard_filters_enabled", + "options", ), ) diff --git a/redash/models/__init__.py b/redash/models/__init__.py index 68f0075434..564a944755 100644 --- a/redash/models/__init__.py +++ b/redash/models/__init__.py @@ -1099,6 +1099,9 @@ class Dashboard(ChangeTrackingMixin, TimestampMixin, BelongsToOrgMixin, db.Model tags = Column( "tags", MutableList.as_mutable(postgresql.ARRAY(db.Unicode)), nullable=True ) + options = Column( + MutableDict.as_mutable(postgresql.JSON), server_default="{}", default={} + ) __tablename__ = "dashboards" __mapper_args__ = {"version_id_col": version} @@ -1132,7 +1135,6 @@ def all(cls, org, group_ids, user_id): ), Dashboard.org == org, ) - .distinct() ) query = query.filter( diff --git a/redash/serializers/__init__.py b/redash/serializers/__init__.py index 83b1fa266b..f123a93fbd 100644 --- a/redash/serializers/__init__.py +++ b/redash/serializers/__init__.py @@ -257,6 +257,7 @@ def serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state= "layout": layout, "dashboard_filters_enabled": obj.dashboard_filters_enabled, "widgets": widgets, + "options": obj.options, "is_archived": obj.is_archived, "is_draft": obj.is_draft, "tags": obj.tags or [], From 6071befdc5781e5f7d237b8ee8654037e148b573 Mon Sep 17 00:00:00 2001 From: Rafael Wendel Date: Mon, 28 Dec 2020 15:20:20 -0300 Subject: [PATCH 15/32] removed temporary edits --- client/app/components/Parameters.jsx | 1 - client/app/services/dashboard.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/client/app/components/Parameters.jsx b/client/app/components/Parameters.jsx index 3decfcc85a..3620de83dc 100644 --- a/client/app/components/Parameters.jsx +++ b/client/app/components/Parameters.jsx @@ -122,7 +122,6 @@ export default class Parameters extends React.Component {
- {/* TODO: add a prop to allow conditional show of this decoupled from sort */} {editable && (