Skip to content

Commit

Permalink
Add UI for adding and viewing sample queries.
Browse files Browse the repository at this point in the history
  • Loading branch information
Marina Samuel committed Feb 5, 2019
1 parent d4746ad commit 424e193
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 12 deletions.
5 changes: 5 additions & 0 deletions client/app/components/proptypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export const TableMetadata = PropTypes.shape({
table_visible: PropTypes.bool.isRequired,
});

export const Query = PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
});

export const Field = PropTypes.shape({
name: PropTypes.string.isRequired,
title: PropTypes.string,
Expand Down
59 changes: 51 additions & 8 deletions client/app/components/queries/SchemaData.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,51 @@ import { react2angular } from 'react2angular';
import Drawer from 'antd/lib/drawer';
import Table from 'antd/lib/table';

import { DataSourceMetadata } from '@/components/proptypes';
import { DataSourceMetadata, Query } from '@/components/proptypes';

class SchemaData extends React.PureComponent {
static propTypes = {
show: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
tableName: PropTypes.string,
tableDescription: PropTypes.string,
sampleQueries: PropTypes.arrayOf(Query),
tableMetadata: PropTypes.arrayOf(DataSourceMetadata),
};

static defaultProps = {
tableName: '',
tableDescription: '',
tableMetadata: [],
sampleQueries: [],
};

render() {
const columns = [{
const tableDataColumns = [{
title: 'Metadata',
dataIndex: 'metadata',
width: 400,
key: 'metadata',
}, {
title: 'Value',
dataIndex: 'value',
width: 400,
key: 'value',
render: (text) => {
if (typeof text === 'string') {
return text;
}
return (
<ul style={{ margin: 0, paddingLeft: '15px' }}>
{Object.values(text).map(query => (
<li><a target="_blank" rel="noopener noreferrer" href={`queries/${query.id}/source`}>{query.name}</a></li>
))}
</ul>
);
},
}];

const columnDataColumns = [{
title: 'Column Name',
dataIndex: 'name',
width: 400,
Expand All @@ -44,24 +70,41 @@ class SchemaData extends React.PureComponent {
key: 'column_description',
}];

const tableData = [{
metadata: 'Table Description',
value: this.props.tableDescription || 'N/A',
}, {
metadata: 'Sample Usage',
value: this.props.sampleQueries.length > 0 ? this.props.sampleQueries : 'N/A',
}];

return (
<Drawer
title={this.props.tableName}
closable={false}
placement="bottom"
height={500}
onClose={this.props.onClose}
visible={this.props.show}
>
<h5 className="table-description">
{this.props.tableDescription}
</h5>
<h4 style={{ margin: 0 }}>{this.props.tableName}</h4>
<hr />
<h5>Table Data</h5>
<Table
dataSource={this.props.tableMetadata}
dataSource={tableData}
pagination={false}
scroll={{ y: 350 }}
size="small"
columns={columns}
showHeader={false}
columns={tableDataColumns}
/>
<br />
<h5>Column Data</h5>
<Table
dataSource={this.props.tableMetadata}
pagination={false}
scroll={{ y: 175 }}
size="small"
columns={columnDataColumns}
/>
</Drawer>
);
Expand Down
1 change: 1 addition & 0 deletions client/app/components/queries/schema-browser.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
table-name="tableName"
table-description="tableDescription"
table-metadata="tableMetadata"
sample-queries="sampleQueries"
on-close="closeSchemaInfo"
></schema-data>
</div>
1 change: 1 addition & 0 deletions client/app/components/queries/schema-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function SchemaBrowserCtrl($rootScope, $scope) {
$scope.tableName = table.name;
$scope.tableDescription = table.table_description;
$scope.tableMetadata = table.columns;
$scope.sampleQueries = Object.values(table.sample_queries);
$scope.showSchemaInfo = true;
$event.stopPropagation();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Input from 'antd/lib/input';
import PropTypes from 'prop-types';
import { TableMetadata } from '@/components/proptypes';
import TableVisibilityCheckbox from './TableVisibilityCheckbox';
import SampleQueryList from './SampleQueryList';

import './schema-table.css';

const FormItem = Form.Item;
Expand All @@ -24,13 +26,15 @@ export class EditableCell extends React.Component {
dataIndex: PropTypes.string,
input_type: PropTypes.string,
editing: PropTypes.bool,
query: PropTypes.func,
record: TableMetadata,
};

static defaultProps = {
dataIndex: undefined,
input_type: undefined,
editing: false,
query: function query() {},
record: {},
};

Expand All @@ -52,6 +56,8 @@ export class EditableCell extends React.Component {
visible={this.state.visible}
onChange={this.onChange}
/>);
} else if (this.props.input_type === 'sample_queries') {
return <SampleQueryList query={this.props.query} />;
}
return <TextArea className="table-textarea" placeholder="Enter table description..." style={{ resize: 'vertical' }} />;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import Input from 'antd/lib/input';
import PropTypes from 'prop-types';
import { Query } from '@/components/proptypes';


const QueryListItem = (props) => {
if (!props.query) {
return <div />;
}

return (
<div className="p-relative">
<Input
className="bg-white"
readOnly="readonly"
disabled
value={props.query.name}
/>
{
props.removeQuery ? (
<a
href="#"
onClick={() => props.removeQuery(null)}
className="d-flex align-items-center justify-content-center"
style={{
position: 'absolute',
right: '1px',
top: '1px',
bottom: '1px',
width: '30px',
background: '#fff',
borderRadius: '3px',
}}
>
<i className="text-muted fa fa-times" />
</a>) : null
}
</div>
);
};

QueryListItem.propTypes = {
query: Query.isRequired,
removeQuery: PropTypes.func.isRequired,
};

export default QueryListItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React from 'react';
import Modal from 'antd/lib/modal';
import Input from 'antd/lib/input';
import PropTypes from 'prop-types';

import QueryListItem from './QueryListItem';


export default class QuerySearchDialog extends React.Component {
static propTypes = {
onOk: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
visible: PropTypes.bool.isRequired,
query: PropTypes.func.isRequired,
};

constructor(props) {
super(props);
this.state = {
selectedQuery: null,
searchedQueries: [],
};
}

getHighlightedText = (text, highlight) => {
// Split text on highlight term, include term itself into parts, ignore case
const parts = text.split(new RegExp(`(${highlight})`, 'gi'));
return (
<span>
{
parts.map(part => (
<span style={part.toLowerCase() === highlight.toLowerCase() ? { fontWeight: 'bold' } : {}}>
{ part }
</span>
))
}
</span>
);
}

createRecentQueriesList = () => {
if (!this.recentQueries || this.recentQueries.length <= 0) {
return [];
}
return this.recentQueries.map(query => (
<button key={query.id} className="list-group-item" onClick={() => this.selectQuery(query)}>
{query.name}
</button>
));
}

createSearchQueriesList = () =>
(this.state.searchedQueries.map(query => (
<button key={query.id} className="btn btn-default list-group-item" onClick={() => this.selectQuery(query)}>
{this.getHighlightedText(query.name, this.searchTerm)}
</button>
)));

selectQuery = (query) => {
this.setState({ selectedQuery: query });
}

searchQueries = (e) => {
this.searchTerm = e.target.value;
this.props.query.query({ q: this.searchTerm }, (results) => {
this.setState({ searchedQueries: results.results });
});
}

render() {
const {
query,
} = this.props;

query.recent().$promise.then((items) => {
this.recentQueries = items;
});

return (
<Modal
title="Add Sample Query"
visible={this.props.visible}
onCancel={this.props.onCancel}
onOk={() => this.props.onOk(this.state.selectedQuery)}
okText="Add Sample"
cancelText="Close"
>
{this.state.selectedQuery ?
(
<QueryListItem
query={this.state.selectedQuery}
removeQuery={() => this.selectQuery(null)}
/>
) : (
<div>
<div className="form-group">
<Input
className="form-control"
autoFocus
onChange={this.searchQueries}
placeholder="Search a query by name..."
/>
</div>

<div className="scrollbox" style={{ maxHeight: '50vh' }}>
{!this.state.searchedQueries || this.state.searchedQueries.length <= 0 ?
(
<div className="list-group">
{this.createRecentQueriesList()}
</div>
) : (
<div className="list-group">
{this.createSearchQueriesList()}
</div>
)
}
</div>
</div>
)
}

</Modal>
);
}
}
Loading

0 comments on commit 424e193

Please sign in to comment.