This is an automated email from the ASF dual-hosted git repository.
hugh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push:
new eb408d7 Add copy to clipboard buttons in explore and sqllab (#6461)
eb408d7 is described below
commit eb408d71c4e52d6b6d9fe93ff1cced91ef45f6e5
Author: leakingoxide <[email protected]>
AuthorDate: Fri Dec 7 19:03:33 2018 +0100
Add copy to clipboard buttons in explore and sqllab (#6461)
* Add copy to clipboard buttons in explore and sqllab
* Eslint fixes
* Review changes: deconstruct props, extract function to utils, add tests
---
.../assets/spec/javascripts/utils/common_spec.jsx | 15 ++++++++++++-
.../assets/src/SqlLab/components/ResultSet.jsx | 12 +++++++++++
superset/assets/src/components/CopyToClipboard.jsx | 25 +++++++++++++++++++++-
.../src/explore/components/DisplayQueryButton.jsx | 10 +++++++++
superset/assets/src/utils/common.js | 8 +++++++
5 files changed, 68 insertions(+), 2 deletions(-)
diff --git a/superset/assets/spec/javascripts/utils/common_spec.jsx
b/superset/assets/spec/javascripts/utils/common_spec.jsx
index b6fc39f..eabb69e 100644
--- a/superset/assets/spec/javascripts/utils/common_spec.jsx
+++ b/superset/assets/spec/javascripts/utils/common_spec.jsx
@@ -1,4 +1,4 @@
-import { isTruthy, optionFromValue } from '../../../src/utils/common';
+import { isTruthy, optionFromValue, prepareCopyToClipboardTabularData } from
'../../../src/utils/common';
describe('utils/common', () => {
describe('isTruthy', () => {
@@ -48,4 +48,17 @@ describe('utils/common', () => {
expect(optionFromValue(5)).toEqual({ value: 5, label: '5' });
});
});
+ describe('prepareCopyToClipboardTabularData', () => {
+ it('converts empty array', () => {
+ const array = [];
+ expect(prepareCopyToClipboardTabularData(array)).toEqual('');
+ });
+ it('converts non empty array', () => {
+ const array = [
+ { column1: 'lorem', column2: 'ipsum' },
+ { column1: 'dolor', column2: 'sit', column3: 'amet' },
+ ];
+
expect(prepareCopyToClipboardTabularData(array)).toEqual('lorem\tipsum\ndolor\tsit\tamet\n');
+ });
+ });
});
diff --git a/superset/assets/src/SqlLab/components/ResultSet.jsx
b/superset/assets/src/SqlLab/components/ResultSet.jsx
index c7c10cd..a9416d4 100644
--- a/superset/assets/src/SqlLab/components/ResultSet.jsx
+++ b/superset/assets/src/SqlLab/components/ResultSet.jsx
@@ -9,6 +9,8 @@ import ExploreResultsButton from './ExploreResultsButton';
import HighlightedSql from './HighlightedSql';
import FilterableTable from '../../components/FilterableTable/FilterableTable';
import QueryStateLabel from './QueryStateLabel';
+import CopyToClipboard from '../../components/CopyToClipboard';
+import { prepareCopyToClipboardTabularData } from '../../utils/common';
const propTypes = {
actions: PropTypes.object,
@@ -112,6 +114,16 @@ export default class ResultSet extends React.PureComponent
{
<Button bsSize="small" href={'/superset/csv/' +
this.props.query.id}>
<i className="fa fa-file-text-o" /> {t('.CSV')}
</Button>}
+
+ <CopyToClipboard
+
text={prepareCopyToClipboardTabularData(this.props.query.results.data)}
+ wrapped={false}
+ copyNode={
+ <Button bsSize="small">
+ <i className="fa fa-clipboard" /> {t('Clipboard')}
+ </Button>
+ }
+ />
</ButtonGroup>
</div>
<div className="pull-right">
diff --git a/superset/assets/src/components/CopyToClipboard.jsx
b/superset/assets/src/components/CopyToClipboard.jsx
index 199e8d4..433ebcf 100644
--- a/superset/assets/src/components/CopyToClipboard.jsx
+++ b/superset/assets/src/components/CopyToClipboard.jsx
@@ -10,6 +10,7 @@ const propTypes = {
shouldShowText: PropTypes.bool,
text: PropTypes.string,
inMenu: PropTypes.bool,
+ wrapped: PropTypes.bool,
tooltipText: PropTypes.string,
};
@@ -18,6 +19,7 @@ const defaultProps = {
onCopyEnd: () => {},
shouldShowText: true,
inMenu: false,
+ wrapped: true,
tooltipText: t('Copy to clipboard'),
};
@@ -94,6 +96,23 @@ export default class CopyToClipboard extends React.Component
{
return this.props.tooltipText;
}
+ renderNotWrapped() {
+ const { copyNode } = this.props;
+ return (
+ <OverlayTrigger
+ placement="top"
+ style={{ cursor: 'pointer' }}
+ overlay={this.renderTooltip()}
+ trigger={['hover']}
+ bsStyle="link"
+ onClick={this.onClick}
+ onMouseOut={this.onMouseOut}
+ >
+ {copyNode}
+ </OverlayTrigger>
+ );
+ }
+
renderLink() {
return (
<span>
@@ -139,7 +158,11 @@ export default class CopyToClipboard extends
React.Component {
}
render() {
- return this.props.inMenu ? this.renderInMenu() : this.renderLink();
+ const { wrapped, inMenu } = this.props;
+ if (!wrapped) {
+ return this.renderNotWrapped();
+ }
+ return inMenu ? this.renderInMenu() : this.renderLink();
}
}
diff --git a/superset/assets/src/explore/components/DisplayQueryButton.jsx
b/superset/assets/src/explore/components/DisplayQueryButton.jsx
index 042ca24..1497e50 100644
--- a/superset/assets/src/explore/components/DisplayQueryButton.jsx
+++ b/superset/assets/src/explore/components/DisplayQueryButton.jsx
@@ -19,6 +19,7 @@ import Loading from '../../components/Loading';
import ModalTrigger from './../../components/ModalTrigger';
import Button from '../../components/Button';
import RowCountLabel from './RowCountLabel';
+import { prepareCopyToClipboardTabularData } from '../../utils/common';
registerLanguage('markdown', markdownSyntax);
registerLanguage('html', htmlSyntax);
@@ -130,6 +131,15 @@ export default class DisplayQueryButton extends
React.PureComponent {
<Row>
<Col md={9}>
<RowCountLabel rowcount={data.length} suffix={t('rows retrieved')}
/>
+ <CopyToClipboard
+ text={prepareCopyToClipboardTabularData(data)}
+ wrapped={false}
+ copyNode={
+ <Button style={{ padding: '2px 10px', fontSize: '11px' }}>
+ <i className="fa fa-clipboard" />
+ </Button>
+ }
+ />
</Col>
<Col md={3}>
<FormControl
diff --git a/superset/assets/src/utils/common.js
b/superset/assets/src/utils/common.js
index 93870c2..89b72e8 100644
--- a/superset/assets/src/utils/common.js
+++ b/superset/assets/src/utils/common.js
@@ -100,3 +100,11 @@ export function optionFromValue(opt) {
// From a list of options, handles special values & labels
return { value: optionValue(opt), label: optionLabel(opt) };
}
+
+export function prepareCopyToClipboardTabularData(data) {
+ let result = '';
+ for (let i = 0; i < data.length; ++i) {
+ result += Object.values(data[i]).join('\t') + '\n';
+ }
+ return result;
+}