diff --git a/CODING_GUIDELINES.md b/CODING_GUIDELINES.md new file mode 100644 index 000000000..0856b3a4b --- /dev/null +++ b/CODING_GUIDELINES.md @@ -0,0 +1,33 @@ +# Coding Guidelines + +This is a live document that collects those coding guidelines we intend new code +to follow. + + +## ESLint + +- Run `yarn lint` to validate your code. + +- Don't commit code with ESLint errors (it'll make the build in CircleCI fail). + +- Code with ESLint warnings won't make the build in CircleCI fail, but reviewers + will likely request they're fixed before merging a contribution. + + +## Naming Conventions + +### Path and Filenames + +- Use lower case names and only alphanumeric characters (unless required + otherwise). + +- Avoid stuttering: + - bad: `my-connector/my-connector-driver.js` + - good: `my-connector/driver.js` + +- Use the extension `.jsx` for files that contain JSX and `.js` for those that + only contain JavaScript. + +### Variable and Function Names + +- Use JavaScript convention, i.e. camelCase. diff --git a/app/components/Settings/Preview/ChartEditor.react.js b/app/components/Settings/Preview/ChartEditor.react.js deleted file mode 100644 index f2bf21a18..000000000 --- a/app/components/Settings/Preview/ChartEditor.react.js +++ /dev/null @@ -1,205 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import createPlotlyComponent from 'react-plotlyjs'; -import Plotly from 'plotly.js/dist/plotly.min.js'; -import R from 'ramda'; - -import { DragDropContext } from 'react-dnd'; -import HTML5Backend from 'react-dnd-html5-backend'; -import AxisDropZone from './components/AxisDropZone.react.js'; -import Box from './components/Box.react.js'; - -import {PLOT_TYPES, controlPanelStyle, columnSelectLabelStyle, - dropdownContainerStyle, selectDropdownStyle} from './components/editorConstants'; - - -@DragDropContext(HTML5Backend) -export default class ChartEditor extends PureComponent { - - constructor(props) { - super(props); - - this.handleRemove = this.handleRemove.bind(this); - this.handleClick = this.handleClick.bind(this); - this.handleSelect = this.handleSelect.bind(this); - } - - static propTypes = { - boxes: PropTypes.arrayOf(PropTypes.object), - plotJSON: PropTypes.object, - selectedChartType: PropTypes.string, - selectedColumn: PropTypes.string, - xAxisColumnName: PropTypes.string, - yAxisColumnNames: PropTypes.string, - droppedBoxNames: PropTypes.array, - updateProps: PropTypes.func, - columnTraceTypes: PropTypes.array, - columnNames: PropTypes.array - } - - shouldComponentUpdate(nextProps) { - return (JSON.stringify(nextProps) !== this.props); - } - - isDropped(boxName) { - return this.props.droppedBoxNames.indexOf(boxName) > -1; - } - - render() { - const PlotlyComponent = createPlotlyComponent(Plotly); - - const { - boxes, - plotJSON, - selectedChartType, - selectedColumn, - xAxisColumnName, - yAxisColumnNames - } = this.props; - - const columnLabel = selectedColumn ? - 'Select a chart type for ' + selectedColumn : - 'Select a chart type'; - - return ( -
-
-
-
{columnLabel}
- {/* TODO is this a bug? - there's no value attribute on - {PLOT_TYPES.map((opt, i) => - - )} - -
-
- {boxes.map(({ name, type }, index) => - - )} -
-
-
-
- this.handleDrop(item, 'yaxis')} - removeDroppedItem={this.handleRemove} - handleClick={this.handleClick} - key={1} - dropType="yaxis" - droppedItems={yAxisColumnNames} - selectedColumn={selectedColumn} - /> -
-
- -
-
- this.handleDrop(item, 'xaxis')} - removeDroppedItem={this.handleRemove} - key={0} - dropType="xaxis" - droppedItems={[xAxisColumnName]} - selectedColumn={selectedColumn} - /> -
- ); - } - - handleRemove(colName, axisType) { - let {selectedColumn} = this.props; - const {updateProps, yAxisColumnNames} = this.props; - - if (colName === selectedColumn || yAxisColumnNames.length === 1) { - selectedColumn = ''; - } - - if (axisType === 'xaxis') { - updateProps({ - xAxisColumnName: '', - selectedColumn: selectedColumn - }); - } - else if (axisType === 'yaxis') { - updateProps({ - yAxisColumnNames: yAxisColumnNames.filter(val => val !== colName), - selectedColumn: selectedColumn - }); - } - } - - handleSelect(event) { - const traceType = event.target.value; - const columnTraceTypes = this.props.columnTraceTypes; - const {columnNames, selectedColumn, updateProps} = this.props; - - if (selectedColumn) { - columnTraceTypes[selectedColumn] = traceType; - } - else { - columnNames.forEach( - colName => (columnTraceTypes[colName] = traceType) - ); - } - - updateProps({ - columnTraceTypes: columnTraceTypes, - selectedChartType: traceType - }); - } - - handleClick(colName) { - const {columnTraceTypes, selectedColumn, updateProps} = this.props; - if (colName === selectedColumn) { - updateProps({selectedColumn: ''}); - } else { - updateProps({ - selectedColumn: colName, - selectedChartType: columnTraceTypes[colName] - }); - } - } - - handleDrop(item, axisType) { - const {name} = item; - const { - updateProps, - droppedBoxNames, - yAxisColumnNames - } = this.props; - - if (axisType === 'xaxis') { - updateProps({ - droppedBoxNames: R.concat(droppedBoxNames, [name]), - xAxisColumnName: name - }); - } - else if (axisType === 'yaxis') { - updateProps({ - droppedBoxNames: R.concat(droppedBoxNames, [name]), - yAxisColumnNames: R.concat( - yAxisColumnNames, - [name] - ), - selectedColumn: name - }); - } - } -} diff --git a/app/components/Settings/Preview/Preview.react.js b/app/components/Settings/Preview/Preview.react.js index 67d6de56e..0842a6d34 100644 --- a/app/components/Settings/Preview/Preview.react.js +++ b/app/components/Settings/Preview/Preview.react.js @@ -1,67 +1,72 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {connect} from 'react-redux'; -import R, {has, isEmpty, propOr} from 'ramda'; +import {has, isEmpty, propOr} from 'ramda'; + +import SplitPane from 'react-split-pane'; import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; +import TableTree from './TableTree.react.js'; import SQLTable from './sql-table.jsx'; import CodeEditorField from './CodeEditorField.react.js'; -import ChartEditor from './ChartEditor.react.js'; +import ChartEditor from './chart-editor.jsx'; import ApacheDrillPreview from './ApacheDrillPreview.js'; import S3Preview from './S3Preview.js'; import OptionsDropdown from '../OptionsDropdown/OptionsDropdown.react'; import {Link} from '../../Link.react'; import {DIALECTS, PREVIEW_QUERY, SQL_DIALECTS_USING_EDITOR} from '../../../constants/constants.js'; -import getPlotJsonFromState from './components/getPlotJsonFromState.js'; import {homeUrl, isOnPrem} from '../../../utils/utils'; class Preview extends Component { + static propTypes = { + connections: PropTypes.object, + connectionObject: PropTypes.object, + + selectedTab: PropTypes.string, + selectedTable: PropTypes.any, + selectedIndex: PropTypes.any, + setTable: PropTypes.func, + setIndex: PropTypes.func, + + preview: PropTypes.obj, + updatePreview: PropTypes.func, + + tablesRequest: PropTypes.object, + + schemaRequest: PropTypes.object, + getSqlSchema: PropTypes.func, + + runSqlQuery: PropTypes.func, + previewTableRequest: PropTypes.object, + queryRequest: PropTypes.object, + elasticsearchMappingsRequest: PropTypes.object, + + username: PropTypes.string + }; constructor(props) { super(props); + this.testClass = this.testClass.bind(this); this.updateCode = this.updateCode.bind(this); this.toggleEditor = this.toggleEditor.bind(this); this.runQuery = this.runQuery.bind(this); this.fetchDatacache = this.fetchDatacache.bind(this); - this.propsToState = this.propsToState.bind(this); - - this.state = { - plotlyLinks: [], - plotJSON: {}, - rows: [], - columnNames: [], - isLoading: false, - errorMsg: '', - successMsg: '', - timeQueryElapsedMsg: '', - chartEditorState: {} - }; - } - static propTypes = { - runSqlQuery: PropTypes.func, - updatePreview: PropTypes.func, - preview: PropTypes.obj, - connectionObject: PropTypes.object, - elasticsearchMappingsRequest: PropTypes.object, - schemaRequest: PropTypes.object, - selectedTable: PropTypes.any, - selectedIndex: PropTypes.any, - tablesRequest: PropTypes.object, - setTable: PropTypes.func, - setIndex: PropTypes.func, - username: PropTypes.string - }; + this.state = Preview.checkQueryResults(this.props); + this.state.plotlyJSON = {}; + this.state.plotlyLinks = []; + this.state.timeQueryElapsedMsg = ''; + } - propsToState(nextProps, props) { + static checkQueryResults(props) { const { preview, previewTableRequest, queryRequest - } = nextProps; - const {chartEditor, lastSuccessfulQuery} = preview; + } = props; + const {lastSuccessfulQuery} = preview; let rows = []; let columnNames = []; @@ -122,68 +127,47 @@ class Preview extends Component { successMsg = `${rows.length} rows retrieved`; } - let csvString = columnNames.join(', ') + '\n'; - rows.map(row => { - csvString = csvString + row.join(', ') + '\n'; - }); - - const chartEditorState = this.state.chartEditorState; - const defaultChartEditorState = { - xAxisColumnName: colNames => colNames[0], - yAxisColumnNames: colNames => [colNames[1]], - boxes: colNames => colNames.map( - colName => ({name: colName, type: 'column'})), - columnTraceTypes: colNames => ( - R.reduce((r, k) => R.set(R.lensProp(k), 'scatter', r), {}, colNames) - ), - droppedBoxNames: () => [], - selectedColumn: () => '', - selectedChartType: () => 'scatter' + return { + rows, + columnNames, + isLoading, + successMsg, + errorMsg }; + } - if (columnNames !== this.state.columnNames) { - // Reset props to defaults based off of columnNames - R.keys(defaultChartEditorState).forEach(k => { - chartEditorState[k] = defaultChartEditorState[k](columnNames); - }); - } else if (columnNames.length === 0) { - ['droppedBoxNames', 'selectedColumn', 'selectedChartType'].forEach( - // Function body wrapped in block because without a wrapping block, - // JavaScript treats the assignment as the implicit return value, - // which in turn breaks ESLint `no-return-assign` rule - k => { - chartEditorState[k] = defaultChartEditorState[k](); - } - ); - } else { - R.keys(defaultChartEditorState).forEach(k => { - if (!R.has(k, chartEditor || {})) { - chartEditorState[k] = defaultChartEditorState[k](columnNames); - } else { - if (!R.has('chartEditor', props.preview) || - nextProps.preview.chartEditor[k] !== props.preview.chartEditor[k]) { - chartEditorState[k] = nextProps.preview.chartEditor[k]; - } - } - }); + componentWillReceiveProps(nextProps) { + const { + preview, + previewTableRequest, + queryRequest + } = nextProps; + + let hasChanged = (preview.lastSuccessfulQuery !== this.props.preview.lastSuccessfulQuery); + + hasChanged = hasChanged || (previewTableRequest.status !== this.props.previewTableRequest.status); + hasChanged = hasChanged || (previewTableRequest.columnnames !== this.props.previewTableRequest.columnnames); + hasChanged = hasChanged || (previewTableRequest.rows !== this.props.previewTableRequest.rows); + + hasChanged = hasChanged || (queryRequest.status !== this.props.queryRequest.status); + hasChanged = hasChanged || (queryRequest.columnnames !== this.props.queryRequest.columnnames); + hasChanged = hasChanged || (queryRequest.rows !== this.props.queryRequest.rows); + + if (hasChanged) { + const nextState = Preview.checkQueryResults(nextProps); + this.setState(nextState); } + } - const plotJSON = getPlotJsonFromState({ - columnNames, - rows, - ...chartEditorState - }); + getCSVString() { + const {columnNames, rows} = this.state; - this.setState({ - chartEditorState, - columnNames, - csvString, - errorMsg, - isLoading, - plotJSON, - rows, - successMsg + let csvString = columnNames.join(', ') + '\n'; + rows.forEach(row => { + csvString += row.join(', ') + '\n'; }); + + return csvString; } fetchDatacache(payload, type) { @@ -266,21 +250,15 @@ class Preview extends Component { }); } - componentWillReceiveProps(nextProps) { - this.propsToState(nextProps, this.props); - } - - componentWillMount() { - this.propsToState(this.props, {}); - } - render() { - const { + connections, connectionObject, elasticsearchMappingsRequest, + getSqlSchema, preview, schemaRequest, + selectedTab, selectedIndex, selectedTable, setIndex, @@ -290,183 +268,263 @@ class Preview extends Component { } = this.props; const { - chartEditorState, columnNames, - csvString, errorMsg, + plotlyJSON, isLoading, - plotJSON, rows, successMsg, timeQueryElapsedMsg } = this.state; const dialect = connectionObject.dialect; + + const minSize = 10; + const defaultSize = 200; + const maxSize = -400; + const size = propOr(defaultSize, 'size')(preview); + const lastSize = propOr(defaultSize, 'lastSize')(preview); + const showEditor = propOr(true, 'showEditor')(preview); + const showChart = propOr(false, 'showChart')(preview); + const code = propOr(PREVIEW_QUERY(connectionObject, selectedTable), 'code')(preview); propOr('', 'error')(preview); // Surpressing ESLint cause restricting line length would harm JSX readability /* eslint-disable max-len */ return ( -
-
+ + this.props.updatePreview({ + size: nextSize + }) + } + + style={{position: 'relative !important'}} + > +
{SQL_DIALECTS_USING_EDITOR.includes(dialect) && -
- - - - {showEditor ? 'Hide Editor' : 'Show Editor'} - - - - - -
- } + }
+
+
+
+ {SQL_DIALECTS_USING_EDITOR.includes(dialect) && + + } - {errorMsg && -
-
{`ERROR: ${errorMsg}`}
-
- } + {!SQL_DIALECTS_USING_EDITOR.includes(dialect) && + + } +
- {dialect !== DIALECTS.S3 && dialect !== DIALECTS.APACHE_DRILL && -
- - - Chart - Table - Export - - - - { - updatePreview({'chartEditor': R.merge( - chartEditorState, - newProps - )}); + {errorMsg && +
+
{`ERROR: ${errorMsg}`}
+
+ } + + {dialect !== DIALECTS.S3 && dialect !== DIALECTS.APACHE_DRILL && +
+ { + if (index === lastIndex) { + return; + } + + const chartEditorSelected = (index === 1); + const schemasVisible = (size > minSize); + if (chartEditorSelected) { + if (schemasVisible) { + // If Chart Editor selected and Schemas Tree visible, + // then save size in lastSize before hiding + this.props.updatePreview({ + showChart: true, + showEditor: false, + lastSize: size, + size: minSize + }); + } else { + this.props.updatePreview({ + showChart: true, + showEditor: false + }); + } + } else { + if (!schemasVisible) { + // If Chart Editor not selected and Schemas Tree was hidden, + // then restore the last size + this.props.updatePreview({ + showChart: false, + showEditor: true, + size: lastSize + }); + } else { + this.props.updatePreview({ + showChart: false, + showEditor: true + }); + } + } }} - {...chartEditorState} - /> - - - - - - - -
-
- - {!isOnPrem() && - } - -
-
+ - {this.state.plotlyLinks.map(link => ( -
- {link.type === 'grid' && -
-
🎉 Link to your CSV on Chart Studio ⬇️
- {link.url} -
- } - {link.type === 'csv' && -
-
💾 Your CSV has been saved ⬇️
- {link.url} -
- } - {link.type === 'plot' && -
-
📈 Link to your chart on Chart Studio ⬇️
- {link.url} -
- } - {link.type === 'error' && -
-
{`[ERROR] ${link.message}`}
+ Table + Chart + Export + + + + + + + + this.setState({plotlyJSON: nextPlotlyJSON})} + + hidden={!showChart} + /> + + + +
+
+ + {!isOnPrem() && + } + +
+
+ {this.state.plotlyLinks.map(link => ( +
+ {link.type === 'grid' && +
+
🎉 Link to your CSV on Chart Studio ⬇️
+ {link.url} +
+ } + {link.type === 'csv' && +
+
💾 Your CSV has been saved ⬇️
+ {link.url} +
+ } + {link.type === 'plot' && +
+
📈 Link to your chart on Chart Studio ⬇️
+ {link.url} +
+ } + {link.type === 'error' && +
+
{`[ERROR] ${link.message}`}
+
+ }
- } + ))}
- ))} -
-
- - -
- } +
+ + +
+ } - {successMsg && -
-

{successMsg} {timeQueryElapsedMsg}

-
- } + {successMsg && +
+

{successMsg} {timeQueryElapsedMsg}

+
+ } - {S3Preview(this.props)} - {ApacheDrillPreview(this.props)} -
+ {S3Preview(this.props)} + {ApacheDrillPreview(this.props)} +
+
+ ); /* eslint-enable max-len */ } diff --git a/app/components/Settings/Preview/chart-editor.jsx b/app/components/Settings/Preview/chart-editor.jsx new file mode 100644 index 000000000..d71ee82f8 --- /dev/null +++ b/app/components/Settings/Preview/chart-editor.jsx @@ -0,0 +1,144 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import plotly from 'plotly.js/dist/plotly'; +import PlotlyEditor from 'react-chart-editor'; +import 'react-chart-editor/lib/react-chart-editor.css'; + +export default class ChartEditor extends React.Component { + static propTypes = { + columnNames: PropTypes.array, + rows: PropTypes.array, + + plotlyJSON: PropTypes.object, + onUpdate: PropTypes.func, + + hidden: PropTypes.bool + } + + /** + * Maximum number of rows to be displayed in a chart + * + * @type {number} + */ + static MAX_ROWS = 100000; + + /** + * ChartEditor displays a Plotly.js chart using the query results + * + * @param {object} props - Component properties + * + * @param {Array} props.columnNames - Array of column names + * @param {Array.} props.rows - Array of rows + * + * @param {object} props.plotlyJSON - Plotly graph div + * @param {Array} [props.plotlyJSON.data] - Plotly graph data + * @param {Array} [props.plotlyJSON.frames] - Plotly graph frames + * @param {object} [props.plotlyJSON.layout] - Plotly graph layout + * @param {function} props.onUpdate - Callback invoked to update plotlyJSON + * + * @param {hidden} props.hidden - If hidden, don't mount + */ + constructor(props) { + super(props); + + /** + * @member {object} state - Component state + * @property {object} state.dataSources - A data source per column (used by ) + * + * @property {Array.} state.dataSourceOptions - Data source options (used by ) + * @property {string} state.dataSourceOptions.label - Data source label (used by ) + * @property {string} state.dataSourceOptions.value - Data source index in dataSources (used by ) + */ + this.state = ChartEditor.computeDataSources(this.props); + } + + static computeDataSources(props) { + const {columnNames, rows} = props; + + const dataSources = {}; + columnNames.forEach(name => { + dataSources[name] = []; + }); + + // Cap number of rows displayed in a chart + const length = Math.min(rows.length, ChartEditor.MAX_ROWS); + + for (let i = 0; i < length; i++) { + const row = rows[i]; + + for (let j = 0; j < row.length; j++) { + const value = row[j]; + const columnName = columnNames[j]; + dataSources[columnName].push(value); + } + } + + const dataSourceOptions = columnNames.map(name => ({value: name, label: name})); + + return {dataSources, dataSourceOptions}; + } + + componentWillReceiveProps(nextProps) { + // Did props change? + const {columnNames, rows} = this.props; + const {columnNames: nextColumnNames, rows: nextRows} = nextProps; + + let isEqual = (columnNames.length === nextColumnNames.length); + for (let i = 0; isEqual && (i < columnNames.length); i++) { + isEqual = isEqual && (columnNames[i] === nextColumnNames[i]); + isEqual = isEqual && (rows[i] === nextRows[i]); + } + + // If it did, update the data sources + if (!isEqual) { + this.setState(ChartEditor.computeDataSources(nextProps)); + } + } + + render() { + const {plotlyJSON, onUpdate, hidden} = this.props; + const {dataSources, dataSourceOptions} = this.state; + + const config = { + editable: true + }; + const data = plotlyJSON && plotlyJSON.data; + const frames = plotlyJSON && plotlyJSON.frames; + const layout = plotlyJSON && plotlyJSON.layout; + + return ( +
+ {hidden || + onUpdate({ + data: nextData, + layout: nextLayout, + frames: nextFrames + }) + } + + useResizeHandler + debug + advancedTraceTypeSelector + />} +
+ ); + } +} diff --git a/app/components/Settings/Preview/components/AxisDropZone.react.js b/app/components/Settings/Preview/components/AxisDropZone.react.js deleted file mode 100644 index 4ef03c6dd..000000000 --- a/app/components/Settings/Preview/components/AxisDropZone.react.js +++ /dev/null @@ -1,81 +0,0 @@ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { DropTarget } from 'react-dnd'; -import Box from './Box.react.js'; -import {xAxisDropStyle, yAxisDropStyle} from './editorConstants'; - -const axisZoneTarget = { - drop(props, monitor) { - props.onDrop(monitor.getItem()); - }, - - canDrop(props, monitor) { - // Can't drop an item twice - const item = monitor.getItem(); - - return !props.droppedItems.includes(item.name); - } -}; - -@DropTarget(props => props.accepts, axisZoneTarget, (connect, monitor) => ({ - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - canDrop: monitor.canDrop() -})) -export default class AxisDropZone extends Component { - - constructor (props) { - super(props); - this.clickRemoveHandler = this.clickRemoveHandler.bind(this); - } - - static propTypes = { - connectDropTarget: PropTypes.func.isRequired, - isOver: PropTypes.bool.isRequired, - canDrop: PropTypes.bool.isRequired, - accepts: PropTypes.arrayOf(PropTypes.string).isRequired, - onDrop: PropTypes.func.isRequired, - removeDroppedItem: PropTypes.func, - dropType: PropTypes.string, - droppedItems: PropTypes.array, - handleClick: PropTypes.func, - selectedColumn: PropTypes.string - }; - - clickRemoveHandler (colName, dropType) { - this.props.removeDroppedItem(colName, dropType); - } - - render() { - const { isOver, canDrop, connectDropTarget, dropType, droppedItems } = this.props; - const isActive = isOver && canDrop; - const style = dropType === 'xaxis' ? xAxisDropStyle : yAxisDropStyle; - - let backgroundColor = '#f3f6fa'; - if (isActive) { - backgroundColor = '#00cc96'; - } else if (canDrop) { - backgroundColor = '#c8d4e3'; - } - - return connectDropTarget( -
- - {droppedItems.map((colName, i) => - - )} - -
, - ); - } -} diff --git a/app/components/Settings/Preview/components/Box.react.js b/app/components/Settings/Preview/components/Box.react.js deleted file mode 100644 index dd23d78a8..000000000 --- a/app/components/Settings/Preview/components/Box.react.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { DragSource } from 'react-dnd'; - -const style = { - border: '1px dashed #c8d4e3', - backgroundColor: 'white', - padding: '0.5rem 1rem', - marginRight: '0.5rem', - marginBottom: '0.5rem', - cursor: 'move', - float: 'left', - fontSize: '12px', - borderRadius: '10px' -}; - -const boxSource = { - beginDrag(props) { - return { - name: props.name - }; - } -}; - -@DragSource(props => props.type, boxSource, (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging() -})) -export default class Box extends Component { - constructor(props) { - super(props); - - this.handleRemove = this.handleRemove.bind(this); - this.handleClick = this.handleClick.bind(this); - } - - static propTypes = { - connectDragSource: PropTypes.func.isRequired, - isDragging: PropTypes.bool.isRequired, - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - isDropped: PropTypes.bool.isRequired, - dropType: PropTypes.string, - removeDroppedItem: PropTypes.func, - handleClick: PropTypes.func, - selectedColumn: PropTypes.string - }; - - handleRemove() { - const { name, dropType } = this.props; - - if (typeof this.props.removeDroppedItem === 'function') { - this.props.removeDroppedItem(name, dropType); - } - } - - handleClick() { - const { name } = this.props; - - if (typeof this.props.handleClick === 'function') { - this.props.handleClick(name); - } - } - - render() { - const { name, isDropped, isDragging, connectDragSource } = this.props; - const opacity = isDragging ? 0.4 : 1; - const borderColor = (name === this.props.selectedColumn) ? '#2a3f5f' : '#c8d4e3'; - - if (!name) { - return null; - } - - return connectDragSource( -
- {isDropped && this.props.dropType ? - : -
- {name} -
- } -
, - ); - } -} diff --git a/app/components/Settings/Preview/components/editorConstants.js b/app/components/Settings/Preview/components/editorConstants.js deleted file mode 100644 index 862b36675..000000000 --- a/app/components/Settings/Preview/components/editorConstants.js +++ /dev/null @@ -1,79 +0,0 @@ - -export const controlPanelStyle = { - padding: '10px 20px', - margin: '20px 0', - background: '#f3f6fa', - minHeight: '0.2rem', - height: '100%', - textAlign: 'left', - color: '#506784', - overflow: 'auto', - border: '1px solid #dfe8f3', - borderRadius: '6px' -}; - -export const columnSelectLabelStyle = { - fontWeight: '300', - fontSize: '14px', - display: 'inline', - marginRight: '10px', - width: 'auto' -}; - -export const dropdownContainerStyle = { - marginBottom: '10px', - height: '30px' -}; - -export const selectDropdownStyle = { - display: 'inline-block', - outline: 'none' -}; - -export const submitStyle = { - background: '#fff', - padding: '10px', - cursor: 'pointer', - border: '1px solid #c8d4e3', - fontSize: '12px', - width: '100%', - float: 'right' -}; - -export const PLOT_TYPES = [ - {label: 'Scatter', value: 'scatter'}, - {label: 'Bar', value: 'bar'}, - {label: 'Pie', value: 'pie'}, - {label: 'Line', value: 'line'}, - {label: 'Area', value: 'area'}, - {label: 'Histogram', value: 'histogram'}, - {label: 'Box Plot', value: 'box'}, - {label: 'Candlestick Plot', value: 'candlestick'}, - {label: 'OHLC Plot', value: 'ohlc'}, - {label: 'US [x=lat, y=lon]', value: 'scattergeo-usa'}, - {label: 'World [x=lat, y=lon]', value: 'scattergeo-world'}, - {label: 'US [x=state, y=value]', value: 'choropleth-usa'}, - {label: 'World [x=country, y=value]', value: 'choropleth-world'} -]; - -export const DEFAULT_DATA = [{x: [1, 2, 3, 4], y: [1, 4, 9, 16]}]; -export const DEFAULT_LAYOUT = {margin: {t: 10, l: 40}}; -export const DEFAULT_COLORS = ['#19d3f3', '#e763fa', '#ab63fa', '#636efa', '#00cc96', '#d36046']; - -export const xAxisDropStyle = { - height: '4rem', - color: '#2a3f5f', - padding: '0.5rem', - borderRadius: '6px', - border: '1px solid #dfe8f3' -}; - -export const yAxisDropStyle = { - height: '100%', - width: '8rem', - color: '#2a3f5f', - padding: '0.5rem', - backgroundColor: 'rgb(243, 246, 250)', - borderRadius: '6px', - border: '1px solid #dfe8f3' -}; diff --git a/app/components/Settings/Preview/components/getPlotJsonFromState.js b/app/components/Settings/Preview/components/getPlotJsonFromState.js deleted file mode 100644 index 105ec72b1..000000000 --- a/app/components/Settings/Preview/components/getPlotJsonFromState.js +++ /dev/null @@ -1,182 +0,0 @@ - -import {transpose, contains} from 'ramda'; -import {DEFAULT_DATA, DEFAULT_LAYOUT, DEFAULT_COLORS} from './editorConstants'; - -export default function getPlotJsonFromState(state) { - let data = DEFAULT_DATA; - const layout = DEFAULT_LAYOUT; - - // Get chart data - const allColumnNames = state.columnNames; - const rowData = state.rows; - - // Get chart configuration - const {xAxisColumnName, yAxisColumnNames, columnTraceTypes} = state; - - const colsWithTraceTypes = Object.keys(columnTraceTypes); - - if (allColumnNames && rowData) { - data = []; - const columnData = transpose(rowData); - let xColumnData; - let yColumnData; - let traceColor; - let traceType; - let dataObj; - - yAxisColumnNames.map((yColName, i) => { - - const numColors = DEFAULT_COLORS.length; - const colorWheelIndex = parseInt(numColors * (i / numColors), 10); - traceColor = DEFAULT_COLORS[colorWheelIndex]; - dataObj = {}; - xColumnData = columnData[allColumnNames.indexOf(xAxisColumnName)]; - yColumnData = columnData[allColumnNames.indexOf(yColName)]; - - // Get trace type - if (colsWithTraceTypes.includes(yColName)) { - traceType = columnTraceTypes[yColName]; - } - - const dataTemplate = { - name: yColName, - mode: traceType === 'line' || traceType === 'area' ? 'lines' : 'markers', - fill: traceType === 'area' ? 'tozeroy' : null - }; - - dataObj = { - x: xColumnData, - y: yColumnData, - type: traceType, - marker: { - color: traceColor, - size: 6, - line: { - width: 1 - } - } - }; - - if (traceType === 'scattergeo-usa' || traceType === 'scattergeo-world') { - dataObj = { - lat: xColumnData, - lon: yColumnData, - type: 'scattergeo', - marker: { - color: traceColor, - size: 4, - line: { - width: 1 - } - } - }; - } - else if (traceType === 'choropleth-usa') { - dataObj = { - locations: xColumnData, - z: yColumnData, - type: 'choropleth', - locationmode: 'USA-states', - colorscale: [[0, 'rgb(242,240,247)'], [0.2, 'rgb(218,218,235)'], - [0.4, 'rgb(188,189,220)'], [0.6, 'rgb(158,154,200)'], - [0.8, 'rgb(117,107,177)'], [1, 'rgb(84,39,143)']] - }; - } - else if (traceType === 'choropleth-world') { - dataObj = { - locations: xColumnData, - z: yColumnData, - type: 'choropleth', - locationmode: 'country names', - colorscale: [[0, 'rgb(5, 10, 172)'], [0.35, 'rgb(40, 60, 190)'], - [0.5, 'rgb(70, 100, 245)'], [0.6, 'rgb(90, 120, 245)'], - [0.7, 'rgb(106, 137, 247)'], [1, 'rgb(220, 220, 220)']] - }; - } - else if (traceType === 'pie') { - delete dataObj.x; - delete dataObj.y; - delete dataObj.marker.color; - let pieColors = []; - - // TODO A bug or works as intended? If as intended, transform to forEach - Array(100).fill().forEach(() => { - pieColors = pieColors.concat(DEFAULT_COLORS); - }); - dataObj = { - values: xColumnData, - labels: yColumnData, - marker: {colors: pieColors}, - hole: 0.2, - pull: 0.05 - }; - } - - if ((traceType === 'scatter' || traceType === 'line') && xColumnData.length > 20000) { - dataObj.type = 'scattergl'; - } - - data.push(Object.assign(dataObj, dataTemplate)); - }); - - layout.xaxis = {}; - layout.yaxis = {}; - layout.title = ' '; - layout.xaxis.title = xAxisColumnName; - layout.xaxis.zeroline = false; - layout.yaxis.zeroline = false; - layout.xaxis.showgrid = false; - layout.barmode = 'stack'; - layout.yaxis.title = ' '; - layout.yaxis.gridcolor = '#dfe8f3'; - layout.font = {color: '#506784', size: '12px'}; - if (allColumnNames.length === 2) { - layout.yaxis = {}; - layout.yaxis.title = ''; - } - - if (data.length) { - if (data[0].type === 'pie') { - layout.yaxis.showgrid = false; - layout.yaxis.showticklabels = false; - layout.xaxis.showticklabels = false; - layout.xaxis.title = ' '; - } - } - - if (contains(traceType, ['scattergeo-world', 'choropleth-world', 'scattergeo-usa', 'choropleth-usa'])) { - layout.geo = { - showland: true, - landcolor: 'rgb(212,212,212)', - subunitcolor: 'rgb(255,255,255)', - countrycolor: 'rgb(255,255,255)', - showlakes: true, - lakecolor: 'rgb(255,255,255)', - showsubunits: true, - showcountries: true - }; - } - - if (traceType === 'choropleth-usa') { - layout.geo.scope = 'usa'; - } - - if (traceType === 'scattergeo-usa') { - layout.geo.scope = 'north america'; - layout.geo.lonaxis = { - showgrid: true, - gridwidth: 0.5, - range: [ -140.0, -55.0 ], - dtick: 5 - }; - layout.geo.lataxis = { - showgrid: true, - gridwidth: 0.5, - range: [ 20.0, 60.0 ], - dtick: 5 - }; - } - } - - return {data: data, layout: layout}; -} diff --git a/app/components/Settings/Preview/styles/Preview.css b/app/components/Settings/Preview/styles/Preview.css deleted file mode 100644 index 5a5799fab..000000000 --- a/app/components/Settings/Preview/styles/Preview.css +++ /dev/null @@ -1,25 +0,0 @@ -table { - border-collapse: collapse; - border-spacing: 0; -} - -th, td { - border: 1px solid #828282; - padding: 0.3125rem 0.625rem; - color: #828282; - font-size: 0.875em; - text-align: left; -} - -.previewContainer { - margin: 0 25px; - overflow: auto; - border-radius: 3px; -} - -.tableHeader { - padding-top: 6px; - padding-bottom: 6px; - margin-left: auto; - margin-right: auto; -} diff --git a/app/components/Settings/Settings.react.js b/app/components/Settings/Settings.react.js index 74f407c5f..40a8cf208 100644 --- a/app/components/Settings/Settings.react.js +++ b/app/components/Settings/Settings.react.js @@ -6,14 +6,12 @@ import ReactToolTip from 'react-tooltip'; import classnames from 'classnames'; import * as Actions from '../../actions/sessions'; import fetch from 'isomorphic-fetch'; -import SplitPane from 'react-split-pane'; import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; import ConnectionTabs from './Tabs/Tabs.react'; import UserConnections from './UserConnections/UserConnections.react'; import DialectSelector from './DialectSelector/DialectSelector.react'; import ConnectButton from './ConnectButton/ConnectButton.react'; import Preview from './Preview/Preview.react'; -import TableTree from './Preview/TableTree.react.js'; import {Link} from '../Link.react'; import {DIALECTS, FAQ, PREVIEW_QUERY, SQL_DIALECTS_USING_EDITOR} from '../../constants/constants.js'; import {isElectron, isOnPrem} from '../../utils/utils'; @@ -324,47 +322,35 @@ class Settings extends Component { {this.props.connectRequest.status === 200 && selectedTable ? ( - -
- {SQL_DIALECTS_USING_EDITOR.includes(dialect) && - - } -
-
- -
-
+ ) : (

Please connect to a data store in the Connection tab first.

diff --git a/backend/persistent/style.css b/backend/persistent/style.css deleted file mode 100644 index 73c20a0ca..000000000 --- a/backend/persistent/style.css +++ /dev/null @@ -1,2 +0,0 @@ -html{font-size:62.5%}body{background-color:#eef0f3;position:relative;height:100vh;overflow-y:scroll;font-weight:200;font-size:1.5em;line-height:1.6;font-weight:400;font-family:Open Sans;color:#69738a}a{color:#447bdc;cursor:pointer}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:2rem;font-weight:300}h1{font-size:4rem;line-height:1.2}h1,h2{letter-spacing:-.1rem}h2{font-size:3.6rem;line-height:1.25}h3{font-size:3rem;line-height:1.3;letter-spacing:-.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media (min-width:550px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem;text-align:center}h5{font-size:2.4rem}h6{font-size:1.5rem}}p{margin-top:0}::-webkit-input-placeholder{color:#bec8d9;letter-spacing:1px;font-size:15px}::-webkit-scrollbar{width:12px;height:12px;border-left:0;background:#f8f8f9;border:1px solid #bec8d9}::-webkit-scrollbar-thumb{background-color:#818b99;border:2px solid transparent;background-clip:content-box;border-radius:5px;width:12px}.Settings__containerWrapper___2ti87{width:100%}.Settings__container___x_FDG{width:100%;max-width:800px;margin-left:auto;margin-right:auto}.Settings__tabsContainer___9Fshy{text-align:center}.Settings__openTab___2Y2mY{background:#fff;border-width:0 1px 1px;border-color:#e7e8e9;border-style:solid}.Settings__configurationOptions___3VFnz{width:100%;text-align:center}.Settings__info___2g2yE{padding:25px;font-size:14px}.Settings__userConnections___1al1-{margin-left:auto;margin-right:auto}.Settings__dialectSelector___3zNi4{display:inline-block}.Settings__connectButton___1pD8W{padding-top:30px;text-align:center}.Settings__step1Container___2gWyS{width:800px}.Settings__step2Container___2JQLp{width:800px;padding-top:30px}.Settings__step3Container___1qpbY,.Settings__step4Container___3kO33{width:800px;padding-top:20px}.Settings__previewController___21j4h{max-height:500px;max-width:100%;overflow-y:scroll;border:1px solid #bec8d9;border-radius:3px;padding:10px;margin-left:25px;margin-right:25px}.Settings__logContainer___2HwS0{max-height:300px;margin-top:40px}.Settings__log___2IPRB{margin-bottom:100px}.Settings__unfoldIcon___3ZaIK{height:18px;width:18px;padding-left:20px}.Tabs__tabManagerWrapper___1B09Y{text-align:left;width:100%;white-space:nowrap;overflow-x:auto;overflow-y:hidden}.Tabs__tabManagerContainer___2_mhi{margin-left:auto;margin-right:auto;text-align:left}.Tabs__tabAddWrapper___34VXE{border:1px solid #e7e8e9;height:36px;width:36px}.Tabs__tabAddWrapper___34VXE,.Tabs__tabWrapper___3TK9B{background-color:#fff;display:inline-block;vertical-align:middle}.Tabs__tabWrapper___3TK9B{cursor:pointer;border-left:1px solid #e7e8e9;border-bottom:1px solid #e7e8e9;border-top:1px solid #e7e8e9;color:#687ba6;padding:5px 10px;max-width:300px;white-space:nowrap;overflow-x:hidden;text-overflow:ellipsis}.Tabs__tabLogo___3qrJl{height:20px;display:inline;vertical-align:middle;padding-right:5px}.Tabs__tabIdentifier___2l5TL{margin:0;display:inline;vertical-align:middle}.Tabs__tabDelete___2a3gQ{width:12px;padding-right:5px;vertical-align:middle}.Tabs__tabAdd___3ZRRq{width:20px;padding:8px}.Tabs__tabAdd___3ZRRq:hover,.Tabs__tabDelete___2a3gQ:hover,.Tabs__tabWrapper___3TK9B:hover,tab:focus,tabWrapper:focus{cursor:pointer}.Tabs__tabWrapperSelected___1i599{border-bottom:1px solid #fff}.Tabs__tabAddText___3fB1O{display:inline-block;padding:10px 20px;color:#8bb5fe}.Tabs__Flipped___2yAMy,.Tabs__Flipped___2yAMy .Tabs__tabAddText___3fB1O,.Tabs__Flipped___2yAMy .Tabs__tabWrapper___3TK9B{transform:rotateX(180deg);-ms-transform:rotateX(180deg);-webkit-transform:rotateX(180deg)}.UserConnections__inputContainer___GRMNg{padding-top:10px;width:100%;margin-left:auto;margin-right:auto;text-align:center}input{display:inline-block!important;padding:5px;border:none;border-bottom-width:1px;border-bottom-color:#bec8d9;border-bottom-style:solid;color:#6d6d6d;width:400px;font-size:18px;font-weight:100;margin:10px 40px 0;text-align:left;height:39px}input:hover{border-bottom-color:#8bb5fe}.UserConnections__inputNamesContainer___6HFPe{width:150px;vertical-align:top;position:absolute;left:10%}.UserConnections__inputFieldsContainer___1XH9Z{display:inline-block}.UserConnections__inputName___2wc33{text-transform:capitalize;padding:5px;display:inline-block;text-align:right;margin:10px 0 0;height:40px;width:100%}.UserConnections__documentationLink___260d4{display:inline-block!important;padding:5px;text-transform:capitalize;width:400px;font-size:18px;font-weight:100;margin:10px 40px 0;text-align:left;height:30px}.UserConnections__checkbox___8facc{margin-top:1px;display:inline;vertical-align:middle;margin:10px 20px 10px 10px;width:20px}.UserConnections__optionsContainer___2mgcC{text-align:left;padding-left:40px;padding-top:25px}.UserConnections__options___2HbKG{color:#6d6d6d;font-weight:100;font-size:18px}.UserConnections__options___2HbKG:hover{color:#8bb5fe}.DialectSelector__logoContainer___D1HnC{width:600px;text-align:center}.DialectSelector__logo___1Zq8g{display:inline-block;width:90px;height:50px;padding:5px;margin:5px;border:2px solid #bec8d9;border-radius:5px;vertical-align:middle}.DialectSelector__logoImage___2uQrV{width:100%}.DialectSelector__logo___1Zq8g:hover,.DialectSelector__logoSelected___3sXag{border-color:#8bb5fe;cursor:pointer;border-width:4px;margin:3px}.DialectSelector__dialectInput___21QrR,.DialectSelector__dialectLabel___GSwUX{display:inline-block}.ConnectButton__buttonPrimary___2cAF8{background-color:#fff;border:1px solid #8d98af;border-radius:4px;color:#6d6d6d;cursor:pointer;font-size:16px;font-weight:100;letter-spacing:1px;margin-left:auto;margin-right:auto;padding:10px;padding-left:20px;padding-right:20px;text-align:center;text-transform:capitalize;width:100px}.ConnectButton__buttonPrimary___2cAF8:hover{border-color:#8bb5fe}.ConnectButton__errorMessage___Doieo{color:red;height:30px;padding-top:10px}.ConnectButton__footer___2Loee{padding-top:10px}.TableDropdown__dropdown___tM0pZ{text-transform:capitalize;cursor:pointer;width:300px;padding-bottom:10px;padding-left:25px}.Configuration__header___PO00n{vertical-align:top}.Configuration__logoAndTitle___SNbKG{display:inline-block;width:50%}.Configuration__plotlyLogo___3Gq76{width:100px;display:inline-block}.Configuration__applicationTitle___3LWQx{margin-top:25px;display:inline-block;vertical-align:top;color:#096cb3}.Configuration__supportLinks___19W_4{display:inline-block;direction:rtl;text-align:start;vertical-align:top;width:50%;margin-top:30px}.Configuration__externalLinkContainer___1DeFY{display:inline-block}.Configuration__externalLink___3FrE1{cursor:pointer;margin:10px}.Configuration__settings___2RaQK{background-color:#fff;padding-top:20px;margin:0 20px}.Configuration__onPremDomainInput___3orwC,.Configuration__onPremForm___GL4UP{text-align:center}.Configuration__addOnPremLink___2NC3B{cursor:pointer;text-align:center} -/*# sourceMappingURL=style.css.map*/ \ No newline at end of file diff --git a/package.json b/package.json index 8190081e9..9ee0329f1 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "test": "yarn run lint && yarn run test-unit-all && yarn run test-e2e && yarn run test-jest", "test-e2e": "cross-env NODE_ENV=test mocha --bail --full-trace --compilers js:babel-register --require babel-polyfill ./test/integration_test.js", "test-e2e-local": "source test/set_creds.sh && yarn run test-e2e", + "test-jest": "jest test/app", "test-unit": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --bail --timeout 90000 --compilers js:babel-register ", "test-unit-all": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --bail --full-trace --timeout 90000 --compilers js:babel-register --recursive test/**/*.spec.js", "test-unit-all-watch": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --bail --full-trace --timeout 90000 --compilers js:babel-register --recursive test/**/*.spec.js --watch", @@ -41,7 +42,6 @@ "test-unit-scheduler": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/QueryScheduler.spec.js", "test-unit-routes": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/routes.spec.js", "test-unit-mock": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.mock.spec.js", - "test-jest": "./node_modules/.bin/jest ./test/app", "pack": "cross-env NODE_ENV=production electron-builder --publish=never", "package": "cross-env NODE_ENV=production node -r babel-register package.js", "package-all": "yarn run package -- --all", @@ -56,6 +56,11 @@ "dist-m": "build -m", "dist-l": "build -l" }, + "jest": { + "moduleNameMapper": { + "\\.css$": "identity-obj-proxy" + } + }, "build": { "appId": "org.plotly.connector", "productName": "Falcon SQL Client", @@ -174,6 +179,7 @@ "fetch-cookie": "^0.7.0", "form-data": "^2.3.1", "fs-extra": "^4.0.2", + "identity-obj-proxy": "^3.0.0", "immutable": "^3.8.2", "jest": "^22.3.0", "json-loader": "^0.5.4", @@ -182,18 +188,19 @@ "nock": "^9.1.5", "node-fetch": "^1.7.2", "node-impala": "^2.0.4", - "plotly.js": "^1.31.2", + "plotly.js": "^1.35.2", "prop-types": "^15.6.0", "query-string": "^5.0.1", "ramda": "^0.21.0", "react": "^16.2.0", + "react-chart-editor": "^0.13.0", "react-codemirror": "^1.0.0", "react-cookies": "^0.1.0", - "react-dnd": "^2.5.4", - "react-dnd-html5-backend": "^2.5.4", + "react-data-grid": "^3.0.11", + "react-data-grid-addons": "^3.0.11", "react-dom": "^16.2.0", "react-immutable-proptypes": "^2.1.0", - "react-plotlyjs": "^0.4.4", + "react-plotly.js": "^2.1.0", "react-redux": "^5.0.7", "react-router": "^3.2.1", "react-router-dom": "^4.2.2", @@ -228,8 +235,6 @@ "papaparse": "^4.3.7", "pg": "^4.5.5", "pg-hstore": "^2.3.2", - "react-data-grid": "^3.0.11", - "react-data-grid-addons": "^3.0.11", "restify": "^4.3.2", "sequelize": "^3.30.4", "source-map-support": "^0.5.0", diff --git a/static/css-vendor/chart-editor.css b/static/css-vendor/chart-editor.css deleted file mode 100644 index c2bddd048..000000000 --- a/static/css-vendor/chart-editor.css +++ /dev/null @@ -1,11 +0,0 @@ -.control-panel { - padding: 20px; - margin: 20px 0; - background: #f3f6fa; - border: 1px solid #ebf0f8; -} - -.control-panel div.label { - font-size: 13px; - margin-left: 10px; -} diff --git a/static/css/Preview.css b/static/css/Preview.css index d320903cc..c4ad60917 100644 --- a/static/css/Preview.css +++ b/static/css/Preview.css @@ -5,62 +5,46 @@ table { th, td { border: 1px solid #828282; - padding: 0.3125rem 0.625rem; color: #828282; font-size: 0.875em; + padding: 0.3125rem 0.625rem; text-align: left; } -.Pane.vertical { - overflow-x: auto; -} - .previewContainer { + border-radius: 3px; margin: 0 25px 25px 25px; overflow: auto; - border-radius: 3px; -} - -.tableHeader { - padding-top: 6px; - padding-bottom: 6px; - margin-left: auto; - margin-right: auto; } .runButton { + bottom: 18px; position: absolute; right: 18px; - bottom: 18px; width: 70px; z-index: 10; } .errorStatus pre { - padding: 20px; - margin: 0 0 20px 0; background: none; border: none; - color: rgb(211, 96, 70); border-bottom: 1px solid #ebf0f8; - font-size: 12px; + color: rgb(211, 96, 70); font-family: 'Ubuntu Mono'; + font-size: 12px; + margin: 0 0 20px 0; + max-height: 150px; overflow-x: auto; overflow-y: auto; - max-height: 150px; + padding: 20px; } .successMsg p { - padding: 20px; - margin: 0; background: none; border: none; color: #00cc96; + font-family: 'Ubuntu Mono'; font-size: 14px; - font-family: 'Ubuntu Mono'; + margin: 0; + padding: 20px; } - - - - - diff --git a/static/css/Settings.css b/static/css/Settings.css index 9866c455e..664f60ea9 100644 --- a/static/css/Settings.css +++ b/static/css/Settings.css @@ -1,111 +1,35 @@ -.containerWrapper { - width: 100%; -} - -.container { - width: 100%; - max-width: 800px; - margin-left: auto; - margin-right: auto; -} - -.tabsContainer { - text-align: center; -} - .openTab { - border-radius: 0 0 6px 6px; + background: rgb(251, 252, 253); + border-bottom: 1px solid #c8dfe3; border-left: 1px solid #c8dfe3; - border-right: 1px solid #c8dfe3; - border-bottom: 1px solid #c8dfe3; - background: rgb(251, 252, 253) + border-radius: 0 0 6px 6px; + border-right: 1px solid #c8dfe3; } .configurationContainer { - width: 100%; - text-align: center; font-family: 'Ubuntu'; -} - -.stepTitleContainer { - padding: 20px 30px 0px 30px; -} - -.stepTitle { - border-bottom: 1px solid #E7E8E9; - cursor: pointer; + text-align: center; + width: 100%; } .disabledSection { - pointer-events: none; opacity: 0.4; + pointer-events: none; } .info { - padding: 25px; font-size: 14px; -} - -.userConnections { - margin-left: auto; - margin-right: auto; + padding: 25px; } .dialectSelector { - display: inline-block; -} - -.previewController { - max-height: 500px; - max-width: 100%; - overflow-y: scroll; - border: 1px solid #BEC8D9; - border-radius: 3px; - padding: 10px; - margin-left: 25px; - margin-right: 25px; -} - -.logContainer { - max-height: 300px; - margin-top: 40px; -} - -.log { - margin-bottom: 100px; -} - -.unfoldIcon { - width: 18px; - padding-left: 10px; - position: relative; - top: 3px; -} - -.workspaceLink { - border-radius: 4px; - border: 1px solid #8d98af; - font-size: 16px; - font-weight: 100; - letter-spacing: 1px; - margin: 60px auto; - max-width: 25%; - padding: 10px; - text-align: center; + display: inline-block; } .editButtonContainer { margin: 20px 0; } -.collapsed .unfoldIcon{ - position: relative; - top: 6px; - transform:rotate(-90deg); - -ms-transform:rotate(-90deg); /* IE 9 */ - -webkit-transform:rotate(-90deg); /* Safari and Chrome */ -} - .url { text-decoration: underline; } diff --git a/static/css/chart-editor.css b/static/css/chart-editor.css deleted file mode 100644 index c2bddd048..000000000 --- a/static/css/chart-editor.css +++ /dev/null @@ -1,11 +0,0 @@ -.control-panel { - padding: 20px; - margin: 20px 0; - background: #f3f6fa; - border: 1px solid #ebf0f8; -} - -.control-panel div.label { - font-size: 13px; - margin-left: 10px; -} diff --git a/static/styles/Resizer.css b/static/styles/Resizer.css deleted file mode 100644 index 11555c693..000000000 --- a/static/styles/Resizer.css +++ /dev/null @@ -1,49 +0,0 @@ -.Resizer { - background: #000; - opacity: .2; - z-index: 1; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - -moz-background-clip: padding; - -webkit-background-clip: padding; - background-clip: padding-box; -} - - .Resizer:hover { - -webkit-transition: all 2s ease; - transition: all 2s ease; -} - - .Resizer.horizontal { - height: 11px; - margin: -5px 0; - border-top: 5px solid rgba(255, 255, 255, 0); - border-bottom: 5px solid rgba(255, 255, 255, 0); - cursor: row-resize; - width: 100%; -} - -.Resizer.horizontal:hover { - border-top: 5px solid rgba(0, 0, 0, 0.5); - border-bottom: 5px solid rgba(0, 0, 0, 0.5); -} - -.Resizer.vertical { - width: 11px; - margin: 0 -5px; - border-left: 5px solid rgba(255, 255, 255, 0); - border-right: 5px solid rgba(255, 255, 255, 0); - cursor: col-resize; -} - -.Resizer.vertical:hover { - border-left: 5px solid rgba(0, 0, 0, 0.5); - border-right: 5px solid rgba(0, 0, 0, 0.5); -} -.Resizer.disabled { - cursor: not-allowed; -} -.Resizer.disabled:hover { - border-color: transparent; -} \ No newline at end of file diff --git a/static/styles/Treeview.css b/static/styles/Treeview.css deleted file mode 100644 index f809696ec..000000000 --- a/static/styles/Treeview.css +++ /dev/null @@ -1,42 +0,0 @@ -/* the tree node's style */ -.tree-view { - overflow-y: hidden; - overflow-x: hidden; - font-size: 13px; -} - -.tree-view_item { - /* immediate child of .tree-view, for styling convenience */ -} - -/* style for the children nodes container */ -.tree-view_children { - margin-left: 16px; -} - -.tree-view_children-collapsed { - height: 0px; -} - -.tree-view_arrow { - cursor: pointer; - margin-right: 6px; - display: inline-block; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - display: inline; -} - -.tree-view_arrow:after { - content: '▾'; -} - -/* rotate the triangle to close it */ -.tree-view_arrow-collapsed { - -webkit-transform: rotate(-90deg); - -moz-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - transform: rotate(-90deg); -} diff --git a/static/styles/chart-editor.css b/static/styles/chart-editor.css deleted file mode 100644 index c2bddd048..000000000 --- a/static/styles/chart-editor.css +++ /dev/null @@ -1,11 +0,0 @@ -.control-panel { - padding: 20px; - margin: 20px 0; - background: #f3f6fa; - border: 1px solid #ebf0f8; -} - -.control-panel div.label { - font-size: 13px; - margin-left: 10px; -} diff --git a/static/styles/fixed-data-table-custom.css b/static/styles/fixed-data-table-custom.css deleted file mode 100644 index 7c92bf012..000000000 --- a/static/styles/fixed-data-table-custom.css +++ /dev/null @@ -1,9 +0,0 @@ -.public_fixedDataTable_main, .fixedDataTableLayout_main public_fixedDataTable_main { - font-size: 12px !important; - border-color: #c8d4e3; -} - -.public_fixedDataTable_header, .public_fixedDataTable_header .public_fixedDataTableCell_main { - background-image: none; - background: #f3f6fa; -} diff --git a/static/styles/fixed-data-table.css b/static/styles/fixed-data-table.css deleted file mode 100644 index 75cf1446a..000000000 --- a/static/styles/fixed-data-table.css +++ /dev/null @@ -1,503 +0,0 @@ -/** - * FixedDataTable v0.6.4 - * - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTableCellGroupLayout - */ - -.fixedDataTableCellGroupLayout_cellGroup { - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - left: 0; - overflow: hidden; - position: absolute; - top: 0; - white-space: nowrap; -} - -.fixedDataTableCellGroupLayout_cellGroup > .public_fixedDataTableCell_main { - display: inline-block; - vertical-align: top; - white-space: normal; -} - -.fixedDataTableCellGroupLayout_cellGroupWrapper { - position: absolute; - top: 0; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTableCellLayout - */ - -.fixedDataTableCellLayout_main { - border-right-style: solid; - border-right-width: 1px; - border-width: 0 1px 0 0; - box-sizing: border-box; - display: block; - overflow: hidden; - position: absolute; - white-space: normal; -} - -.fixedDataTableCellLayout_lastChild { - border-width: 0 1px 1px 0; -} - -.fixedDataTableCellLayout_alignRight { - text-align: right; -} - -.fixedDataTableCellLayout_alignCenter { - text-align: center; -} - -.fixedDataTableCellLayout_wrap1 { - display: table; -} - -.fixedDataTableCellLayout_wrap2 { - display: table-row; -} - -.fixedDataTableCellLayout_wrap3 { - display: table-cell; - vertical-align: middle; -} - -.fixedDataTableCellLayout_columnResizerContainer { - position: absolute; - right: 0px; - width: 6px; - z-index: 1; -} - -.fixedDataTableCellLayout_columnResizerContainer:hover { - cursor: ew-resize; -} - -.fixedDataTableCellLayout_columnResizerContainer:hover .fixedDataTableCellLayout_columnResizerKnob { - visibility: visible; -} - -.fixedDataTableCellLayout_columnResizerKnob { - position: absolute; - right: 0px; - visibility: hidden; - width: 4px; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTableColumnResizerLineLayout - */ - -.fixedDataTableColumnResizerLineLayout_mouseArea { - cursor: ew-resize; - position: absolute; - right: -5px; - width: 12px; -} - -.fixedDataTableColumnResizerLineLayout_main { - border-right-style: solid; - border-right-width: 1px; - box-sizing: border-box; - position: absolute; - z-index: 10; -} - -body[dir="rtl"] .fixedDataTableColumnResizerLineLayout_main { - /* the resizer line is in the wrong position in RTL with no easy fix. - * Disabling is more useful than displaying it. - * #167 (github) should look into this and come up with a permanent fix. - */ - display: none !important; -} - -.fixedDataTableColumnResizerLineLayout_hiddenElem { - display: none !important; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTableLayout - */ - -.fixedDataTableLayout_main { - border-style: solid; - border-width: 1px; - box-sizing: border-box; - overflow: hidden; - position: relative; -} - -.fixedDataTableLayout_header, -.fixedDataTableLayout_hasBottomBorder { - border-bottom-style: solid; - border-bottom-width: 1px; -} - -.fixedDataTableLayout_footer .public_fixedDataTableCell_main { - border-top-style: solid; - border-top-width: 1px; -} - -.fixedDataTableLayout_topShadow, -.fixedDataTableLayout_bottomShadow { - height: 4px; - left: 0; - position: absolute; - right: 0; - z-index: 1; -} - -.fixedDataTableLayout_bottomShadow { - margin-top: -4px; -} - -.fixedDataTableLayout_rowsContainer { - overflow: hidden; - position: relative; -} - -.fixedDataTableLayout_horizontalScrollbar { - bottom: 0; - position: absolute; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTableRowLayout - */ - -.fixedDataTableRowLayout_main { - box-sizing: border-box; - overflow: hidden; - position: absolute; - top: 0; -} - -.fixedDataTableRowLayout_body { - left: 0; - position: absolute; - top: 0; -} - -.fixedDataTableRowLayout_fixedColumnsDivider { - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - border-left-style: solid; - border-left-width: 1px; - left: 0; - position: absolute; - top: 0; - width: 0; -} - -.fixedDataTableRowLayout_columnsShadow { - width: 4px; -} - -.fixedDataTableRowLayout_rowWrapper { - position: absolute; - top: 0; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ScrollbarLayout - */ - -.ScrollbarLayout_main { - box-sizing: border-box; - outline: none; - overflow: hidden; - position: absolute; - transition-duration: 250ms; - transition-timing-function: ease; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.ScrollbarLayout_mainVertical { - bottom: 0; - right: 0; - top: 0; - transition-property: background-color width; - width: 15px; -} - -.ScrollbarLayout_mainVertical.public_Scrollbar_mainActive, -.ScrollbarLayout_mainVertical:hover { - width: 17px; -} - -.ScrollbarLayout_mainHorizontal { - bottom: 0; - height: 15px; - left: 0; - transition-property: background-color height; -} - -/* Touching the scroll-track directly makes the scroll-track bolder */ -.ScrollbarLayout_mainHorizontal.public_Scrollbar_mainActive, -.ScrollbarLayout_mainHorizontal:hover { - height: 17px; -} - -.ScrollbarLayout_face { - left: 0; - overflow: hidden; - position: absolute; - z-index: 1; -} - -/** - * This selector renders the "nub" of the scrollface. The nub must - * be rendered as pseudo-element so that it won't receive any UI events then - * we can get the correct `event.offsetX` and `event.offsetY` from the - * scrollface element while dragging it. - */ -.ScrollbarLayout_face:after { - border-radius: 6px; - content: ''; - display: block; - position: absolute; - transition: background-color 250ms ease; -} - -.ScrollbarLayout_faceHorizontal { - bottom: 0; - left: 0; - top: 0; -} - -.ScrollbarLayout_faceHorizontal:after { - bottom: 4px; - left: 0; - top: 4px; - width: 100%; -} - -.ScrollbarLayout_faceVertical { - left: 0; - right: 0; - top: 0; -} - -.ScrollbarLayout_faceVertical:after { - height: 100%; - left: 4px; - right: 4px; - top: 0; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTable - * - */ - -/** - * Table. - */ -.public_fixedDataTable_main { - border-color: #d3d3d3; -} - -.public_fixedDataTable_header, -.public_fixedDataTable_hasBottomBorder { - border-color: #d3d3d3; -} - -.public_fixedDataTable_header .public_fixedDataTableCell_main { - font-weight: bold; -} - -.public_fixedDataTable_header, -.public_fixedDataTable_header .public_fixedDataTableCell_main { - background-color: #f6f7f8; - background-image: linear-gradient(#fff, #efefef); -} - -.public_fixedDataTable_footer .public_fixedDataTableCell_main { - background-color: #f6f7f8; - border-color: #d3d3d3; -} - -.public_fixedDataTable_topShadow { - background: 0 0 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAECAYAAABP2FU6AAAAF0lEQVR4AWPUkNeSBhHCjJoK2twgFisAFagCCp3pJlAAAAAASUVORK5CYII=) repeat-x; -} - -.public_fixedDataTable_bottomShadow { - background: 0 0 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAECAYAAABP2FU6AAAAHElEQVQI12MwNjZmZdAT1+Nm0JDWEGZQk1GTBgAWkwIeAEp52AAAAABJRU5ErkJggg==) repeat-x; -} - -.public_fixedDataTable_horizontalScrollbar .public_Scrollbar_mainHorizontal { - background-color: #fff; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTableCell - */ - -/** - * Table cell. - */ -.public_fixedDataTableCell_main { - background-color: #fff; - border-color: #d3d3d3; -} - -.public_fixedDataTableCell_highlighted { - background-color: #f4f4f4; -} - -.public_fixedDataTableCell_cellContent { - padding: 8px; -} - -.public_fixedDataTableCell_columnResizerKnob { - background-color: #0284ff; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTableColumnResizerLine - * - */ - -/** - * Column resizer line. - */ -.public_fixedDataTableColumnResizerLine_main { - border-color: #0284ff; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule fixedDataTableRow - */ - -/** - * Table row. - */ -.public_fixedDataTableRow_main { - background-color: #fff; -} - -.public_fixedDataTableRow_highlighted, -.public_fixedDataTableRow_highlighted .public_fixedDataTableCell_main { - background-color: #f6f7f8; -} - -.public_fixedDataTableRow_fixedColumnsDivider { - border-color: #d3d3d3; -} - -.public_fixedDataTableRow_columnsShadow { - background: 0 0 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABCAYAAAD5PA/NAAAAFklEQVQIHWPSkNeSBmJhTQVtbiDNCgASagIIuJX8OgAAAABJRU5ErkJggg==) repeat-y; -} -/** - * Copyright (c) 2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule Scrollbar - * - */ - -/** - * Scrollbars. - */ - -/* Touching the scroll-track directly makes the scroll-track bolder */ -.public_Scrollbar_main.public_Scrollbar_mainActive, -.public_Scrollbar_main:hover { - background-color: rgba(255, 255, 255, 0.8); -} - -.public_Scrollbar_mainOpaque, -.public_Scrollbar_mainOpaque.public_Scrollbar_mainActive, -.public_Scrollbar_mainOpaque:hover { - background-color: #fff; -} - -.public_Scrollbar_face:after { - background-color: #c2c2c2; -} - -.public_Scrollbar_main:hover .public_Scrollbar_face:after, -.public_Scrollbar_mainActive .public_Scrollbar_face:after, -.public_Scrollbar_faceActive:after { - background-color: #7d7d7d; -} \ No newline at end of file diff --git a/static/styles/show-hint.css b/static/styles/show-hint.css deleted file mode 100644 index bfad43a3c..000000000 --- a/static/styles/show-hint.css +++ /dev/null @@ -1,35 +0,0 @@ -.CodeMirror-hints { - position: absolute; - z-index: 10; - overflow: hidden; - list-style: none; - - margin: 0; - padding: 2px; - - -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); - box-shadow: 2px 3px 5px rgba(0,0,0,.2); - border-radius: 3px; - border: 1px solid silver; - - background: white; - font-size: 90%; - font-family: monospace; - - max-height: 20em; - overflow-y: auto; -} - -.CodeMirror-hint { - margin: 0; - padding: 0 4px; - border-radius: 2px; - white-space: pre; - color: black; - cursor: pointer; -} - -li.CodeMirror-hint-active { - background: #08f; - color: white; -} \ No newline at end of file diff --git a/test/app/components/Settings/Preview/chart-editor.test.jsx b/test/app/components/Settings/Preview/chart-editor.test.jsx new file mode 100644 index 000000000..70d99052a --- /dev/null +++ b/test/app/components/Settings/Preview/chart-editor.test.jsx @@ -0,0 +1,86 @@ +import React from 'react'; +import {configure, mount} from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +// workaround `TypeError: window.URL.createObjectURL is not a function` +window.URL.createObjectURL = function() {}; +// workaround `Error: Not implemented: HTMLCanvasElement.prototype.getContext` +HTMLCanvasElement.prototype.getContext = function () {return null;}; + +jest.unmock('../../../../../app/components/Settings/Preview/chart-editor.jsx'); +const ChartEditor = require('../../../../../app/components/Settings/Preview/chart-editor.jsx'); + +describe('ChartEditor', () => { + let chartEditor, columnNames, rows, plotlyJSON, onUpdate, hidden; + + beforeAll(() => { + configure({adapter: new Adapter()}); + + columnNames = ['x', 'y']; + + rows = []; + for (let i = 0; i < 1 + ChartEditor.MAX_ROWS; i++) { + rows.push([i, 10 * i]); + } + + plotlyJSON = {}; + + onUpdate = (nextPlotlyJSON) => {plotlyJSON = nextPlotlyJSON;}; + + hidden = true; + + chartEditor = mount( +