You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by cc...@apache.org on 2018/05/12 08:34:16 UTC
[incubator-superset] branch dashboard-builder updated: Markdown for
dashboard (#4962)
This is an automated email from the ASF dual-hosted git repository.
ccwilliams pushed a commit to branch dashboard-builder
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/dashboard-builder by this push:
new 6b4e153 Markdown for dashboard (#4962)
6b4e153 is described below
commit 6b4e153526b66a5e0880439b7f0a7f1b9c6a253e
Author: Grace Guo <gr...@airbnb.com>
AuthorDate: Sat May 12 01:34:13 2018 -0700
Markdown for dashboard (#4962)
---
superset/assets/package.json | 3 +-
.../assets/src/dashboard/actions/sliceEntities.js | 46 +++--
.../dashboard/components/BuilderComponentPane.jsx | 18 +-
.../src/dashboard/components/DashboardBuilder.jsx | 2 +
.../components/gridComponents/Markdown.jsx | 225 +++++++++++++++++++++
.../dashboard/components/gridComponents/index.js | 4 +
.../components/gridComponents/new/NewMarkdown.jsx | 16 ++
.../components/menu/MarkdownModeDropdown.jsx | 39 ++++
.../src/dashboard/containers/DashboardBuilder.jsx | 2 +
.../src/dashboard/reducers/getInitialState.js | 38 ++--
.../dashboard/stylesheets/builder-sidepane.less | 12 +-
.../dashboard/stylesheets/components/index.less | 1 +
.../dashboard/stylesheets/components/markdown.less | 11 +
.../src/dashboard/util/dashboardLayoutConverter.js | 84 +++++---
superset/assets/src/modules/utils.js | 4 +
superset/views/core.py | 2 +-
16 files changed, 430 insertions(+), 77 deletions(-)
diff --git a/superset/assets/package.json b/superset/assets/package.json
index 576920a..33fe5cc 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -85,7 +85,7 @@
"prop-types": "^15.6.0",
"re-resizable": "^4.3.1",
"react": "^15.6.2",
- "react-ace": "^5.0.1",
+ "react-ace": "^5.10.0",
"react-addons-css-transition-group": "^15.6.0",
"react-addons-shallow-compare": "^15.4.2",
"react-alert": "^2.3.0",
@@ -98,6 +98,7 @@
"react-dom": "^15.6.2",
"react-gravatar": "^2.6.1",
"react-map-gl": "^3.0.4",
+ "react-markdown": "^3.3.0",
"react-redux": "^5.0.2",
"react-resizable": "^1.3.3",
"react-search-input": "^0.11.3",
diff --git a/superset/assets/src/dashboard/actions/sliceEntities.js b/superset/assets/src/dashboard/actions/sliceEntities.js
index 37781f9..b635ea0 100644
--- a/superset/assets/src/dashboard/actions/sliceEntities.js
+++ b/superset/assets/src/dashboard/actions/sliceEntities.js
@@ -1,6 +1,8 @@
/* eslint camelcase: 0 */
import $ from 'jquery';
+import { getDatasourceParameter } from '../../modules/utils';
+
export const SET_ALL_SLICES = 'SET_ALL_SLICES';
export function setAllSlices(slices) {
return { type: SET_ALL_SLICES, slices };
@@ -29,22 +31,34 @@ export function fetchAllSlices(userId) {
success: response => {
const slices = {};
response.result.forEach(slice => {
- const form_data = JSON.parse(slice.params);
- slices[slice.id] = {
- slice_id: slice.id,
- slice_url: slice.slice_url,
- slice_name: slice.slice_name,
- edit_url: slice.edit_url,
- form_data,
- datasource: form_data.datasource,
- datasource_name: slice.datasource_name_text,
- datasource_link: slice.datasource_link,
- changed_on: new Date(slice.changed_on).getTime(),
- description: slice.description,
- description_markdown: slice.description_markeddown,
- viz_type: slice.viz_type,
- modified: slice.modified,
- };
+ let form_data = JSON.parse(slice.params);
+ let datasource = form_data.datasource;
+ if (!datasource) {
+ datasource = getDatasourceParameter(
+ slice.datasource_id,
+ slice.datasource_type,
+ );
+ form_data = {
+ ...form_data,
+ datasource,
+ };
+ }
+ if (['markup', 'separator'].indexOf(slice.viz_type) === -1) {
+ slices[slice.id] = {
+ slice_id: slice.id,
+ slice_url: slice.slice_url,
+ slice_name: slice.slice_name,
+ edit_url: slice.edit_url,
+ form_data,
+ datasource_name: slice.datasource_name_text,
+ datasource_link: slice.datasource_link,
+ changed_on: new Date(slice.changed_on).getTime(),
+ description: slice.description,
+ description_markdown: slice.description_markeddown,
+ viz_type: slice.viz_type,
+ modified: slice.modified,
+ };
+ }
});
return dispatch(setAllSlices(slices));
},
diff --git a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx
index b42650e..c35a637 100644
--- a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx
+++ b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx
@@ -9,11 +9,13 @@ import NewDivider from './gridComponents/new/NewDivider';
import NewHeader from './gridComponents/new/NewHeader';
import NewRow from './gridComponents/new/NewRow';
import NewTabs from './gridComponents/new/NewTabs';
+import NewMarkdown from './gridComponents/new/NewMarkdown';
import SliceAdder from '../containers/SliceAdder';
import { t } from '../../locales';
const propTypes = {
topOffset: PropTypes.number,
+ toggleBuilderPane: PropTypes.func.isRequired,
};
const defaultProps = {
@@ -52,7 +54,12 @@ class BuilderComponentPane extends React.PureComponent {
>
<div className="component-layer slide-content">
<div className="dashboard-builder-sidepane-header">
- {t('Saved components')}
+ <span>{t('Insert')}</span>
+ <i
+ className="fa fa-times trigger"
+ onClick={this.props.toggleBuilderPane}
+ role="none"
+ />
</div>
<div
className="new-component static"
@@ -67,17 +74,12 @@ class BuilderComponentPane extends React.PureComponent {
<i className="fa fa-arrow-right trigger" />
</div>
- <div className="dashboard-builder-sidepane-header">
- {t('Containers')}
- </div>
<NewTabs />
<NewRow />
<NewColumn />
- <div className="dashboard-builder-sidepane-header">
- {t('More components')}
- </div>
<NewHeader />
+ <NewMarkdown />
<NewDivider />
</div>
<div className="slices-layer slide-content">
@@ -87,7 +89,7 @@ class BuilderComponentPane extends React.PureComponent {
role="none"
>
<i className="fa fa-arrow-left trigger" />
- {t('All components')}
+ <span>{t('All components')}</span>
</div>
<SliceAdder height={calculatedHeight} />
</div>
diff --git a/superset/assets/src/dashboard/components/DashboardBuilder.jsx b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
index 7f92948..0951ebf 100644
--- a/superset/assets/src/dashboard/components/DashboardBuilder.jsx
+++ b/superset/assets/src/dashboard/components/DashboardBuilder.jsx
@@ -34,6 +34,7 @@ const propTypes = {
editMode: PropTypes.bool.isRequired,
showBuilderPane: PropTypes.bool,
handleComponentDrop: PropTypes.func.isRequired,
+ toggleBuilderPane: PropTypes.func.isRequired,
};
const defaultProps = {
@@ -184,6 +185,7 @@ class DashboardBuilder extends React.Component {
this.props.showBuilderPane && (
<BuilderComponentPane
topOffset={topLevelTabs ? TABS_HEIGHT : 0}
+ toggleBuilderPane={this.props.toggleBuilderPane}
/>
)}
</div>
diff --git a/superset/assets/src/dashboard/components/gridComponents/Markdown.jsx b/superset/assets/src/dashboard/components/gridComponents/Markdown.jsx
new file mode 100644
index 0000000..459f89a
--- /dev/null
+++ b/superset/assets/src/dashboard/components/gridComponents/Markdown.jsx
@@ -0,0 +1,225 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ReactMarkdown from 'react-markdown';
+import AceEditor from 'react-ace';
+import 'brace/mode/markdown';
+import 'brace/theme/textmate';
+
+import DeleteComponentButton from '../DeleteComponentButton';
+import DragDroppable from '../dnd/DragDroppable';
+import ResizableContainer from '../resizable/ResizableContainer';
+import MarkdownModeDropdown from '../menu/MarkdownModeDropdown';
+import WithPopoverMenu from '../menu/WithPopoverMenu';
+import { componentShape } from '../../util/propShapes';
+import { ROW_TYPE, COLUMN_TYPE } from '../../util/componentTypes';
+import {
+ GRID_MIN_COLUMN_COUNT,
+ GRID_MIN_ROW_UNITS,
+ GRID_BASE_UNIT,
+} from '../../util/constants';
+
+const propTypes = {
+ id: PropTypes.string.isRequired,
+ parentId: PropTypes.string.isRequired,
+ component: componentShape.isRequired,
+ parentComponent: componentShape.isRequired,
+ index: PropTypes.number.isRequired,
+ depth: PropTypes.number.isRequired,
+ editMode: PropTypes.bool.isRequired,
+
+ // grid related
+ availableColumnCount: PropTypes.number.isRequired,
+ columnWidth: PropTypes.number.isRequired,
+ onResizeStart: PropTypes.func.isRequired,
+ onResize: PropTypes.func.isRequired,
+ onResizeStop: PropTypes.func.isRequired,
+
+ // dnd
+ deleteComponent: PropTypes.func.isRequired,
+ handleComponentDrop: PropTypes.func.isRequired,
+ updateComponents: PropTypes.func.isRequired,
+};
+
+const defaultProps = {};
+const markdownPlaceHolder = `### New Markdown
+Insert *bold* or _italic_ text, and (urls)[www.url.com] here.`;
+
+class Markdown extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ isFocused: false,
+ markdownSource: props.component.meta.code,
+ editor: null,
+ editorMode: props.component.meta.code ? 'preview' : 'edit', // show edit mode when code is empty
+ };
+
+ this.handleChangeFocus = this.handleChangeFocus.bind(this);
+ this.handleChangeEditorMode = this.handleChangeEditorMode.bind(this);
+ this.handleMarkdownChange = this.handleMarkdownChange.bind(this);
+ this.handleDeleteComponent = this.handleDeleteComponent.bind(this);
+ this.setEditor = this.setEditor.bind(this);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (
+ this.state.editor &&
+ (prevProps.component.meta.width !== this.props.component.meta.width ||
+ prevProps.columnWidth !== this.props.columnWidth)
+ ) {
+ this.state.editor.resize(true);
+ }
+ }
+
+ setEditor(editor) {
+ editor.getSession().setUseWrapMode(true);
+ this.setState({
+ editor,
+ });
+ }
+
+ handleChangeFocus(nextFocus) {
+ this.setState(() => ({ isFocused: Boolean(nextFocus) }));
+ }
+
+ handleChangeEditorMode(mode) {
+ if (this.state.editorMode === 'edit') {
+ const { updateComponents, component } = this.props;
+ if (component.meta.code !== this.state.markdownSource) {
+ updateComponents({
+ [component.id]: {
+ ...component,
+ meta: {
+ ...component.meta,
+ code: this.state.markdownSource,
+ },
+ },
+ });
+ }
+ }
+
+ this.setState(() => ({
+ editorMode: mode,
+ }));
+ }
+
+ handleMarkdownChange(nextValue) {
+ this.setState({
+ markdownSource: nextValue,
+ });
+ }
+
+ handleDeleteComponent() {
+ const { deleteComponent, id, parentId } = this.props;
+ deleteComponent(id, parentId);
+ }
+
+ renderEditMode() {
+ return (
+ <AceEditor
+ mode="markdown"
+ theme="textmate"
+ onChange={this.handleMarkdownChange}
+ width={'100%'}
+ height={'100%'}
+ editorProps={{ $blockScrolling: true }}
+ value={this.state.markdownSource || markdownPlaceHolder}
+ readOnly={false}
+ onLoad={this.setEditor}
+ />
+ );
+ }
+
+ renderPreviewMode() {
+ return (
+ <ReactMarkdown source={this.state.markdownSource} escapeHtml={false} />
+ );
+ }
+
+ render() {
+ const { isFocused } = this.state;
+
+ const {
+ component,
+ parentComponent,
+ index,
+ depth,
+ availableColumnCount,
+ columnWidth,
+ onResizeStart,
+ onResize,
+ onResizeStop,
+ handleComponentDrop,
+ editMode,
+ } = this.props;
+
+ // inherit the size of parent columns
+ const widthMultiple =
+ parentComponent.type === COLUMN_TYPE
+ ? parentComponent.meta.width || GRID_MIN_COLUMN_COUNT
+ : component.meta.width || GRID_MIN_COLUMN_COUNT;
+
+ return (
+ <DragDroppable
+ component={component}
+ parentComponent={parentComponent}
+ orientation={depth % 2 === 1 ? 'column' : 'row'}
+ index={index}
+ depth={depth}
+ onDrop={handleComponentDrop}
+ disableDragDrop={isFocused}
+ editMode={editMode}
+ >
+ {({ dropIndicatorProps, dragSourceRef }) => (
+ <WithPopoverMenu
+ onChangeFocus={this.handleChangeFocus}
+ menuItems={[
+ <MarkdownModeDropdown
+ id={`${component.id}-mode`}
+ value={this.state.editorMode}
+ onChange={this.handleChangeEditorMode}
+ />,
+ <DeleteComponentButton onDelete={this.handleDeleteComponent} />,
+ ]}
+ editMode={editMode}
+ >
+ <div className="dashboard-markdown">
+ <ResizableContainer
+ id={component.id}
+ adjustableWidth={parentComponent.type === ROW_TYPE}
+ adjustableHeight
+ widthStep={columnWidth}
+ widthMultiple={widthMultiple}
+ heightStep={GRID_BASE_UNIT}
+ heightMultiple={component.meta.height}
+ minWidthMultiple={GRID_MIN_COLUMN_COUNT}
+ minHeightMultiple={GRID_MIN_ROW_UNITS}
+ maxWidthMultiple={availableColumnCount + widthMultiple}
+ onResizeStart={onResizeStart}
+ onResize={onResize}
+ onResizeStop={onResizeStop}
+ editMode={editMode}
+ >
+ <div
+ ref={dragSourceRef}
+ className="dashboard-component dashboard-component-chart-holder"
+ >
+ {editMode && this.state.editorMode === 'edit'
+ ? this.renderEditMode()
+ : this.renderPreviewMode()}
+ </div>
+
+ {dropIndicatorProps && <div {...dropIndicatorProps} />}
+ </ResizableContainer>
+ </div>
+ </WithPopoverMenu>
+ )}
+ </DragDroppable>
+ );
+ }
+}
+
+Markdown.propTypes = propTypes;
+Markdown.defaultProps = defaultProps;
+
+export default Markdown;
diff --git a/superset/assets/src/dashboard/components/gridComponents/index.js b/superset/assets/src/dashboard/components/gridComponents/index.js
index 016ab03..c56bed0 100644
--- a/superset/assets/src/dashboard/components/gridComponents/index.js
+++ b/superset/assets/src/dashboard/components/gridComponents/index.js
@@ -1,5 +1,6 @@
import {
CHART_TYPE,
+ MARKDOWN_TYPE,
COLUMN_TYPE,
DIVIDER_TYPE,
HEADER_TYPE,
@@ -9,6 +10,7 @@ import {
} from '../../util/componentTypes';
import ChartHolder from './ChartHolder';
+import Markdown from './Markdown';
import Column from './Column';
import Divider from './Divider';
import Header from './Header';
@@ -17,6 +19,7 @@ import Tab from './Tab';
import Tabs from './Tabs';
export { default as ChartHolder } from './ChartHolder';
+export { default as Markdown } from './Markdown';
export { default as Column } from './Column';
export { default as Divider } from './Divider';
export { default as Header } from './Header';
@@ -26,6 +29,7 @@ export { default as Tabs } from './Tabs';
export default {
[CHART_TYPE]: ChartHolder,
+ [MARKDOWN_TYPE]: Markdown,
[COLUMN_TYPE]: Column,
[DIVIDER_TYPE]: Divider,
[HEADER_TYPE]: Header,
diff --git a/superset/assets/src/dashboard/components/gridComponents/new/NewMarkdown.jsx b/superset/assets/src/dashboard/components/gridComponents/new/NewMarkdown.jsx
new file mode 100644
index 0000000..e4c8892
--- /dev/null
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewMarkdown.jsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { MARKDOWN_TYPE } from '../../../util/componentTypes';
+import { NEW_MARKDOWN_ID } from '../../../util/constants';
+import DraggableNewComponent from './DraggableNewComponent';
+
+export default function DraggableNewDivider() {
+ return (
+ <DraggableNewComponent
+ id={NEW_MARKDOWN_ID}
+ type={MARKDOWN_TYPE}
+ label="Markdown"
+ className="fa fa-code"
+ />
+ );
+}
diff --git a/superset/assets/src/dashboard/components/menu/MarkdownModeDropdown.jsx b/superset/assets/src/dashboard/components/menu/MarkdownModeDropdown.jsx
new file mode 100644
index 0000000..10aa932
--- /dev/null
+++ b/superset/assets/src/dashboard/components/menu/MarkdownModeDropdown.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { t } from '../../../locales';
+
+import PopoverDropdown from './PopoverDropdown';
+
+const propTypes = {
+ id: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+const dropdownOptions = [
+ {
+ value: 'edit',
+ label: t('Edit'),
+ },
+ {
+ value: 'preview',
+ label: t('Preview'),
+ },
+];
+
+export default class MarkdownModeDropdown extends React.PureComponent {
+ render() {
+ const { id, value, onChange } = this.props;
+
+ return (
+ <PopoverDropdown
+ id={id}
+ options={dropdownOptions}
+ value={value}
+ onChange={onChange}
+ />
+ );
+ }
+}
+
+MarkdownModeDropdown.propTypes = propTypes;
diff --git a/superset/assets/src/dashboard/containers/DashboardBuilder.jsx b/superset/assets/src/dashboard/containers/DashboardBuilder.jsx
index 6bece3d..fde1e76 100644
--- a/superset/assets/src/dashboard/containers/DashboardBuilder.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardBuilder.jsx
@@ -2,6 +2,7 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import DashboardBuilder from '../components/DashboardBuilder';
+import { toggleBuilderPane } from '../actions/dashboardState';
import {
deleteTopLevelTabs,
handleComponentDrop,
@@ -20,6 +21,7 @@ function mapDispatchToProps(dispatch) {
{
deleteTopLevelTabs,
handleComponentDrop,
+ toggleBuilderPane,
},
dispatch,
);
diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js
index ba24b36..b209043 100644
--- a/superset/assets/src/dashboard/reducers/getInitialState.js
+++ b/superset/assets/src/dashboard/reducers/getInitialState.js
@@ -74,26 +74,28 @@ export default function(bootstrapData) {
const sliceIds = new Set();
dashboard.slices.forEach(slice => {
const key = slice.slice_id;
- chartQueries[key] = {
- ...chart,
- id: key,
- form_data: slice.form_data,
- formData: applyDefaultFormData(slice.form_data),
- };
+ if (['separator', 'markup'].indexOf(slice.form_data.viz_type) === -1) {
+ chartQueries[key] = {
+ ...chart,
+ id: key,
+ form_data: slice.form_data,
+ formData: applyDefaultFormData(slice.form_data),
+ };
- slices[key] = {
- slice_id: key,
- slice_url: slice.slice_url,
- slice_name: slice.slice_name,
- form_data: slice.form_data,
- edit_url: slice.edit_url,
- viz_type: slice.form_data.viz_type,
- datasource: slice.form_data.datasource,
- description: slice.description,
- description_markeddown: slice.description_markeddown,
- };
+ slices[key] = {
+ slice_id: key,
+ slice_url: slice.slice_url,
+ slice_name: slice.slice_name,
+ form_data: slice.form_data,
+ edit_url: slice.edit_url,
+ viz_type: slice.form_data.viz_type,
+ datasource: slice.form_data.datasource,
+ description: slice.description,
+ description_markeddown: slice.description_markeddown,
+ };
- sliceIds.add(key);
+ sliceIds.add(key);
+ }
// sync layout names with current slice names in case a slice was edited
// in explore since the layout was updated. name updates go through layout for undo/redo
diff --git a/superset/assets/src/dashboard/stylesheets/builder-sidepane.less b/superset/assets/src/dashboard/stylesheets/builder-sidepane.less
index d45da4f..5f87d0c 100644
--- a/superset/assets/src/dashboard/stylesheets/builder-sidepane.less
+++ b/superset/assets/src/dashboard/stylesheets/builder-sidepane.less
@@ -10,13 +10,21 @@
border-top: 1px solid @gray-light;
border-bottom: 1px solid @gray-light;
padding: 16px;
+ display: flex;
+ align-items: center;
}
.trigger {
- height: 18px;
- width: 25px;
+ font-size: 16px;
color: @almost-black;
opacity: 1;
+ margin-left: auto;
+ cursor: pointer;
+ }
+
+ .slices-layer .trigger {
+ margin-left: 0;
+ margin-right: 20px;
}
.viewport {
diff --git a/superset/assets/src/dashboard/stylesheets/components/index.less b/superset/assets/src/dashboard/stylesheets/components/index.less
index 5a1803e..5f8d610 100644
--- a/superset/assets/src/dashboard/stylesheets/components/index.less
+++ b/superset/assets/src/dashboard/stylesheets/components/index.less
@@ -5,3 +5,4 @@
@import './new-component.less';
@import './row.less';
@import './tabs.less';
+@import './markdown.less';
\ No newline at end of file
diff --git a/superset/assets/src/dashboard/stylesheets/components/markdown.less b/superset/assets/src/dashboard/stylesheets/components/markdown.less
new file mode 100644
index 0000000..d377c68
--- /dev/null
+++ b/superset/assets/src/dashboard/stylesheets/components/markdown.less
@@ -0,0 +1,11 @@
+.dashboard-markdown {
+ overflow: hidden;
+
+ .dashboard--editing & {
+ cursor: move;
+ }
+
+ #brace-editor {
+ border: none;
+ }
+}
\ No newline at end of file
diff --git a/superset/assets/src/dashboard/util/dashboardLayoutConverter.js b/superset/assets/src/dashboard/util/dashboardLayoutConverter.js
index f3f6061..e28e3be 100644
--- a/superset/assets/src/dashboard/util/dashboardLayoutConverter.js
+++ b/superset/assets/src/dashboard/util/dashboardLayoutConverter.js
@@ -5,6 +5,7 @@ import {
ROW_TYPE,
COLUMN_TYPE,
CHART_TYPE,
+ MARKDOWN_TYPE,
DASHBOARD_ROOT_TYPE,
DASHBOARD_GRID_TYPE,
} from './componentTypes';
@@ -76,11 +77,22 @@ function getColContainer() {
}
function getChartHolder(item) {
- const { size_x, size_y, slice_id } = item;
+ const { size_x, size_y, slice_id, code } = item;
const width = Math.max(1, Math.floor(size_x / GRID_RATIO));
const height = Math.max(1, Math.round(size_y / GRID_RATIO));
-
+ if (code !== undefined) {
+ return {
+ type: MARKDOWN_TYPE,
+ id: `DASHBOARD_MARKDOWN_TYPE-${generateId()}`,
+ children: [],
+ meta: {
+ width,
+ height: Math.round(height * 100 / ROW_HEIGHT),
+ code,
+ },
+ };
+ }
return {
type: CHART_TYPE,
id: `DASHBOARD_CHART_TYPE-${generateId()}`,
@@ -135,7 +147,7 @@ function doConvert(positions, level, parent, root) {
if (positions.length === 1 || level >= MAX_RECURSIVE_LEVEL) {
// special treatment for single chart dash, always wrap chart inside a row
- if (parent.type === 'DASHBOARD_GRID_TYPE') {
+ if (parent.type === DASHBOARD_GRID_TYPE) {
const rowContainer = getRowContainer();
root[rowContainer.id] = rowContainer;
parent.children.push(rowContainer.id);
@@ -181,7 +193,7 @@ function doConvert(positions, level, parent, root) {
return;
}
- if (layer.length === 1) {
+ if (layer.length === 1 && parent.type === COLUMN_TYPE) {
const chartHolder = getChartHolder(layer[0]);
root[chartHolder.id] = chartHolder;
parent.children.push(chartHolder.id);
@@ -262,6 +274,35 @@ function doConvert(positions, level, parent, root) {
});
}
+export function convertToLayout(positions) {
+ const root = {
+ [DASHBOARD_VERSION_KEY]: 'v2',
+ [DASHBOARD_ROOT_ID]: {
+ type: DASHBOARD_ROOT_TYPE,
+ id: DASHBOARD_ROOT_ID,
+ children: [DASHBOARD_GRID_ID],
+ },
+ [DASHBOARD_GRID_ID]: {
+ type: DASHBOARD_GRID_TYPE,
+ id: DASHBOARD_GRID_ID,
+ children: [],
+ },
+ };
+
+ doConvert(positions, 0, root[DASHBOARD_GRID_ID], root);
+
+ // remove row's width/height and col's height
+ Object.values(root).forEach(item => {
+ if (ROW_TYPE === item.type) {
+ const meta = item.meta;
+ delete meta.width;
+ }
+ });
+
+ // console.log(JSON.stringify(root));
+ return root;
+}
+
export default function(dashboard) {
const positions = [];
@@ -281,7 +322,7 @@ export default function(dashboard) {
Math.max.apply(null, position_json.map(pos => pos.row + pos.size_y)),
);
let newSliceCounter = 0;
- dashboard.slices.forEach(({ slice_id }) => {
+ dashboard.slices.forEach(({ slice_id, form_data }) => {
let position = positionDict[slice_id];
if (!position) {
// append new slices to dashboard bottom, 3 slices per row
@@ -294,33 +335,14 @@ export default function(dashboard) {
};
newSliceCounter += 1;
}
-
- positions.push(position);
- });
-
- const root = {
- [DASHBOARD_VERSION_KEY]: 'v2',
- [DASHBOARD_ROOT_ID]: {
- type: DASHBOARD_ROOT_TYPE,
- id: DASHBOARD_ROOT_ID,
- children: [DASHBOARD_GRID_ID],
- },
- [DASHBOARD_GRID_ID]: {
- type: DASHBOARD_GRID_TYPE,
- id: DASHBOARD_GRID_ID,
- children: [],
- },
- };
-
- doConvert(positions, 0, root[DASHBOARD_GRID_ID], root);
-
- // remove row's width/height and col's height
- Object.values(root).forEach(item => {
- if (ROW_TYPE === item.type) {
- const meta = item.meta;
- delete meta.width;
+ if (form_data && ['markup', 'separator'].indexOf(form_data.viz_type) > -1) {
+ position = {
+ ...position,
+ code: form_data.code,
+ };
}
+ positions.push(position);
});
- return root;
+ return convertToLayout(positions);
}
diff --git a/superset/assets/src/modules/utils.js b/superset/assets/src/modules/utils.js
index c4ea9ce..eb937bb 100644
--- a/superset/assets/src/modules/utils.js
+++ b/superset/assets/src/modules/utils.js
@@ -202,6 +202,10 @@ export function getAjaxErrorMsg(error) {
error.responseText;
}
+export function getDatasourceParameter(datasourceId, datasourceType) {
+ return `${datasourceId}__${datasourceType}`;
+}
+
export function customizeToolTip(chart, xAxisFormatter, yAxisFormatters) {
chart.useInteractiveGuideline(true);
chart.interactiveLayer.tooltip.contentGenerator(function (d) {
diff --git a/superset/views/core.py b/superset/views/core.py
index fde5be7..2ff3500 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -518,7 +518,7 @@ appbuilder.add_view_no_menu(SliceAsync)
class SliceAddView(SliceModelView): # noqa
list_columns = [
'id', 'slice_name', 'slice_url', 'edit_url', 'viz_type', 'params',
- 'description', 'description_markeddown',
+ 'description', 'description_markeddown', 'datasource_id', 'datasource_type',
'datasource_name_text', 'datasource_link',
'owners', 'modified', 'changed_on']
--
To stop receiving notification emails like this one, please contact
ccwilliams@apache.org.