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
This commit is contained in:
leakingoxide
2018-12-07 19:03:33 +01:00
committed by Hugh A. Miles II
parent fc8acf27c8
commit eb408d71c4
5 changed files with 68 additions and 2 deletions

View File

@@ -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');
});
});
});

View File

@@ -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">

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;
}