UNPKG

@lyra/vision

Version:

React-based data management tool for Lyra projects

494 lines (416 loc) 16 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _queryString = require('query-string'); var _queryString2 = _interopRequireDefault(_queryString); var _localState = require('../util/localState'); var _parseApiQueryString = require('../util/parseApiQueryString'); var _parseApiQueryString2 = _interopRequireDefault(_parseApiQueryString); var _tryParseParams = require('../util/tryParseParams'); var _tryParseParams2 = _interopRequireDefault(_tryParseParams); var _DelayedSpinner = require('./DelayedSpinner'); var _DelayedSpinner2 = _interopRequireDefault(_DelayedSpinner); var _QueryEditor = require('./QueryEditor'); var _QueryEditor2 = _interopRequireDefault(_QueryEditor); var _ParamsEditor = require('./ParamsEditor'); var _ParamsEditor2 = _interopRequireDefault(_ParamsEditor); var _ResultView = require('./ResultView'); var _ResultView2 = _interopRequireDefault(_ResultView); var _NoResultsDialog = require('./NoResultsDialog'); var _NoResultsDialog2 = _interopRequireDefault(_NoResultsDialog); var _QueryErrorDialog = require('./QueryErrorDialog'); var _QueryErrorDialog2 = _interopRequireDefault(_QueryErrorDialog); var _reactSplitPane = require('react-split-pane'); var _reactSplitPane2 = _interopRequireDefault(_reactSplitPane); var _encodeQueryString = require('../util/encodeQueryString'); var _encodeQueryString2 = _interopRequireDefault(_encodeQueryString); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const lyraUrl = /\.api\.lyra\.io.*?(?:query|listen)\/(.*?)\?(.*)/; const handleCopyUrl = () => { const emailLink = document.querySelector('#vision-query-url'); emailLink.select(); try { document.execCommand('copy'); } catch (err) { // eslint-disable-next-line no-console console.error('Unable to copy to clipboard :('); } }; class VisionGui extends _react2.default.PureComponent { constructor(props) { super(props); const lastQuery = (0, _localState.getState)('lastQuery'); const lastParams = (0, _localState.getState)('lastParams'); const firstDataset = this.props.datasets[0] && this.props.datasets[0].name; let dataset = (0, _localState.getState)('dataset', firstDataset); if (!this.props.datasets.includes(dataset)) { dataset = firstDataset; } this.subscribers = {}; this.state = { query: lastQuery, params: lastParams && (0, _tryParseParams2.default)(lastParams), rawParams: lastParams, queryInProgress: false, editorHeight: 100, dataset }; this.handleChangeDataset = this.handleChangeDataset.bind(this); this.handleListenExecution = this.handleListenExecution.bind(this); this.handleListenerMutation = this.handleListenerMutation.bind(this); this.handleQueryExecution = this.handleQueryExecution.bind(this); this.handleQueryChange = this.handleQueryChange.bind(this); this.handleParamsChange = this.handleParamsChange.bind(this); this.handleHeightChange = this.handleHeightChange.bind(this); this.handlePaste = this.handlePaste.bind(this); } componentDidMount() { this.context.client.config({ dataset: this.state.dataset }); window.document.addEventListener('paste', this.handlePaste); } componentWillUnmount() { this.cancelQuery(); this.cancelListener(); } handlePaste(evt) { const data = evt.clipboardData.getData('text/plain'); const match = data.match(lyraUrl); if (!match) { return; } var _match = _slicedToArray(match, 3); const dataset = _match[1], urlQuery = _match[2]; const qs = _queryString2.default.parse(urlQuery); let parts; try { parts = (0, _parseApiQueryString2.default)(qs); } catch (err) { console.warn('Error while trying to parse API URL: ', err.message); // eslint-disable-line no-console return; // Give up on error } if (this.context.client.config().dataset !== dataset) { this.handleChangeDataset({ target: { value: dataset } }); } evt.preventDefault(); this.setState({ query: parts.query, params: parts.params, rawParams: JSON.stringify(parts.params, null, 2) }); } cancelQuery() { if (!this.subscribers.query) { return; } this.subscribers.query.unsubscribe(); this.subscribers.query = null; } cancelListener() { if (!this.subscribers.listen) { return; } this.subscribers.listen.unsubscribe(); this.subscribers.listen = null; } handleChangeDataset(evt) { const dataset = evt.target.value; (0, _localState.storeState)('dataset', dataset); this.setState({ dataset }); this.context.client.config({ dataset }); this.handleQueryExecution(); } handleListenerMutation(mut) { const listenMutations = [mut].concat(this.state.listenMutations); if (listenMutations.length > 50) { listenMutations.pop(); } this.setState({ listenMutations }); } handleListenExecution() { var _state = this.state; const query = _state.query, params = _state.params, rawParams = _state.rawParams, listenInProgress = _state.listenInProgress; if (listenInProgress) { this.cancelListener(); this.setState({ listenInProgress: false }); return; } const client = this.context.client; const paramsError = params instanceof Error && params; const url = client.getUrl(client.getDataUrl('listen', (0, _encodeQueryString2.default)(query, params))); (0, _localState.storeState)('lastQuery', query); (0, _localState.storeState)('lastParams', rawParams); this.cancelQuery(); this.setState({ url, listenMutations: [], queryInProgress: false, listenInProgress: !paramsError && Boolean(query), error: paramsError || undefined, result: undefined, queryTime: null, e2eTime: null }); if (!query || paramsError) { return; } this.subscribers.listen = client.listen(query, params, {}).subscribe({ next: this.handleListenerMutation, error: _error => this.setState({ error: _error, query, listenInProgress: false }) }); } handleQueryExecution() { var _state2 = this.state; const query = _state2.query, params = _state2.params, rawParams = _state2.rawParams; const client = this.context.client.observable; const paramsError = params instanceof Error && params; (0, _localState.storeState)('lastQuery', query); (0, _localState.storeState)('lastParams', rawParams); this.cancelListener(); this.setState({ queryInProgress: !paramsError && Boolean(query), listenInProgress: false, listenMutations: [], error: paramsError || undefined, result: undefined, queryTime: null, e2eTime: null }); if (!query || paramsError) { return; } const url = client.getUrl(client.getDataUrl('query', (0, _encodeQueryString2.default)(query, params))); const queryStart = Date.now(); this.subscribers.query = client.fetch(query, params, { filterResponse: false }).subscribe({ next: res => this.setState({ query, url, queryTime: res.ms, e2eTime: Date.now() - queryStart, result: res.result, queryInProgress: false, error: null }), error: _error2 => this.setState({ error: _error2, query, queryInProgress: false }) }); } handleQueryChange(data) { this.setState({ query: data.query }); } handleParamsChange(data) { this.setState({ rawParams: data.raw, params: data.parsed }); } handleHeightChange(newHeight) { if (this.state.editorHeight !== newHeight) { this.setState({ editorHeight: Math.max(newHeight, 75) }); } } render() { var _context = this.context; const client = _context.client, components = _context.components; var _state3 = this.state; const error = _state3.error, result = _state3.result, url = _state3.url, query = _state3.query, queryInProgress = _state3.queryInProgress, listenInProgress = _state3.listenInProgress, queryTime = _state3.queryTime, e2eTime = _state3.e2eTime, listenMutations = _state3.listenMutations; const Button = components.Button, Select = components.Select; const styles = this.context.styles.visionGui; const dataset = client.config().dataset; const datasets = this.props.datasets.map(set => set.name); const hasResult = !error && !queryInProgress && typeof result !== 'undefined'; // Note that because of react-json-inspector, we need at least one // addressable, non-generated class name. Therefore; // leave `lyra-vision` untouched! const visionClass = ['lyra-vision', this.context.styles.visionGui.root].filter(Boolean).join(' '); const headerClass = ['lyra-vision', this.context.styles.visionGui.header].filter(Boolean).join(' '); return _react2.default.createElement( 'div', { className: visionClass }, _react2.default.createElement( 'div', { className: headerClass }, _react2.default.createElement( 'div', { className: styles.headerLeft }, _react2.default.createElement( 'label', { className: styles.datasetSelectorContainer }, _react2.default.createElement( 'span', { className: styles.datasetLabel }, 'Dataset' ), _react2.default.createElement(Select, { value: this.state.dataset || client.config().dataset, values: datasets, onChange: this.handleChangeDataset }) ) ), _react2.default.createElement( 'div', { className: styles.queryUrlContainer }, typeof url === 'string' && _react2.default.createElement( 'span', null, 'Query URL:', _react2.default.createElement('input', { className: styles.queryUrl, readOnly: true, id: 'vision-query-url', value: url }), _react2.default.createElement( 'button', { onClick: handleCopyUrl }, 'Copy' ) ) ), _react2.default.createElement( 'div', { className: styles.queryTimingContainer }, typeof queryTime === 'number' && _react2.default.createElement( 'p', { className: queryTime > 0.5 ? styles.queryTiming || 'queryTiming' : styles.queryTimingLong || 'queryTiming' }, 'Query time: ', queryTime, 'ms (end-to-end: ', e2eTime, 'ms)' ) ), _react2.default.createElement( 'div', { className: styles.headerFunctions }, _react2.default.createElement( Button, { onClick: this.handleListenExecution, loading: listenInProgress }, 'Listen' ), _react2.default.createElement( Button, { onClick: this.handleQueryExecution, loading: queryInProgress, color: 'primary' }, 'Run query' ) ) ), _react2.default.createElement( 'div', { className: styles.splitContainer }, _react2.default.createElement( _reactSplitPane2.default, { split: 'vertical', minSize: 150, defaultSize: 400 }, _react2.default.createElement( 'div', { className: styles.edit }, _react2.default.createElement( _reactSplitPane2.default, { split: 'horizontal', defaultSize: '80%' }, _react2.default.createElement( 'div', { className: styles.inputContainer }, _react2.default.createElement( 'h3', { className: styles.inputLabelQuery || 'query' }, 'Query' ), _react2.default.createElement(_QueryEditor2.default, { className: styles.queryEditor, value: this.state.query, onExecute: this.handleQueryExecution, onChange: this.handleQueryChange, onHeightChange: this.handleHeightChange, style: { minHeight: this.state.editorHeight }, schema: this.props.schema }) ), _react2.default.createElement( 'div', { className: styles.inputContainer }, _react2.default.createElement( 'h3', { className: styles.inputLabelQuery || 'query' }, 'Params' ), _react2.default.createElement(_ParamsEditor2.default, { className: styles.paramsEditor, classNameInvalid: styles.paramsEditorInvalid, value: this.state.rawParams, onExecute: this.handleQueryExecution, onChange: this.handleParamsChange, onHeightChange: this.handleHeightChange, style: { minHeight: this.state.editorHeight } }) ) ) ), _react2.default.createElement( 'div', { className: styles.resultContainer }, _react2.default.createElement( 'h3', { className: styles.inputLabelQuery || 'resultLabel' }, 'Result' ), _react2.default.createElement( 'div', { className: styles.result }, queryInProgress && _react2.default.createElement(_DelayedSpinner2.default, null), error && _react2.default.createElement(_QueryErrorDialog2.default, { error: error }), hasResult && _react2.default.createElement(_ResultView2.default, { data: result, query: query }), Array.isArray(result) && result.length === 0 && _react2.default.createElement(_NoResultsDialog2.default, { query: query, dataset: dataset }), listenMutations && listenMutations.length > 0 && _react2.default.createElement(_ResultView2.default, { data: listenMutations }) ) ) ) ) ); } } VisionGui.propTypes = { datasets: _propTypes2.default.arrayOf(_propTypes2.default.shape({ name: _propTypes2.default.string })) }; VisionGui.contextTypes = { client: _propTypes2.default.shape({ fetch: _propTypes2.default.func }).isRequired, styles: _propTypes2.default.object, components: _propTypes2.default.object }; exports.default = VisionGui;