diff --git a/.circleci/docker-compose.cypress.yml b/.circleci/docker-compose.cypress.yml index 5305f41d4e..2483582ce7 100644 --- a/.circleci/docker-compose.cypress.yml +++ b/.circleci/docker-compose.cypress.yml @@ -23,7 +23,7 @@ services: REDASH_LOG_LEVEL: "INFO" REDASH_REDIS_URL: "redis://redis:6379/0" REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres" - QUEUES: "queries,scheduled_queries,celery" + QUEUES: "queries,scheduled_queries,celery,schemas" WORKERS_COUNT: 2 cypress: build: diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index 8cc09a4949..2ecd723d66 100755 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -3,7 +3,7 @@ set -e worker() { WORKERS_COUNT=${WORKERS_COUNT:-2} - QUEUES=${QUEUES:-queries,scheduled_queries,celery} + QUEUES=${QUEUES:-queries,scheduled_queries,celery,schemas} echo "Starting $WORKERS_COUNT workers for queues: $QUEUES..." exec /usr/local/bin/celery worker --app=redash.worker -c$WORKERS_COUNT -Q$QUEUES -linfo --maxtasksperchild=10 -Ofair diff --git a/client/app/assets/less/ant.less b/client/app/assets/less/ant.less index d2fd4a9581..0037893255 100644 --- a/client/app/assets/less/ant.less +++ b/client/app/assets/less/ant.less @@ -13,6 +13,7 @@ @import '~antd/lib/radio/style/index'; @import '~antd/lib/time-picker/style/index'; @import '~antd/lib/pagination/style/index'; +@import '~antd/lib/drawer/style/index'; @import '~antd/lib/table/style/index'; @import '~antd/lib/popover/style/index'; @import '~antd/lib/icon/style/index'; diff --git a/client/app/assets/less/inc/schema-browser.less b/client/app/assets/less/inc/schema-browser.less index 0034391086..d547a78790 100644 --- a/client/app/assets/less/inc/schema-browser.less +++ b/client/app/assets/less/inc/schema-browser.less @@ -7,14 +7,14 @@ div.table-name { border-radius: @redash-radius; position: relative; - .copy-to-editor { + .copy-to-editor, .info { display: none; } &:hover { background: fade(@redash-gray, 10%); - .copy-to-editor { + .copy-to-editor, .info { display: flex; } } @@ -36,7 +36,7 @@ div.table-name { background: transparent; } - .copy-to-editor { + .copy-to-editor, .info { color: fade(@redash-gray, 90%); cursor: pointer; position: absolute; @@ -49,6 +49,10 @@ div.table-name { justify-content: center; } + .info { + right: 20px + } + .table-open { padding: 0 22px 0 26px; overflow: hidden; @@ -56,14 +60,14 @@ div.table-name { white-space: nowrap; position: relative; - .copy-to-editor { + .copy-to-editor, .info { display: none; } &:hover { background: fade(@redash-gray, 10%); - .copy-to-editor { + .copy-to-editor, .info { display: flex; } } diff --git a/client/app/assets/less/redash/redash-newstyle.less b/client/app/assets/less/redash/redash-newstyle.less index b2e6ebf018..b6f14e392a 100644 --- a/client/app/assets/less/redash/redash-newstyle.less +++ b/client/app/assets/less/redash/redash-newstyle.less @@ -101,6 +101,10 @@ body { } } +.admin-schema-editor { + padding: 50px 0; +} + .creation-container { h5 { color: #a7a7a7; diff --git a/client/app/components/proptypes.js b/client/app/components/proptypes.js index f35e89cc0e..36f7824547 100644 --- a/client/app/components/proptypes.js +++ b/client/app/components/proptypes.js @@ -11,8 +11,16 @@ export const DataSource = PropTypes.shape({ type_name: PropTypes.string, }); +export const DataSourceMetadata = PropTypes.shape({ + key: PropTypes.number, + name: PropTypes.string, + type: PropTypes.string, + example: PropTypes.string, + column_description: PropTypes.string, +}); + export const Table = PropTypes.shape({ - columns: PropTypes.arrayOf(PropTypes.string).isRequired, + columns: PropTypes.arrayOf(PropTypes.object).isRequired, }); export const Schema = PropTypes.arrayOf(Table); @@ -31,6 +39,13 @@ export const RefreshScheduleDefault = { until: null, }; +export const TableMetadata = PropTypes.shape({ + key: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + table_description: PropTypes.string.isRequired, + table_visible: PropTypes.bool.isRequired, +}); + export const Field = PropTypes.shape({ name: PropTypes.string.isRequired, title: PropTypes.string, diff --git a/client/app/components/queries/SchemaData.jsx b/client/app/components/queries/SchemaData.jsx new file mode 100644 index 0000000000..400626aafb --- /dev/null +++ b/client/app/components/queries/SchemaData.jsx @@ -0,0 +1,75 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { react2angular } from 'react2angular'; +import Drawer from 'antd/lib/drawer'; +import Table from 'antd/lib/table'; + +import { DataSourceMetadata } from '@/components/proptypes'; + +class SchemaData extends React.PureComponent { + static propTypes = { + show: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + tableName: PropTypes.string, + tableDescription: PropTypes.string, + tableMetadata: PropTypes.arrayOf(DataSourceMetadata), + }; + + static defaultProps = { + tableName: '', + tableDescription: '', + tableMetadata: [], + }; + + render() { + const columns = [{ + title: 'Column Name', + dataIndex: 'name', + width: 400, + key: 'name', + }, { + title: 'Column Type', + dataIndex: 'type', + width: 400, + key: 'type', + }, { + title: 'Example', + dataIndex: 'example', + width: 400, + key: 'example', + }, { + title: 'Description', + dataIndex: 'column_description', + width: 400, + key: 'column_description', + }]; + + return ( + +
+ {this.props.tableDescription} +
+ + + ); + } +} + +export default function init(ngModule) { + ngModule.component('schemaData', react2angular(SchemaData, null, [])); +} + +init.init = true; diff --git a/client/app/components/queries/schema-browser.html b/client/app/components/queries/schema-browser.html index 6e3f518059..7623aeef59 100644 --- a/client/app/components/queries/schema-browser.html +++ b/client/app/components/queries/schema-browser.html @@ -9,22 +9,31 @@
-
+
{{table.name}} ({{table.size}}) +
-
{{column}} +
{{column.name}} + ng-click="$ctrl.itemSelected($event, [column.name])">
+
diff --git a/client/app/components/queries/schema-browser.js b/client/app/components/queries/schema-browser.js index 34615aa590..13057f3dae 100644 --- a/client/app/components/queries/schema-browser.js +++ b/client/app/components/queries/schema-browser.js @@ -8,6 +8,18 @@ function SchemaBrowserCtrl($rootScope, $scope) { $scope.$broadcast('vsRepeatTrigger'); }; + $scope.showSchemaInfo = false; + $scope.openSchemaInfo = ($event, table) => { + $scope.tableName = table.name; + $scope.tableDescription = table.table_description; + $scope.tableMetadata = table.columns; + $scope.showSchemaInfo = true; + $event.stopPropagation(); + }; + $scope.closeSchemaInfo = () => { + $scope.$apply(() => { $scope.showSchemaInfo = false; }); + }; + this.getSize = (table) => { let size = 22; @@ -22,6 +34,13 @@ function SchemaBrowserCtrl($rootScope, $scope) { return this.schema === undefined || this.schema.length === 0; }; + this.itemExists = (item) => { + if ('visible' in item) { + return item.exists && item.visible; + } + return item.exists; + }; + this.itemSelected = ($event, hierarchy) => { $rootScope.$broadcast('query-editor.command', 'paste', hierarchy.join('.')); $event.preventDefault(); diff --git a/client/app/pages/data-sources/schema-table-components/EditableTable.jsx b/client/app/pages/data-sources/schema-table-components/EditableTable.jsx new file mode 100644 index 0000000000..8f60ef4fc0 --- /dev/null +++ b/client/app/pages/data-sources/schema-table-components/EditableTable.jsx @@ -0,0 +1,86 @@ +import React from 'react'; +import Form from 'antd/lib/form'; +import Input from 'antd/lib/input'; +import PropTypes from 'prop-types'; +import { TableMetadata } from '@/components/proptypes'; +import TableVisibilityCheckbox from './TableVisibilityCheckbox'; +import './schema-table.css'; + +const FormItem = Form.Item; +const { TextArea } = Input; +export const EditableContext = React.createContext(); + +// eslint-disable-next-line react/prop-types +const EditableRow = ({ form, index, ...props }) => ( + +
+ +); + +export const EditableFormRow = Form.create()(EditableRow); + +export class EditableCell extends React.Component { + static propTypes = { + dataIndex: PropTypes.string, + input_type: PropTypes.string, + editing: PropTypes.bool, + record: TableMetadata, + }; + + static defaultProps = { + dataIndex: undefined, + input_type: undefined, + editing: false, + record: {}, + }; + + constructor(props) { + super(props); + this.state = { + visible: this.props.record ? this.props.record.table_visible : false, + }; + } + + onChange = () => { + this.setState({ visible: !this.state.visible }); + } + + getInput = () => { + if (this.props.input_type === 'table_visible') { + return ( + ); + } + return