From 0e7af8d8a64a1e6073f55e798a2e952a6602133c Mon Sep 17 00:00:00 2001 From: Alanna Scott Date: Tue, 20 Sep 2016 13:45:27 -0700 Subject: [PATCH] [explore] refactor slice action button group (#1074) * pull explore actions button group into component * use button component * make sure we render all action buttons * test that embed code is correct * don't need before each * generalize modal trigger for use with plain links or icons --- .../SqlLab/components/QueryTable.jsx | 1 + .../SqlLab/components/TabbedSqlEditors.jsx | 1 + .../components/CopyToClipboard.jsx | 102 ++++++++++ .../javascripts/components/ModalTrigger.jsx | 59 ++++++ .../explore/components/DisplayQueryButton.jsx | 37 ++++ .../explore/components/EmbedCodeButton.jsx | 105 +++++++++++ .../components/ExploreActionButtons.jsx | 46 +++++ .../explore/components/URLShortLinkButton.jsx | 71 +++++++ .../assets/javascripts/explore/explore.jsx | 176 +++--------------- caravel/assets/javascripts/modules/caravel.js | 13 +- .../components/CopyToClipboard_spec.jsx | 17 ++ .../components/ModalTrigger_spec.jsx | 19 ++ .../components/DisplayQueryButton_spec.jsx | 17 ++ .../components/EmbedCodeButton_spec.jsx | 37 ++++ .../components/ExploreActionButtons_spec.jsx | 29 +++ .../components/URLShortLinkButton_spec.jsx | 17 ++ caravel/templates/caravel/explore.html | 48 +---- 17 files changed, 592 insertions(+), 203 deletions(-) create mode 100644 caravel/assets/javascripts/components/CopyToClipboard.jsx create mode 100644 caravel/assets/javascripts/components/ModalTrigger.jsx create mode 100644 caravel/assets/javascripts/explore/components/DisplayQueryButton.jsx create mode 100644 caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx create mode 100644 caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx create mode 100644 caravel/assets/javascripts/explore/components/URLShortLinkButton.jsx create mode 100644 caravel/assets/spec/javascripts/components/CopyToClipboard_spec.jsx create mode 100644 caravel/assets/spec/javascripts/components/ModalTrigger_spec.jsx create mode 100644 caravel/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx create mode 100644 caravel/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx create mode 100644 caravel/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx create mode 100644 caravel/assets/spec/javascripts/explore/components/URLShortLinkButton_spec.jsx diff --git a/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx b/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx index 025ea59d30..e9c35ea8c1 100644 --- a/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx +++ b/caravel/assets/javascripts/SqlLab/components/QueryTable.jsx @@ -33,6 +33,7 @@ class QueryTable extends React.Component { this.props.actions.queryEditorSetSql({ id: query.sqlEditorId }, query.sql); } notImplemented() { + /* eslint no-alert: 0 */ alert('Not implemented yet!'); } render() { diff --git a/caravel/assets/javascripts/SqlLab/components/TabbedSqlEditors.jsx b/caravel/assets/javascripts/SqlLab/components/TabbedSqlEditors.jsx index 2b38d76577..15ca8bd1a6 100644 --- a/caravel/assets/javascripts/SqlLab/components/TabbedSqlEditors.jsx +++ b/caravel/assets/javascripts/SqlLab/components/TabbedSqlEditors.jsx @@ -10,6 +10,7 @@ let queryCount = 1; class QueryEditors extends React.Component { renameTab(qe) { + /* eslint no-alert: 0 */ const newTitle = prompt('Enter a new title for the tab'); if (newTitle) { this.props.actions.queryEditorSetTitle(qe, newTitle); diff --git a/caravel/assets/javascripts/components/CopyToClipboard.jsx b/caravel/assets/javascripts/components/CopyToClipboard.jsx new file mode 100644 index 0000000000..c39d7ff950 --- /dev/null +++ b/caravel/assets/javascripts/components/CopyToClipboard.jsx @@ -0,0 +1,102 @@ +import React, { PropTypes } from 'react'; +import { Button, Tooltip, OverlayTrigger } from 'react-bootstrap'; + +const propTypes = { + copyNode: PropTypes.node, + onCopyEnd: PropTypes.func, + shouldShowText: PropTypes.bool, + text: PropTypes.string.isRequired, +}; + +const defaultProps = { + copyNode: Copy, + onCopyEnd: () => {}, + shouldShowText: true, +}; + +export default class CopyToClipboard extends React.Component { + constructor(props) { + super(props); + this.state = { + hasCopied: false, + }; + + // bindings + this.copyToClipboard = this.copyToClipboard.bind(this); + this.resetTooltipText = this.resetTooltipText.bind(this); + this.onMouseOut = this.onMouseOut.bind(this); + } + + onMouseOut() { + // delay to avoid flash of text change on tooltip + setTimeout(this.resetTooltipText, 200); + } + + resetTooltipText() { + this.setState({ hasCopied: false }); + } + + copyToClipboard() { + const textToCopy = this.props.text; + const textArea = document.createElement('textarea'); + + textArea.style.position = 'fixed'; + textArea.style.left = '-1000px'; + textArea.value = textToCopy; + + document.body.appendChild(textArea); + textArea.select(); + + try { + if (!document.execCommand('copy')) { + throw new Error('Not successful'); + } + } catch (err) { + window.alert('Sorry, your browser does not support copying. Use Ctrl / Cmd + C!'); // eslint-disable-line + } + + document.body.removeChild(textArea); + + this.setState({ hasCopied: true }); + this.props.onCopyEnd(); + } + + tooltipText() { + let tooltipText; + if (this.state.hasCopied) { + tooltipText = 'Copied!'; + } else { + tooltipText = 'Copy text'; + } + return tooltipText; + } + + render() { + const tooltip = ( + + {this.tooltipText()} + + ); + + return ( +
+ {this.props.shouldShowText && + {this.props.text} + } +      + + + +
+ ); + } +} + +CopyToClipboard.propTypes = propTypes; +CopyToClipboard.defaultProps = defaultProps; diff --git a/caravel/assets/javascripts/components/ModalTrigger.jsx b/caravel/assets/javascripts/components/ModalTrigger.jsx new file mode 100644 index 0000000000..b8705717e0 --- /dev/null +++ b/caravel/assets/javascripts/components/ModalTrigger.jsx @@ -0,0 +1,59 @@ +import React, { PropTypes } from 'react'; +import { Modal } from 'react-bootstrap'; +import cx from 'classnames'; + +const propTypes = { + triggerNode: PropTypes.node.isRequired, + modalTitle: PropTypes.string.isRequired, + modalBody: PropTypes.node.isRequired, + beforeOpen: PropTypes.func, + isButton: PropTypes.bool, +}; + +const defaultProps = { + beforeOpen: () => {}, + isButton: false, +}; + +export default class ModalTrigger extends React.Component { + constructor(props) { + super(props); + this.state = { + showModal: false, + }; + this.open = this.open.bind(this); + this.close = this.close.bind(this); + } + + close() { + this.setState({ showModal: false }); + } + + open(e) { + e.preventDefault(); + this.props.beforeOpen(); + this.setState({ showModal: true }); + } + + render() { + const classNames = cx({ + 'btn btn-default btn-sm': this.props.isButton, + }); + return ( + + {this.props.triggerNode} + + + {this.props.modalTitle} + + + {this.props.modalBody} + + + + ); + } +} + +ModalTrigger.propTypes = propTypes; +ModalTrigger.defaultProps = defaultProps; diff --git a/caravel/assets/javascripts/explore/components/DisplayQueryButton.jsx b/caravel/assets/javascripts/explore/components/DisplayQueryButton.jsx new file mode 100644 index 0000000000..6877fe7899 --- /dev/null +++ b/caravel/assets/javascripts/explore/components/DisplayQueryButton.jsx @@ -0,0 +1,37 @@ +import React, { PropTypes } from 'react'; +import ModalTrigger from './../../components/ModalTrigger'; + +const propTypes = { + slice: PropTypes.object.isRequired, +}; + +export default class DisplayQueryButton extends React.Component { + constructor(props) { + super(props); + this.state = { + viewSqlQuery: '', + }; + this.beforeOpen = this.beforeOpen.bind(this); + } + + beforeOpen() { + this.setState({ + viewSqlQuery: this.props.slice.viewSqlQuery, + }); + } + + render() { + const modalBody = (
{this.state.viewSqlQuery}
); + return ( + Query} + modalTitle="Query" + modalBody={modalBody} + beforeOpen={this.beforeOpen} + /> + ); + } +} + +DisplayQueryButton.propTypes = propTypes; diff --git a/caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx b/caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx new file mode 100644 index 0000000000..cddd41e62a --- /dev/null +++ b/caravel/assets/javascripts/explore/components/EmbedCodeButton.jsx @@ -0,0 +1,105 @@ +import React, { PropTypes } from 'react'; +import CopyToClipboard from './../../components/CopyToClipboard'; +import { Popover, OverlayTrigger } from 'react-bootstrap'; + +const propTypes = { + slice: PropTypes.object.isRequired, +}; + +export default class EmbedCodeButton extends React.Component { + constructor(props) { + super(props); + this.state = { + height: '400', + width: '600', + srcLink: window.location.origin + props.slice.data.standalone_endpoint, + }; + this.handleInputChange = this.handleInputChange.bind(this); + } + + handleInputChange(e) { + const value = e.currentTarget.value; + const name = e.currentTarget.name; + const data = {}; + data[name] = value; + this.setState(data); + } + + generateEmbedHTML() { + const { width, height, srcLink } = this.state; + /* eslint max-len: 0 */ + const embedHTML = + ``; + return embedHTML; + } + + renderPopover() { + const html = this.generateEmbedHTML(); + return ( + +
+
+
+ +
+
+ } + /> +
+
+
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+
+
+ ); + } + render() { + return ( + + +   + + + ); + } +} + +EmbedCodeButton.propTypes = propTypes; diff --git a/caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx b/caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx new file mode 100644 index 0000000000..95399b0806 --- /dev/null +++ b/caravel/assets/javascripts/explore/components/ExploreActionButtons.jsx @@ -0,0 +1,46 @@ +import React, { PropTypes } from 'react'; +import cx from 'classnames'; +import URLShortLinkButton from './URLShortLinkButton'; +import EmbedCodeButton from './EmbedCodeButton'; +import DisplayQueryButton from './DisplayQueryButton'; + +const propTypes = { + canDownload: PropTypes.string.isRequired, + slice: PropTypes.object.isRequired, +}; + +export default function ExploreActionButtons({ canDownload, slice }) { + const exportToCSVClasses = cx('btn btn-default btn-sm', { + 'disabled disabledButton': !canDownload, + }); + + return ( +
+ + + + + + .json + + + + .csv + + + +
+ ); +} + +ExploreActionButtons.propTypes = propTypes; diff --git a/caravel/assets/javascripts/explore/components/URLShortLinkButton.jsx b/caravel/assets/javascripts/explore/components/URLShortLinkButton.jsx new file mode 100644 index 0000000000..847ff2ebcd --- /dev/null +++ b/caravel/assets/javascripts/explore/components/URLShortLinkButton.jsx @@ -0,0 +1,71 @@ +import React, { PropTypes } from 'react'; +import { Popover, OverlayTrigger } from 'react-bootstrap'; +import CopyToClipboard from './../../components/CopyToClipboard'; +import $ from 'jquery'; + +const propTypes = { + slice: PropTypes.object.isRequired, +}; + +export default class URLShortLinkButton extends React.Component { + constructor(props) { + super(props); + this.state = { + shortUrl: '', + }; + + this.getShortUrl(); + } + + getShortUrl() { + $.ajax({ + type: 'POST', + url: '/r/shortner/', + data: { + data: '/' + window.location.pathname + this.props.slice.querystring(), + }, + success: (data) => { + this.setState({ + shortUrl: data, + }); + }, + error: (error) => { + /* eslint no-console: 0 */ + if (console && console.warn) { + console.warn('Something went wrong...'); + console.warn(error); + } + }, + }); + } + + renderPopover() { + return ( + + } + /> + + ); + } + + render() { + const shortUrl = this.state.shortUrl; + const isDisabled = shortUrl === ''; + return ( + + +   + + + ); + } +} + +URLShortLinkButton.propTypes = propTypes; diff --git a/caravel/assets/javascripts/explore/explore.jsx b/caravel/assets/javascripts/explore/explore.jsx index 76c548aa75..79aff8fe3b 100644 --- a/caravel/assets/javascripts/explore/explore.jsx +++ b/caravel/assets/javascripts/explore/explore.jsx @@ -11,6 +11,7 @@ const jQuery = window.jQuery = require('jquery'); // eslint-disable-line import React from 'react'; import ReactDOM from 'react-dom'; import QueryAndSaveBtns from './components/QueryAndSaveBtns.jsx'; +import ExploreActionButtons from './components/ExploreActionButtons.jsx'; require('jquery-ui'); $.widget.bridge('uitooltip', $.ui.tooltip); // Shutting down jq-ui tooltips @@ -62,7 +63,6 @@ function query(forceUpdate, pushState) { force = false; } $('.query-and-save button').attr('disabled', 'disabled'); - $('.btn-group.results span,a').attr('disabled', 'disabled'); if (force) { // Don't hide the alert message when the page is just loaded $('div.alert').remove(); } @@ -160,148 +160,6 @@ function initExploreView() { px.initFavStars(); - function copyURLToClipboard(url) { - const textArea = document.createElement('textarea'); - textArea.style.position = 'fixed'; - textArea.style.left = '-1000px'; - textArea.value = url; - - document.body.appendChild(textArea); - textArea.select(); - let successful; - try { - successful = document.execCommand('copy'); - if (!successful) { - throw new Error('Not successful'); - } - } catch (err) { - window.alert('Sorry, your browser does not support copying. Use Ctrl / Cmd + C!'); // eslint-disable-line - } - document.body.removeChild(textArea); - return successful; - } - - $('#shortner').click(function () { - $.ajax({ - type: 'POST', - url: '/r/shortner/', - data: { - data: '/' + window.location.pathname + slice.querystring(), - }, - success(data) { - const close = ( - '' + - '' + - '' - ); - const copy = ( - '' + - '' + - '' - ); - const spaces = '   '; - const popover = data + spaces + copy + spaces + close; - - const $shortner = $('#shortner') - .popover({ - content: popover, - placement: 'left', - html: true, - trigger: 'manual', - }) - .popover('show'); - function destroyPopover() { - $shortner.popover('destroy'); - } - - $('#copy_url') - .tooltip() - .click(function () { - const success = copyURLToClipboard(data); - if (success) { - $(this).attr('data-original-title', 'Copied!') - .tooltip('fixTitle') - .tooltip('show'); - window.setTimeout(destroyPopover, 1200); - } - }); - $('#close_shortner').click(destroyPopover); - }, - error(error) { - showModal({ - title: 'Error', - body: 'Sorry, an error occurred during this operation:
' + error, - }); - }, - }); - }); - - $('#standalone').click(function () { - const srcLink = window.location.origin + slice.data.standalone_endpoint; - const close = ( - '' + - '' + - '' - ); - const copy = ( - '' + - '' + - '' - ); - const spaces = '   '; - const widthInput = ''; - const heightInput = ''; - let popover = ""; - popover = popover + spaces + copy + spaces + close + spaces + widthInput + spaces + heightInput; - let dataToCopy = ''; - - const $standalone = $(this); - - function destroyPopover() { - $standalone.popover('destroy'); - } - - $standalone.popover({ - content: popover, - title: 'embed html', - placement: 'left', - html: true, - trigger: 'manual', - }) - .popover('show'); - $('#copy_embed').tooltip().click(function () { - const success = copyURLToClipboard(dataToCopy); - if (success) { - $(this).attr('data-original-title', 'Copied!') - .tooltip('fixTitle') - .tooltip('show'); - window.setTimeout(destroyPopover, 1200); - } - }); - - $('#close_standalone').click(destroyPopover); - - const $standaloneWidth = $('#standalone_width'); - const $standaloneHeight = $('#standalone_height'); - const $standaloneText = $('#standalone_text'); - - function generateEmbedHTML() { - const width = $standaloneWidth.val(); - const height = $standaloneHeight.val(); - dataToCopy = `'; - $standaloneText.val(dataToCopy); - } - - $standaloneHeight.change(function () { - generateEmbedHTML(); - }); - $standaloneWidth.change(function () { - generateEmbedHTML(); - }); - generateEmbedHTML(); - }); - $('#viz_type').change(function () { $('#query').submit(); }); @@ -386,15 +244,6 @@ function initExploreView() { addFilter(undefined, 'having'); }); - const queryAndSaveBtnsEl = document.getElementById('js-query-and-save-btns'); - ReactDOM.render( - query(true)} - />, - queryAndSaveBtnsEl - ); - function createChoices(term, data) { const filtered = $(data).filter(function () { return this.text.localeCompare(term) === 0; @@ -487,6 +336,26 @@ function initExploreView() { prepSaveDialog(); } +function initComponents() { + const queryAndSaveBtnsEl = document.getElementById('js-query-and-save-btns'); + ReactDOM.render( + query(true)} + />, + queryAndSaveBtnsEl + ); + + const exploreActionsEl = document.getElementById('js-explore-actions'); + ReactDOM.render( + , + exploreActionsEl + ); +} + $(document).ready(function () { const data = $('.slice').data('slice'); @@ -497,7 +366,10 @@ $(document).ready(function () { $('.slice').data('slice', slice); // call vis render method, which issues ajax + // calls render on the slice for the first time query(false, false); slice.bindResizeToWindowResize(); + + initComponents(); }); diff --git a/caravel/assets/javascripts/modules/caravel.js b/caravel/assets/javascripts/modules/caravel.js index 2e673aef93..c39877e162 100644 --- a/caravel/assets/javascripts/modules/caravel.js +++ b/caravel/assets/javascripts/modules/caravel.js @@ -151,19 +151,13 @@ const px = function () { .tooltip('fixTitle'); } } + if (data !== undefined) { - $('#query_container').html(data.query); + slice.viewSqlQuery = data.query; } + $('#timer').removeClass('label-warning label-danger'); $('#timer').addClass('label-success'); - $('span.view_query').removeClass('disabled'); - $('#json').click(function () { - window.location = data.json_endpoint; - }); - $('#csv').click(function () { - window.location = data.csv_endpoint; - }); - $('.btn-group.results span,a').removeAttr('disabled'); $('.query-and-save button').removeAttr('disabled'); always(data); }, @@ -195,7 +189,6 @@ const px = function () { container.show(); $('span.query').removeClass('disabled'); $('#timer').addClass('btn-danger'); - $('.btn-group.results span,a').removeAttr('disabled'); $('.query-and-save button').removeAttr('disabled'); always(data); }, diff --git a/caravel/assets/spec/javascripts/components/CopyToClipboard_spec.jsx b/caravel/assets/spec/javascripts/components/CopyToClipboard_spec.jsx new file mode 100644 index 0000000000..a5f0ee3f7f --- /dev/null +++ b/caravel/assets/spec/javascripts/components/CopyToClipboard_spec.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import CopyToClipboard from '../../../javascripts/components/CopyToClipboard'; + +describe('CopyToClipboard', () => { + const defaultProps = { + text: 'some text to copy', + }; + + it('renders', () => { + expect( + React.isValidElement() + ).to.equal(true); + }); +}); diff --git a/caravel/assets/spec/javascripts/components/ModalTrigger_spec.jsx b/caravel/assets/spec/javascripts/components/ModalTrigger_spec.jsx new file mode 100644 index 0000000000..28a35146be --- /dev/null +++ b/caravel/assets/spec/javascripts/components/ModalTrigger_spec.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import ModalTrigger from '../../../javascripts/components/ModalTrigger'; + +describe('ModalTrigger', () => { + const defaultProps = { + triggerNode: , + modalTitle: 'My Modal Title', + modalBody:
Modal Body
, + }; + + it('renders', () => { + expect( + React.isValidElement() + ).to.equal(true); + }); +}); diff --git a/caravel/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx b/caravel/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx new file mode 100644 index 0000000000..a74deb0e78 --- /dev/null +++ b/caravel/assets/spec/javascripts/explore/components/DisplayQueryButton_spec.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import DisplayQueryButton from '../../../../javascripts/explore/components/DisplayQueryButton'; + +describe('DisplayQueryButton', () => { + const defaultProps = { + slice: { + viewSqlQuery: 'sql query string', + }, + }; + + it('renders', () => { + expect(React.isValidElement()).to.equal(true); + }); +}); diff --git a/caravel/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx b/caravel/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx new file mode 100644 index 0000000000..62f73c6d37 --- /dev/null +++ b/caravel/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { shallow, mount } from 'enzyme'; +import { OverlayTrigger } from 'react-bootstrap'; + +import EmbedCodeButton from '../../../../javascripts/explore/components/EmbedCodeButton'; + +describe('EmbedCodeButton', () => { + const defaultProps = { + slice: { + data: { + standalone_endpoint: 'endpoint_url', + }, + }, + }; + + it('renders', () => { + expect(React.isValidElement()).to.equal(true); + }); + + it('renders overlay trigger', () => { + const wrapper = shallow(); + expect(wrapper.find(OverlayTrigger)).to.have.length(1); + }); + + it('returns correct embed code', () => { + const wrapper = mount(); + wrapper.setState({ + height: '1000', + width: '2000', + srcLink: 'http://localhost/endpoint_url', + }); + const embedHTML = ``; + expect(wrapper.instance().generateEmbedHTML()).to.equal(embedHTML); + }); +}); diff --git a/caravel/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx b/caravel/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx new file mode 100644 index 0000000000..f00889a34a --- /dev/null +++ b/caravel/assets/spec/javascripts/explore/components/ExploreActionButtons_spec.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { shallow } from 'enzyme'; + +import ExploreActionButtons from '../../../../javascripts/explore/components/ExploreActionButtons'; + +describe('ExploreActionButtons', () => { + const defaultProps = { + canDownload: 'True', + slice: { + data: { + csv_endpoint: '', + json_endpoint: '', + }, + }, + }; + + it('renders', () => { + expect( + React.isValidElement() + ).to.equal(true); + }); + + it('should render 5 children/buttons', () => { + const wrapper = shallow(); + expect(wrapper.children()).to.have.length(5); + }); +}); diff --git a/caravel/assets/spec/javascripts/explore/components/URLShortLinkButton_spec.jsx b/caravel/assets/spec/javascripts/explore/components/URLShortLinkButton_spec.jsx new file mode 100644 index 0000000000..f2729db116 --- /dev/null +++ b/caravel/assets/spec/javascripts/explore/components/URLShortLinkButton_spec.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import URLShortLinkButton from '../../../../javascripts/explore/components/URLShortLinkButton'; + +describe('URLShortLinkButton', () => { + const defaultProps = { + slice: { + querystring: () => 'query string', + }, + }; + + it('renders', () => { + expect(React.isValidElement()).to.equal(true); + }); +}); diff --git a/caravel/templates/caravel/explore.html b/caravel/templates/caravel/explore.html index 93f2c194fb..1ea84c5de8 100644 --- a/caravel/templates/caravel/explore.html +++ b/caravel/templates/caravel/explore.html @@ -205,29 +205,12 @@ data-toggle="tooltip"> {{ _("0 sec") }} -
- -   - - -   - - - - .json - - - .csv - - - - {{ _("query") }} - - -
+ + + @@ -245,24 +228,7 @@ - +