Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Widget component to React #4020

Merged
merged 40 commits into from
Sep 20, 2019
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a02ce8f
Improve sizing for Number inputs
gabrieldutra Aug 1, 2019
97bfcd3
Migrate WidgetDialog
gabrieldutra Jul 31, 2019
d415c5e
Start migrating Widget
gabrieldutra Jul 31, 2019
b7cbf80
Update textbox to use HtmlContent
gabrieldutra Jul 31, 2019
4413063
QueryLink migration and some updates
gabrieldutra Aug 6, 2019
1b9d002
Add visualization rendering
gabrieldutra Aug 6, 2019
673f6c8
Render widget
gabrieldutra Aug 8, 2019
0d95996
Add delete button
gabrieldutra Aug 12, 2019
dd9fd35
Update AutoHeight
gabrieldutra Aug 12, 2019
ca01216
Add widget bottom
gabrieldutra Aug 12, 2019
7738c9e
Add Drodpown button
gabrieldutra Aug 13, 2019
ffcb419
Split Widget component
gabrieldutra Aug 15, 2019
45618c3
Update with #4056 and trigger netlify
gabrieldutra Aug 18, 2019
3ba42c0
In progress: use composition
gabrieldutra Aug 19, 2019
a7f6d41
Add header and footer
gabrieldutra Aug 22, 2019
5021b44
Update widget actions positioning
gabrieldutra Aug 22, 2019
a34ad85
Re-render when refreshing from widget
gabrieldutra Aug 22, 2019
71f6e8f
Add workaround to force DashboardGrid re-render
gabrieldutra Aug 24, 2019
1622f84
VisualizationWidgetFooter component
gabrieldutra Aug 25, 2019
a5ab07d
VisualizationWidget menu
gabrieldutra Aug 27, 2019
5f66382
Separate RestrictedWidget
gabrieldutra Aug 27, 2019
8838e97
Update tests
gabrieldutra Aug 27, 2019
584200c
Update margin for Parameters
gabrieldutra Aug 27, 2019
daf21ac
Merge branch 'master' into react-widget
gabrieldutra Aug 27, 2019
9f269c1
Remove widget files
gabrieldutra Aug 27, 2019
7f6bcd7
Revert "Improve sizing for Number inputs"
gabrieldutra Aug 28, 2019
111b68e
Some cleanup
gabrieldutra Aug 29, 2019
b81a555
Merge branch 'master' into react-widget
gabrieldutra Aug 30, 2019
6d6f81a
Move refresh logic to the Dashboard
gabrieldutra Aug 31, 2019
83e7149
Add loadingWidgets logic to the public dashboard
gabrieldutra Sep 2, 2019
ade0b48
Merge branch 'master' into react-widget
gabrieldutra Sep 2, 2019
da6e93e
Add onLoadWidget
gabrieldutra Sep 2, 2019
5b3e9b9
Remove parameter from URL when empty
gabrieldutra Sep 2, 2019
7f24c27
Recreate widget array instead of loadingWidgets
gabrieldutra Sep 3, 2019
4716d4b
Add comment about re-rendering + whitespace missing
gabrieldutra Sep 3, 2019
72da894
Merge branch 'master' into react-widget
gabrieldutra Sep 11, 2019
cd69e16
CR changes
gabrieldutra Sep 11, 2019
80897e4
Use plain html instead of string syntax
gabrieldutra Sep 11, 2019
3f49b59
Merge branch 'master' into react-widget
gabrieldutra Sep 18, 2019
97f402a
Merge branch 'react-widget' of github.com:getredash/redash into react…
gabrieldutra Sep 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/app/assets/less/inc/visualizations/misc.less
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
visualization-renderer {
.visualization-renderer {
display: block;

.pagination,
Expand Down
2 changes: 1 addition & 1 deletion client/app/assets/less/inc/visualizations/pivot-table.less
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.pivot-table-renderer > table, grid-renderer > div, visualization-renderer > div {
.pivot-table-renderer > table, grid-renderer > div, .visualization-renderer > div {
overflow: auto;
}
2 changes: 1 addition & 1 deletion client/app/assets/less/redash/query.less
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ a.label-tag {
border-bottom: 1px solid #efefef;
}

.pivot-table-renderer > table, grid-renderer > div, visualization-renderer > div {
.pivot-table-renderer > table, grid-renderer > div, .visualization-renderer > div {
overflow: visible;
}

Expand Down
2 changes: 1 addition & 1 deletion client/app/components/Parameters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ export class Parameters extends React.Component {
this.setState(({ parameters }) => {
const parametersWithPendingValues = parameters.filter(p => p.hasPendingValue);
forEach(parameters, p => p.applyPendingValue());
onValuesChange(parametersWithPendingValues);
if (!disableUrlUpdate) {
updateUrl(parameters);
}
onValuesChange(parametersWithPendingValues);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Dashboard context it needs to have the url updated before triggering onValuesChange

return { parameters };
});
};
Expand Down
40 changes: 40 additions & 0 deletions client/app/components/QueryLink.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import { VisualizationType } from '@/visualizations';
import { VisualizationName } from '@/visualizations/VisualizationName';

function QueryLink({ query, visualization, readOnly }) {
const getUrl = () => {
let hash = null;
if (visualization) {
if (visualization.type === 'TABLE') {
// link to hard-coded table tab instead of the (hidden) visualization tab
hash = 'table';
} else {
hash = visualization.id;
}
}

return query.getUrl(false, hash);
};

return (
<a href={readOnly ? null : getUrl()} className="query-link">
<VisualizationName visualization={visualization} />{' '}
<span>{query.name}</span>
</a>
);
}

QueryLink.propTypes = {
query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
visualization: VisualizationType,
readOnly: PropTypes.bool,
};

QueryLink.defaultProps = {
visualization: null,
readOnly: false,
};

export default QueryLink;
4 changes: 2 additions & 2 deletions client/app/components/Timer.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo, useEffect } from 'react';
import moment from 'moment';
import { useMemo, useEffect } from 'react';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
import { Moment } from '@/components/proptypes';
Expand All @@ -17,7 +17,7 @@ export function Timer({ from }) {
const diff = moment.now() - startTime;
const format = diff > 1000 * 60 * 60 ? 'HH:mm:ss' : 'mm:ss'; // no HH under an hour

return moment.utc(diff).format(format);
return (<span className="rd-timer">{moment.utc(diff).format(format)}</span>);
}

Timer.propTypes = {
Expand Down
2 changes: 1 addition & 1 deletion client/app/components/dashboards/AutoHeightController.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { includes, reduce, some } from 'lodash';
const WIDGET_SELECTOR = '[data-widgetid="{0}"]';
const WIDGET_CONTENT_SELECTOR = [
'.widget-header', // header
'visualization-renderer', // visualization
'.visualization-renderer', // visualization
'.scrollbox .alert', // error state
'.spinner-container', // loading state
'.tile__bottom-control', // footer
Expand Down
59 changes: 40 additions & 19 deletions client/app/components/dashboards/DashboardGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { chain, cloneDeep, find } from 'lodash';
import { react2angular } from 'react2angular';
import cx from 'classnames';
import { Responsive, WidthProvider } from 'react-grid-layout';
import { DashboardWidget } from '@/components/dashboards/widget';
import { VisualizationWidget, TextboxWidget, RestrictedWidget } from '@/components/dashboards/dashboard-widget';
import { FiltersType } from '@/components/Filters';
import cfg from '@/config/dashboard-grid-options';
import AutoHeightController from './AutoHeightController';
Expand Down Expand Up @@ -41,16 +41,24 @@ class DashboardGrid extends React.Component {
widgets: PropTypes.arrayOf(WidgetType).isRequired,
filters: FiltersType,
onBreakpointChange: PropTypes.func,
onRefreshWidget: PropTypes.func,
onRemoveWidget: PropTypes.func,
onLayoutChange: PropTypes.func,
onParameterMappingsChange: PropTypes.func,
// Force component update when widgets are refreshing.
// Remove this when Dashboard is migrated to React
refreshingWidgets: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
};

static defaultProps = {
isPublic: false,
filters: [],
onRefreshWidget: () => {},
onRemoveWidget: () => {},
onLayoutChange: () => {},
onBreakpointChange: () => {},
onParameterMappingsChange: () => {},
refreshingWidgets: 0,
};

static normalizeFrom(widget) {
Expand Down Expand Up @@ -168,7 +176,8 @@ class DashboardGrid extends React.Component {

render() {
const className = cx('dashboard-wrapper', this.props.isEditing ? 'editing-mode' : 'preview-mode');
const { onRemoveWidget, dashboard, widgets } = this.props;
const { onRefreshWidget, onRemoveWidget, onParameterMappingsChange,
filters, dashboard, isPublic, widgets } = this.props;

return (
<div className={className}>
Expand All @@ -186,23 +195,35 @@ class DashboardGrid extends React.Component {
onBreakpointChange={this.onBreakpointChange}
breakpoints={{ [MULTI]: cfg.mobileBreakPoint, [SINGLE]: 0 }}
>
{widgets.map(widget => (
<div
key={widget.id}
data-grid={DashboardGrid.normalizeFrom(widget)}
data-widgetid={widget.id}
data-test={`WidgetId${widget.id}`}
className={cx('dashboard-widget-wrapper', { 'widget-auto-height-enabled': this.autoHeightCtrl.exists(widget.id) })}
>
<DashboardWidget
widget={widget}
dashboard={dashboard}
filters={this.props.filters}
deleted={() => onRemoveWidget(widget.id)}
public={this.props.isPublic}
/>
</div>
))}
{widgets.map((widget) => {
const widgetProps = {
widget,
filters,
isPublic,
canEdit: dashboard.canEdit(),
onDelete: () => onRemoveWidget(widget.id),
};
return (
<div
key={widget.id}
data-grid={DashboardGrid.normalizeFrom(widget)}
data-widgetid={widget.id}
data-test={`WidgetId${widget.id}`}
className={cx('dashboard-widget-wrapper', { 'widget-auto-height-enabled': this.autoHeightCtrl.exists(widget.id) })}
>
{widget.getType() === 'visualization' && (
<VisualizationWidget
{...widgetProps}
dashboard={dashboard}
onRefresh={() => onRefreshWidget(widget)}
onParameterMappingsChange={onParameterMappingsChange}
/>
)}
{widget.getType() === 'textbox' && <TextboxWidget {...widgetProps} />}
{widget.getType() === 'restricted' && <RestrictedWidget widget={widget} />}
ranbena marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
})}
</ResponsiveGridLayout>
</div>
);
Expand Down
32 changes: 32 additions & 0 deletions client/app/components/dashboards/ExpandedWidgetDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import { VisualizationRenderer } from '@/visualizations/VisualizationRenderer';
import { wrap as wrapDialog, DialogPropType } from '@/components/DialogWrapper';
import { VisualizationName } from '@/visualizations/VisualizationName';

function ExpandedWidgetDialog({ dialog, widget }) {
return (
<Modal
{...dialog.props}
title={(
<>
<VisualizationName visualization={widget.visualization} />
<span>{widget.getQuery().name}</span>
</>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

He-he, GitHub has issues with parsing <>...</> 😁

)}
width="95%"
footer={(<Button onClick={dialog.dismiss}>Close</Button>)}
>
<VisualizationRenderer visualization={widget.visualization} queryResult={widget.getQueryResult()} />
</Modal>
);
}

ExpandedWidgetDialog.propTypes = {
dialog: DialogPropType.isRequired,
widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};

export default wrapDialog(ExpandedWidgetDialog);
1 change: 0 additions & 1 deletion client/app/components/dashboards/TextboxDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import './TextboxDialog.less';

class TextboxDialog extends React.Component {
static propTypes = {
dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
dialog: DialogPropType.isRequired,
onConfirm: PropTypes.func.isRequired,
text: PropTypes.string,
Expand Down
29 changes: 29 additions & 0 deletions client/app/components/dashboards/dashboard-grid.less
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,32 @@
}
}
}

// react-grid-layout overrides
.react-grid-item {

// placeholder color
&.react-grid-placeholder {
border-radius: 3px;
background-color: #E0E6EB;
opacity: 0.5;
}

// resize placeholder behind widget, the lib's default is above 🤷‍♂️
&.resizing {
z-index: 3;
}

// auto-height animation
&.cssTransforms:not(.resizing) {
transition-property: transform, height; // added ", height"
}

// resize handle size
& > .react-resizable-handle::after {
width: 11px;
height: 11px;
right: 5px;
bottom: 5px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import Widget from './Widget';

function RestrictedWidget(props) {
return (
<Widget {...props} className="d-flex justify-content-center align-items-center widget-restricted">
<div className="t-body scrollbox">
<div className="text-center">
<h1><span className="zmdi zmdi-lock" /></h1>
<p className="text-muted">
{'This widget requires access to a data source you don\'t have access to.'}
gabrieldutra marked this conversation as resolved.
Show resolved Hide resolved
</p>
</div>
</div>
</Widget>
);
}

export default RestrictedWidget;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { markdown } from 'markdown';
import Menu from 'antd/lib/menu';
import HtmlContent from '@/components/HtmlContent';
import TextboxDialog from '@/components/dashboards/TextboxDialog';
import Widget from './Widget';

function TextboxWidget(props) {
const { widget, canEdit } = props;
const [text, setText] = useState(widget.text);

const editTextBox = () => {
TextboxDialog.showModal({
text: widget.text,
onConfirm: (newText) => {
widget.text = newText;
setText(newText);
return widget.save();
},
});
};

const TextboxMenuOptions = [
<Menu.Item key="edit" onClick={editTextBox}>Edit</Menu.Item>,
];

if (!widget.width) {
return null;
}

return (
<Widget {...props} menuOptions={canEdit ? TextboxMenuOptions : null} className="widget-text">
<HtmlContent className="body-row-auto scrollbox tiled t-body p-15 markdown">
ranbena marked this conversation as resolved.
Show resolved Hide resolved
{markdown.toHTML(text || '')}
</HtmlContent>
</Widget>
);
}

TextboxWidget.propTypes = {
widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool,
};

TextboxWidget.defaultProps = {
canEdit: false,
};

export default TextboxWidget;
Loading