URL shortner for dashboards (#4760)

* Added support for URLShortLinkButton to work for the dashboard case

* Fix lint errors and test

* Change references to 'slice' to 'chart'.

* Add unit tests to improve coverage

* Fixing lint errors

* Refactor to make URLShortLink more generic. Remove history modification code, redirect should be handling this.

* Remove history modification code, redirect should be handling this

* Generate a shorter link without the directory, and delegate default linked to the contents of window.location

* Fix lint errors

* Fix test_shortner test to check for new pattern

* Remove usage of addHistory to manipulate explore shortlink redirection

* Address build failure and using better practices for shortlink defaults

* Fixing alphabetical order

* More syntax mistakes

* Revert explore view history changes

* Fix use of component props, & rebase
This commit is contained in:
Tamika Tannis 2018-06-02 11:08:43 -07:00 committed by Hugh A. Miles II
parent cc0942ac98
commit dc21e0dd78
6 changed files with 38 additions and 20 deletions

View File

@ -4,13 +4,13 @@ import { describe, it } from 'mocha';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { OverlayTrigger } from 'react-bootstrap'; import { OverlayTrigger } from 'react-bootstrap';
import URLShortLinkButton from '../../../../src/explore/components/URLShortLinkButton'; import URLShortLinkButton from '../../../src/components/URLShortLinkButton';
describe('URLShortLinkButton', () => { describe('URLShortLinkButton', () => {
const defaultProps = { const defaultProps = {
slice: { url: 'mockURL',
querystring: () => 'query string', emailSubject: 'Mock Subject',
}, emailContent: 'mock content',
}; };
it('renders', () => { it('renders', () => {

View File

@ -1,13 +1,14 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Popover, OverlayTrigger } from 'react-bootstrap'; import { Popover, OverlayTrigger } from 'react-bootstrap';
import CopyToClipboard from './../../components/CopyToClipboard'; import CopyToClipboard from './CopyToClipboard';
import { getShortUrl } from '../../utils/common'; import { getShortUrl } from '../utils/common';
import { getExploreLongUrl } from '../exploreUtils'; import { t } from '../locales';
import { t } from '../../locales';
const propTypes = { const propTypes = {
latestQueryFormData: PropTypes.object.isRequired, url: PropTypes.string,
emailSubject: PropTypes.string,
emailContent: PropTypes.string,
}; };
export default class URLShortLinkButton extends React.Component { export default class URLShortLinkButton extends React.Component {
@ -25,12 +26,11 @@ export default class URLShortLinkButton extends React.Component {
} }
getCopyUrl() { getCopyUrl() {
const longUrl = getExploreLongUrl(this.props.latestQueryFormData); getShortUrl(this.props.url, this.onShortUrlSuccess.bind(this));
getShortUrl(longUrl, this.onShortUrlSuccess.bind(this));
} }
renderPopover() { renderPopover() {
const emailBody = t('Check out this chart: %s', this.state.shortUrl); const emailBody = t('%s%s', this.props.emailContent, this.state.shortUrl);
return ( return (
<Popover id="shorturl-popover"> <Popover id="shorturl-popover">
<CopyToClipboard <CopyToClipboard
@ -38,7 +38,7 @@ export default class URLShortLinkButton extends React.Component {
copyNode={<i className="fa fa-clipboard" title={t('Copy to clipboard')} />} copyNode={<i className="fa fa-clipboard" title={t('Copy to clipboard')} />}
/> />
&nbsp;&nbsp; &nbsp;&nbsp;
<a href={`mailto:?Subject=Superset%20Slice%20&Body=${emailBody}`}> <a href={`mailto:?Subject=${this.props.emailSubject}%20&Body=${emailBody}`}>
<i className="fa fa-envelope" /> <i className="fa fa-envelope" />
</a> </a>
</Popover> </Popover>
@ -62,4 +62,10 @@ export default class URLShortLinkButton extends React.Component {
} }
} }
URLShortLinkButton.defaultProps = {
url: window.location.href.substring(window.location.origin.length),
emailSubject: '',
emailContent: '',
};
URLShortLinkButton.propTypes = propTypes; URLShortLinkButton.propTypes = propTypes;

View File

@ -5,6 +5,7 @@ import Controls from './Controls';
import EditableTitle from '../../components/EditableTitle'; import EditableTitle from '../../components/EditableTitle';
import Button from '../../components/Button'; import Button from '../../components/Button';
import FaveStar from '../../components/FaveStar'; import FaveStar from '../../components/FaveStar';
import URLShortLinkButton from '../../components/URLShortLinkButton';
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger'; import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
import { t } from '../../locales'; import { t } from '../../locales';
@ -92,6 +93,12 @@ class Header extends React.PureComponent {
</h1> </h1>
</div> </div>
<div className="pull-right" style={{ marginTop: '35px' }}> <div className="pull-right" style={{ marginTop: '35px' }}>
<span className="m-r-5">
<URLShortLinkButton
emailSubject="Superset Dashboard"
emailContent="Check out this dashboard: "
/>
</span>
{this.renderEditButton()} {this.renderEditButton()}
<Controls <Controls
dashboard={dashboard} dashboard={dashboard}

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cx from 'classnames'; import cx from 'classnames';
import URLShortLinkButton from './URLShortLinkButton'; import URLShortLinkButton from '../../components/URLShortLinkButton';
import EmbedCodeButton from './EmbedCodeButton'; import EmbedCodeButton from './EmbedCodeButton';
import DisplayQueryButton from './DisplayQueryButton'; import DisplayQueryButton from './DisplayQueryButton';
import { t } from '../../locales'; import { t } from '../../locales';
import { exportChart } from '../exploreUtils'; import { exportChart, getExploreLongUrl } from '../exploreUtils';
const propTypes = { const propTypes = {
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired, canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
@ -25,7 +25,12 @@ export default function ExploreActionButtons({
return ( return (
<div className="btn-group results" role="group"> <div className="btn-group results" role="group">
{latestQueryFormData && {latestQueryFormData &&
<URLShortLinkButton latestQueryFormData={latestQueryFormData} />} <URLShortLinkButton
url={getExploreLongUrl(latestQueryFormData)}
emailSubject="Superset Chart"
emailContent="Check out this chart: "
/>
}
{latestQueryFormData && {latestQueryFormData &&
<EmbedCodeButton latestQueryFormData={latestQueryFormData} />} <EmbedCodeButton latestQueryFormData={latestQueryFormData} />}

View File

@ -745,13 +745,12 @@ class R(BaseSupersetView):
@expose('/shortner/', methods=['POST', 'GET']) @expose('/shortner/', methods=['POST', 'GET'])
def shortner(self): def shortner(self):
url = request.form.get('data') url = request.form.get('data')
directory = url.split('?')[0][2:]
obj = models.Url(url=url) obj = models.Url(url=url)
db.session.add(obj) db.session.add(obj)
db.session.commit() db.session.commit()
return Response( return Response(
'{scheme}://{request.headers[Host]}/{directory}?r={obj.id}'.format( '{scheme}://{request.headers[Host]}/r/{obj.id}'.format(
scheme=request.scheme, request=request, directory=directory, obj=obj), scheme=request.scheme, request=request, obj=obj),
mimetype='text/plain') mimetype='text/plain')
@expose('/msg/') @expose('/msg/')

View File

@ -13,6 +13,7 @@ import json
import logging import logging
import os import os
import random import random
import re
import string import string
import unittest import unittest
@ -377,7 +378,7 @@ class CoreTests(SupersetTestCase):
'previous_viz_type=sankey' 'previous_viz_type=sankey'
) )
resp = self.client.post('/r/shortner/', data=dict(data=data)) resp = self.client.post('/r/shortner/', data=dict(data=data))
assert '?r=' in resp.data.decode('utf-8') assert re.search(r'\/r\/[0-9]+', resp.data.decode('utf-8'))
def test_kv(self): def test_kv(self):
self.logout() self.logout()