diff --git a/client/app/assets/less/ant.less b/client/app/assets/less/ant.less index c42a83e2a7..3d650e2159 100644 --- a/client/app/assets/less/ant.less +++ b/client/app/assets/less/ant.less @@ -247,3 +247,34 @@ .ant-popover { z-index: 1000; // make sure it doesn't cover drawer } + +// schema browser table expand icon override +.schema-container { + .schema-browser-title-item .copy-arrow { + display: none; + position: absolute; + right: 5px; + top: 5px; + cursor: pointer; + + svg { + transform: scale(0.6); + } + } + .table-open { + display: none; + + .copyArrow { + top: 2px; + } + } + .schema-table-expanded .table-open { + display: block; + } + .table-name:hover, + .table-open:hover { + .copy-arrow { + display: block; + } + } +} diff --git a/client/app/components/dynamic-table/index.js b/client/app/components/dynamic-table/index.js index a51f661e52..d59c944910 100644 --- a/client/app/components/dynamic-table/index.js +++ b/client/app/components/dynamic-table/index.js @@ -69,12 +69,12 @@ function createRowRenderTemplate(columns, $compile) { switch (column.displayAs) { case 'json': return ` - `; default: return ` - `; } diff --git a/client/app/components/queries/SchemaBrowser.jsx b/client/app/components/queries/SchemaBrowser.jsx new file mode 100644 index 0000000000..502184973c --- /dev/null +++ b/client/app/components/queries/SchemaBrowser.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { react2angular } from 'react2angular'; +import PropTypes from 'prop-types'; +import { Icon } from 'antd'; +import { debounce } from 'lodash'; +import { $rootScope } from '@/services/ng'; + +import { Schema } from '../proptypes'; + +const isSchemaEmpty = schema => schema === undefined || !schema.length; + +// Return true if the search term is contained within the table name +// or any of the table's columns. +const doesTableContainSearchTerm = (searchTerm, table) => { + const regEx = new RegExp(searchTerm, 'gi'); + + // If the table name matches the search term we can return early. + if (regEx.test(table.name)) return true; + + return !!table.columns.filter(column => regEx.test(column)).length; +}; + +const doCopy = (evt, hierarchy) => { + // eslint-disable-next-line + $rootScope.$broadcast('query-editor.command', 'paste', hierarchy.join('.')); + evt.stopPropagation(); +}; + +class SchemaBrowser extends React.Component { + static propTypes = { + schema: Schema, // eslint-disable-line react/no-unused-prop-types + doRefresh: PropTypes.func.isRequired, + }; + + static defaultProps = { + schema: null, + }; + + constructor(props) { + super(props); + + this.state = { + searchTerm: '', + schema: [], + schemaInitialized: false, + }; + + // Candidate to be abstracted out to a set of config constants. + // Performs search filtering after the number of milliseconds set below. + this.doSearch = debounce(this.doSearch, 150); + } + + componentDidUpdate(prevProps, prevState) { + if (prevProps.schema && (this.props.schema && prevState.schema.length !== this.props.schema.length)) { + // setState() is fine here within the right conditions. + // eslint-disable-next-line + if (!this.state.schemaInitialized) this.setState({ schema: this.props.schema }); + } + } + + handleSearch = (evt) => { + if (!evt.target.value) { + this.setState({ schema: this.props.schema, searchTerm: '' }); + return; + } + this.setState({ searchTerm: evt.target.value }); + this.doSearch(evt.target.value); + } + + doSearch = (searchTerm) => { + const schema = this.props.schema.filter(table => doesTableContainSearchTerm(searchTerm, table)); + this.setState({ searchTerm, schema }); + } + + getTitleElement = (title, isTableRow) => ( + + {isTableRow && } + {` ${title} `} + { doCopy(evt, [title]); }} + aria-hidden="true" + type="double-right" + className="copy-arrow" + /> + + ); + + doRowToggle = ({ target }) => { + target.closest('.table-item').classList.toggle('schema-table-expanded'); + } + + render() { + if (!this.props.schema) { + return null; + } + return ( +
+
+ + +
+
+ {this.state.schema.map(table => ( +
+
{this.getTitleElement(table.name, true)}
+ {table.columns.map(column => ( +
{this.getTitleElement(column)}
+ ))} +
+ ))} +
+
+ ); + } +} + +export default function init(ngModule) { + ngModule.component('schemaBrowser', react2angular(SchemaBrowser)); +} + +init.init = true; diff --git a/client/app/components/queries/schema-browser.html b/client/app/components/queries/schema-browser.html deleted file mode 100644 index 6e3f518059..0000000000 --- a/client/app/components/queries/schema-browser.html +++ /dev/null @@ -1,30 +0,0 @@ -
-
- - -
- -
-
-
- - - {{table.name}} - ({{table.size}}) - - -
-
-
{{column}} - -
-
-
-
-
diff --git a/client/app/components/queries/schema-browser.js b/client/app/components/queries/schema-browser.js deleted file mode 100644 index 34615aa590..0000000000 --- a/client/app/components/queries/schema-browser.js +++ /dev/null @@ -1,57 +0,0 @@ -import template from './schema-browser.html'; - -function SchemaBrowserCtrl($rootScope, $scope) { - 'ngInject'; - - this.showTable = (table) => { - table.collapsed = !table.collapsed; - $scope.$broadcast('vsRepeatTrigger'); - }; - - this.getSize = (table) => { - let size = 22; - - if (!table.collapsed) { - size += 18 * table.columns.length; - } - - return size; - }; - - this.isEmpty = function isEmpty() { - return this.schema === undefined || this.schema.length === 0; - }; - - this.itemSelected = ($event, hierarchy) => { - $rootScope.$broadcast('query-editor.command', 'paste', hierarchy.join('.')); - $event.preventDefault(); - $event.stopPropagation(); - }; - - this.splitFilter = (filter) => { - filter = filter.replace(/ {2}/g, ' '); - if (filter.includes(' ')) { - const splitTheFilter = filter.split(' '); - this.schemaFilterObject = { name: splitTheFilter[0], columns: splitTheFilter[1] }; - this.schemaFilterColumn = splitTheFilter[1]; - } else { - this.schemaFilterObject = filter; - this.schemaFilterColumn = ''; - } - }; -} - -const SchemaBrowser = { - bindings: { - schema: '<', - onRefresh: '&', - }, - controller: SchemaBrowserCtrl, - template, -}; - -export default function init(ngModule) { - ngModule.component('schemaBrowser', SchemaBrowser); -} - -init.init = true; diff --git a/client/app/config/index.js b/client/app/config/index.js index fe3a83def5..33a74bdde3 100644 --- a/client/app/config/index.js +++ b/client/app/config/index.js @@ -15,7 +15,6 @@ import uiSelect from 'ui-select'; import ngMessages from 'angular-messages'; import toastr from 'angular-toastr'; import ngUpload from 'angular-base64-upload'; -import vsRepeat from 'angular-vs-repeat'; import 'brace'; import 'angular-ui-ace'; import 'angular-resizable'; @@ -53,7 +52,6 @@ const requirements = [ 'ui.ace', ngUpload, 'angularResizable', - vsRepeat, 'ui.sortable', ]; diff --git a/client/app/pages/queries/query.html b/client/app/pages/queries/query.html index d95b0b76db..39cb454fda 100644 --- a/client/app/pages/queries/query.html +++ b/client/app/pages/queries/query.html @@ -93,7 +93,11 @@

- + +
 
diff --git a/package.json b/package.json index 620281bae0..de4f69bb4f 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "angular-toastr": "^2.1.1", "angular-ui-ace": "^0.2.3", "angular-ui-bootstrap": "^2.5.0", - "angular-vs-repeat": "^1.1.7", "antd": "^3.12.3", "bootstrap": "^3.3.7", "brace": "^0.11.0",