mistercrunch closed pull request #5398: Re-add dashboard short links
URL: https://github.com/apache/incubator-superset/pull/5398
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git 
a/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx 
b/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx
new file mode 100644
index 0000000000..494d0d390a
--- /dev/null
+++ b/superset/assets/spec/javascripts/components/URLShortLinkModal_spec.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import configureStore from 'redux-mock-store';
+import { expect } from 'chai';
+import { describe, it } from 'mocha';
+import { shallow } from 'enzyme';
+
+import URLShortLinkModal from '../../../src/components/URLShortLinkModal';
+import ModalTrigger from '../../../src/components/ModalTrigger';
+
+describe('URLShortLinkModal', () => {
+  const defaultProps = {
+    url: 'mockURL',
+    emailSubject: 'Mock Subject',
+    emailContent: 'mock content',
+  };
+
+  function setup() {
+    const mockStore = configureStore([]);
+    const store = mockStore({});
+    return shallow(<URLShortLinkModal {...defaultProps} />, { context: { store 
} }).dive();
+  }
+
+  it('renders ModalTrigger', () => {
+    const wrapper = setup();
+    expect(wrapper.find(ModalTrigger)).have.length(1);
+  });
+});
diff --git 
a/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
 
b/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
new file mode 100644
index 0000000000..673118bd3b
--- /dev/null
+++ 
b/superset/assets/spec/javascripts/dashboard/components/HeaderActionsDropdown_spec.jsx
@@ -0,0 +1,141 @@
+import React from 'react';
+import { describe, it } from 'mocha';
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import { DropdownButton, MenuItem } from 'react-bootstrap';
+import RefreshIntervalModal from 
'../../../../src/dashboard/components/RefreshIntervalModal';
+import URLShortLinkModal from '../../../../src/components/URLShortLinkModal';
+import HeaderActionsDropdown from 
'../../../../src/dashboard/components/HeaderActionsDropdown';
+import SaveModal from '../../../../src/dashboard/components/SaveModal';
+import CssEditor from '../../../../src/dashboard/components/CssEditor';
+
+describe('HeaderActionsDropdown', () => {
+  const props = {
+    addSuccessToast: () => {},
+    addDangerToast: () => {},
+    dashboardId: 1,
+    dashboardTitle: 'Title',
+    hasUnsavedChanges: false,
+    css: '',
+    onChange: () => {},
+    updateCss: () => {},
+    forceRefreshAllCharts: () => {},
+    startPeriodicRender: () => {},
+    editMode: false,
+    userCanEdit: false,
+    userCanSave: false,
+    layout: {},
+    filters: {},
+    expandedSlices: {},
+    onSave: () => {},
+  };
+
+  function setup(overrideProps) {
+    const wrapper = shallow(
+      <HeaderActionsDropdown {...props} {...overrideProps} />,
+    );
+    return wrapper;
+  }
+
+  describe('readonly-user', () => {
+    const overrideProps = { userCanSave: false };
+
+    it('should render the DropdownButton', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(DropdownButton)).to.have.lengthOf(1);
+    });
+
+    it('should not render the SaveModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(SaveModal)).to.have.lengthOf(0);
+    });
+
+    it('should render one MenuItem', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(MenuItem)).to.have.lengthOf(1);
+    });
+
+    it('should render the RefreshIntervalModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(RefreshIntervalModal)).to.have.lengthOf(1);
+    });
+
+    it('should render the URLShortLinkModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(URLShortLinkModal)).to.have.lengthOf(1);
+    });
+
+    it('should not render the CssEditor', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(CssEditor)).to.have.lengthOf(0);
+    });
+  });
+
+  describe('write-user', () => {
+    const overrideProps = { userCanSave: true };
+
+    it('should render the DropdownButton', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(DropdownButton)).to.have.lengthOf(1);
+    });
+
+    it('should render the SaveModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(SaveModal)).to.have.lengthOf(1);
+    });
+
+    it('should render two MenuItems', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(MenuItem)).to.have.lengthOf(2);
+    });
+
+    it('should render the RefreshIntervalModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(RefreshIntervalModal)).to.have.lengthOf(1);
+    });
+
+    it('should render the URLShortLinkModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(URLShortLinkModal)).to.have.lengthOf(1);
+    });
+
+    it('should not render the CssEditor', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(CssEditor)).to.have.lengthOf(0);
+    });
+  });
+
+  describe('write-user-with-edit-mode', () => {
+    const overrideProps = { userCanSave: true, editMode: true };
+
+    it('should render the DropdownButton', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(DropdownButton)).to.have.lengthOf(1);
+    });
+
+    it('should render the SaveModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(SaveModal)).to.have.lengthOf(1);
+    });
+
+    it('should render three MenuItems', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(MenuItem)).to.have.lengthOf(3);
+    });
+
+    it('should render the RefreshIntervalModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(RefreshIntervalModal)).to.have.lengthOf(1);
+    });
+
+    it('should render the URLShortLinkModal', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(URLShortLinkModal)).to.have.lengthOf(1);
+    });
+
+    it('should render the CssEditor', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(CssEditor)).to.have.lengthOf(1);
+    });
+  });
+});
diff --git 
a/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx 
b/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx
new file mode 100644
index 0000000000..e7ecfc142c
--- /dev/null
+++ b/superset/assets/spec/javascripts/dashboard/components/Header_spec.jsx
@@ -0,0 +1,147 @@
+import React from 'react';
+import { describe, it } from 'mocha';
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import Header from '../../../../src/dashboard/components/Header';
+import EditableTitle from '../../../../src/components/EditableTitle';
+import FaveStar from '../../../../src/components/FaveStar';
+import HeaderActionsDropdown from 
'../../../../src/dashboard/components/HeaderActionsDropdown';
+import Button from '../../../../src/components/Button';
+import UndoRedoKeylisteners from 
'../../../../src/dashboard/components/UndoRedoKeylisteners';
+
+describe('Header', () => {
+  const props = {
+    addSuccessToast: () => {},
+    addDangerToast: () => {},
+    dashboardInfo: { id: 1, dash_edit_perm: true, dash_save_perm: true },
+    dashboardTitle: 'title',
+    charts: {},
+    layout: {},
+    filters: {},
+    expandedSlices: {},
+    css: '',
+    isStarred: false,
+    onSave: () => {},
+    onChange: () => {},
+    fetchFaveStar: () => {},
+    fetchCharts: () => {},
+    saveFaveStar: () => {},
+    startPeriodicRender: () => {},
+    updateDashboardTitle: () => {},
+    editMode: false,
+    setEditMode: () => {},
+    showBuilderPane: false,
+    toggleBuilderPane: () => {},
+    updateCss: () => {},
+    hasUnsavedChanges: false,
+    maxUndoHistoryExceeded: false,
+
+    // redux
+    onUndo: () => {},
+    onRedo: () => {},
+    undoLength: 0,
+    redoLength: 0,
+    setMaxUndoHistoryExceeded: () => {},
+    maxUndoHistoryToast: () => {},
+  };
+
+  function setup(overrideProps) {
+    const wrapper = shallow(<Header {...props} {...overrideProps} />);
+    return wrapper;
+  }
+
+  describe('read-only-user', () => {
+    const overrideProps = {
+      dashboardInfo: { id: 1, dash_edit_perm: false, dash_save_perm: false },
+    };
+
+    it('should render the EditableTitle', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(EditableTitle)).to.have.lengthOf(1);
+    });
+
+    it('should render the FaveStar', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(FaveStar)).to.have.lengthOf(1);
+    });
+
+    it('should render the HeaderActionsDropdown', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(HeaderActionsDropdown)).to.have.lengthOf(1);
+    });
+
+    it('should render one Button', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(Button)).to.have.lengthOf(1);
+    });
+
+    it('should not set up undo/redo', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(UndoRedoKeylisteners)).to.have.lengthOf(0);
+    });
+  });
+
+  describe('write-user', () => {
+    const overrideProps = {
+      editMode: false,
+      dashboardInfo: { id: 1, dash_edit_perm: true, dash_save_perm: true },
+    };
+
+    it('should render the EditableTitle', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(EditableTitle)).to.have.lengthOf(1);
+    });
+
+    it('should render the FaveStar', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(FaveStar)).to.have.lengthOf(1);
+    });
+
+    it('should render the HeaderActionsDropdown', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(HeaderActionsDropdown)).to.have.lengthOf(1);
+    });
+
+    it('should render one Button', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(Button)).to.have.lengthOf(1);
+    });
+
+    it('should not set up undo/redo', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(UndoRedoKeylisteners)).to.have.lengthOf(0);
+    });
+  });
+
+  describe('write-user-with-edit-mode', () => {
+    const overrideProps = {
+      editMode: true,
+      dashboardInfo: { id: 1, dash_edit_perm: true, dash_save_perm: true },
+    };
+
+    it('should render the EditableTitle', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(EditableTitle)).to.have.lengthOf(1);
+    });
+
+    it('should render the FaveStar', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(FaveStar)).to.have.lengthOf(1);
+    });
+
+    it('should render the HeaderActionsDropdown', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(HeaderActionsDropdown)).to.have.lengthOf(1);
+    });
+
+    it('should render four Buttons', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(Button)).to.have.lengthOf(4);
+    });
+
+    it('should set up undo/redo', () => {
+      const wrapper = setup(overrideProps);
+      expect(wrapper.find(UndoRedoKeylisteners)).to.have.lengthOf(1);
+    });
+  });
+});
diff --git 
a/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js 
b/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
new file mode 100644
index 0000000000..c45e65a02a
--- /dev/null
+++ b/superset/assets/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
@@ -0,0 +1,14 @@
+import { describe, it } from 'mocha';
+import { expect } from 'chai';
+
+import getDashboardUrl from '../../../../src/dashboard/util/getDashboardUrl';
+
+describe('getChartIdsFromLayout', () => {
+  it('should encode filters', () => {
+    const filters = { 35: { key: ['value'] } };
+    const url = getDashboardUrl('path', filters);
+    expect(url).to.equal(
+      
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
+    );
+  });
+});
diff --git a/superset/assets/src/components/URLShortLinkButton.jsx 
b/superset/assets/src/components/URLShortLinkButton.jsx
index 1efd4f7122..19c77ab332 100644
--- a/superset/assets/src/components/URLShortLinkButton.jsx
+++ b/superset/assets/src/components/URLShortLinkButton.jsx
@@ -20,6 +20,7 @@ class URLShortLinkButton extends React.Component {
       shortUrl: '',
     };
     this.onShortUrlSuccess = this.onShortUrlSuccess.bind(this);
+    this.getCopyUrl = this.getCopyUrl.bind(this);
   }
 
   onShortUrlSuccess(data) {
@@ -54,7 +55,7 @@ class URLShortLinkButton extends React.Component {
         trigger="click"
         rootClose
         placement="left"
-        onEnter={this.getCopyUrl.bind(this)}
+        onEnter={this.getCopyUrl}
         overlay={this.renderPopover()}
       >
         <span className="btn btn-default btn-sm">
diff --git a/superset/assets/src/components/URLShortLinkModal.jsx 
b/superset/assets/src/components/URLShortLinkModal.jsx
new file mode 100644
index 0000000000..9f7a36bce4
--- /dev/null
+++ b/superset/assets/src/components/URLShortLinkModal.jsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import CopyToClipboard from './CopyToClipboard';
+import { getShortUrl } from '../utils/common';
+import { t } from '../locales';
+import withToasts from '../messageToasts/enhancers/withToasts';
+import ModalTrigger from './ModalTrigger';
+
+const propTypes = {
+  url: PropTypes.string,
+  emailSubject: PropTypes.string,
+  emailContent: PropTypes.string,
+  addDangerToast: PropTypes.func.isRequired,
+  isMenuItem: PropTypes.bool,
+  triggerNode: PropTypes.node.isRequired,
+};
+
+class URLShortLinkModal extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      shortUrl: '',
+    };
+    this.modal = null;
+    this.setModalRef = this.setModalRef.bind(this);
+    this.onShortUrlSuccess = this.onShortUrlSuccess.bind(this);
+    this.getCopyUrl = this.getCopyUrl.bind(this);
+  }
+
+  onShortUrlSuccess(data) {
+    this.setState({
+      shortUrl: data,
+    });
+  }
+
+  setModalRef(ref) {
+    this.modal = ref;
+  }
+
+  getCopyUrl() {
+    getShortUrl(this.props.url, this.onShortUrlSuccess, 
this.props.addDangerToast);
+  }
+
+  render() {
+    const emailBody = t('%s%s', this.props.emailContent, this.state.shortUrl);
+    return (
+      <ModalTrigger
+        ref={this.setModalRef}
+        isMenuItem={this.props.isMenuItem}
+        triggerNode={this.props.triggerNode}
+        beforeOpen={this.getCopyUrl}
+        modalTitle={t('Share Dashboard')}
+        modalBody={
+          <div>
+            <CopyToClipboard
+              text={this.state.shortUrl}
+              copyNode={<i className="fa fa-clipboard" title={t('Copy to 
clipboard')} />}
+            />
+            &nbsp;&nbsp;
+            <a 
href={`mailto:?Subject=${this.props.emailSubject}%20&Body=${emailBody}`}>
+              <i className="fa fa-envelope" />
+            </a>
+          </div>
+        }
+      />
+    );
+  }
+}
+
+URLShortLinkModal.defaultProps = {
+  url: window.location.href.substring(window.location.origin.length),
+  emailSubject: '',
+  emailContent: '',
+};
+
+URLShortLinkModal.propTypes = propTypes;
+
+export default withToasts(URLShortLinkModal);
diff --git a/superset/assets/src/dashboard/components/Header.jsx 
b/superset/assets/src/dashboard/components/Header.jsx
index 0c1951b8d7..9f976cbab9 100644
--- a/superset/assets/src/dashboard/components/Header.jsx
+++ b/superset/assets/src/dashboard/components/Header.jsx
@@ -189,100 +189,103 @@ class Header extends React.PureComponent {
           </span>
         </div>
 
-        {userCanSaveAs && (
-          <div className="button-container">
-            {editMode && (
-              <Button
-                bsSize="small"
-                onClick={onUndo}
-                disabled={undoLength < 1}
-                bsStyle={this.state.emphasizeUndo ? 'primary' : undefined}
-              >
-                <div title="Undo" className="undo-action fa fa-reply" />
-              </Button>
-            )}
-
-            {editMode && (
-              <Button
-                bsSize="small"
-                onClick={onRedo}
-                disabled={redoLength < 1}
-                bsStyle={this.state.emphasizeRedo ? 'primary' : undefined}
-              >
-                <div title="Redo" className="redo-action fa fa-share" />
-              </Button>
-            )}
-
-            {editMode && (
-              <Button bsSize="small" onClick={this.props.toggleBuilderPane}>
-                {showBuilderPane
-                  ? t('Hide components')
-                  : t('Insert components')}
-              </Button>
-            )}
-
-            {editMode &&
-              hasUnsavedChanges && (
+        <div className="button-container">
+          {userCanSaveAs && (
+            <div className="button-container">
+              {editMode && (
                 <Button
                   bsSize="small"
-                  bsStyle={popButton ? 'primary' : undefined}
-                  onClick={this.overwriteDashboard}
+                  onClick={onUndo}
+                  disabled={undoLength < 1}
+                  bsStyle={this.state.emphasizeUndo ? 'primary' : undefined}
                 >
-                  {t('Save changes')}
+                  <div title="Undo" className="undo-action fa fa-reply" />
                 </Button>
               )}
 
-            {!editMode &&
-              !hasUnsavedChanges && (
+              {editMode && (
                 <Button
                   bsSize="small"
-                  onClick={this.toggleEditMode}
-                  bsStyle={popButton ? 'primary' : undefined}
-                  disabled={!userCanEdit}
+                  onClick={onRedo}
+                  disabled={redoLength < 1}
+                  bsStyle={this.state.emphasizeRedo ? 'primary' : undefined}
                 >
-                  {t('Edit dashboard')}
+                  <div title="Redo" className="redo-action fa fa-share" />
                 </Button>
               )}
 
-            {editMode &&
-              !hasUnsavedChanges && (
-                <Button
-                  bsSize="small"
-                  onClick={this.toggleEditMode}
-                  bsStyle={undefined}
-                  disabled={!userCanEdit}
-                >
-                  {t('Switch to view mode')}
+              {editMode && (
+                <Button bsSize="small" onClick={this.props.toggleBuilderPane}>
+                  {showBuilderPane
+                    ? t('Hide components')
+                    : t('Insert components')}
                 </Button>
               )}
 
-            <HeaderActionsDropdown
-              addSuccessToast={this.props.addSuccessToast}
-              addDangerToast={this.props.addDangerToast}
-              dashboardId={dashboardInfo.id}
-              dashboardTitle={dashboardTitle}
-              layout={layout}
-              filters={filters}
-              expandedSlices={expandedSlices}
-              css={css}
-              onSave={onSave}
-              onChange={onChange}
-              forceRefreshAllCharts={this.forceRefresh}
-              startPeriodicRender={this.props.startPeriodicRender}
-              updateCss={updateCss}
-              editMode={editMode}
-              hasUnsavedChanges={hasUnsavedChanges}
-              userCanEdit={userCanEdit}
-            />
+              {editMode &&
+                hasUnsavedChanges && (
+                  <Button
+                    bsSize="small"
+                    bsStyle={popButton ? 'primary' : undefined}
+                    onClick={this.overwriteDashboard}
+                  >
+                    {t('Save changes')}
+                  </Button>
+                )}
+
+              {editMode &&
+                !hasUnsavedChanges && (
+                  <Button
+                    bsSize="small"
+                    onClick={this.toggleEditMode}
+                    bsStyle={undefined}
+                    disabled={!userCanEdit}
+                  >
+                    {t('Switch to view mode')}
+                  </Button>
+                )}
+
+              {editMode && (
+                <UndoRedoKeylisteners
+                  onUndo={this.handleCtrlZ}
+                  onRedo={this.handleCtrlY}
+                />
+              )}
+            </div>
+          )}
 
-            {editMode && (
-              <UndoRedoKeylisteners
-                onUndo={this.handleCtrlZ}
-                onRedo={this.handleCtrlY}
-              />
+          {!editMode &&
+            !hasUnsavedChanges && (
+              <Button
+                bsSize="small"
+                onClick={this.toggleEditMode}
+                bsStyle={popButton ? 'primary' : undefined}
+                disabled={!userCanEdit}
+              >
+                {t('Edit dashboard')}
+              </Button>
             )}
-          </div>
-        )}
+
+          <HeaderActionsDropdown
+            addSuccessToast={this.props.addSuccessToast}
+            addDangerToast={this.props.addDangerToast}
+            dashboardId={dashboardInfo.id}
+            dashboardTitle={dashboardTitle}
+            layout={layout}
+            filters={filters}
+            expandedSlices={expandedSlices}
+            css={css}
+            onSave={onSave}
+            onChange={onChange}
+            forceRefreshAllCharts={this.forceRefresh}
+            startPeriodicRender={this.props.startPeriodicRender}
+            updateCss={updateCss}
+            editMode={editMode}
+            hasUnsavedChanges={hasUnsavedChanges}
+            userCanEdit={userCanEdit}
+            userCanSave={userCanSaveAs}
+          />
+        </div>
       </div>
     );
   }
diff --git a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx 
b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
index dab11c382f..b5e5d022b9 100644
--- a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
+++ b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
@@ -10,6 +10,8 @@ import SaveModal from './SaveModal';
 import injectCustomCss from '../util/injectCustomCss';
 import { SAVE_TYPE_NEWDASHBOARD } from '../util/constants';
 import { t } from '../../locales';
+import URLShortLinkModal from '../../components/URLShortLinkModal';
+import getDashboardUrl from '../util/getDashboardUrl';
 
 const propTypes = {
   addSuccessToast: PropTypes.func.isRequired,
@@ -24,6 +26,7 @@ const propTypes = {
   startPeriodicRender: PropTypes.func.isRequired,
   editMode: PropTypes.bool.isRequired,
   userCanEdit: PropTypes.bool.isRequired,
+  userCanSave: PropTypes.bool.isRequired,
   layout: PropTypes.object.isRequired,
   filters: PropTypes.object.isRequired,
   expandedSlices: PropTypes.object.isRequired,
@@ -82,10 +85,12 @@ class HeaderActionsDropdown extends React.PureComponent {
       expandedSlices,
       onSave,
       userCanEdit,
+      userCanSave,
     } = this.props;
 
-    const emailBody = t('Check out this dashboard: %s', window.location.href);
-    const emailLink = 
`mailto:?Subject=Superset%20Dashboard%20${dashboardTitle}&Body=${emailBody}`;
+    const emailTitle = t('Superset Dashboard');
+    const emailSubject = `${emailTitle} ${dashboardTitle}`;
+    const emailBody = t('Check out this dashboard: ');
 
     return (
       <DropdownButton
@@ -95,31 +100,37 @@ class HeaderActionsDropdown extends React.PureComponent {
         bsSize="small"
         pullRight
       >
-        <SaveModal
-          addSuccessToast={this.props.addSuccessToast}
-          addDangerToast={this.props.addDangerToast}
-          dashboardId={dashboardId}
-          dashboardTitle={dashboardTitle}
-          saveType={SAVE_TYPE_NEWDASHBOARD}
-          layout={layout}
-          filters={filters}
-          expandedSlices={expandedSlices}
-          css={css}
-          onSave={onSave}
-          isMenuItem
-          triggerNode={<span>{t('Save as')}</span>}
-          canOverwrite={userCanEdit}
-        />
-        {hasUnsavedChanges && (
-          <MenuItem
-            eventKey="discard"
-            onSelect={HeaderActionsDropdown.discardChanges}
-          >
-            {t('Discard changes')}
-          </MenuItem>
+        {userCanSave && (
+          <SaveModal
+            addSuccessToast={this.props.addSuccessToast}
+            addDangerToast={this.props.addDangerToast}
+            dashboardId={dashboardId}
+            dashboardTitle={dashboardTitle}
+            saveType={SAVE_TYPE_NEWDASHBOARD}
+            layout={layout}
+            filters={filters}
+            expandedSlices={expandedSlices}
+            css={css}
+            onSave={onSave}
+            isMenuItem
+            triggerNode={<span>{t('Save as')}</span>}
+            canOverwrite={userCanEdit}
+          />
         )}
 
-        <MenuItem divider />
+        {hasUnsavedChanges &&
+          userCanSave && (
+            <div>
+              <MenuItem
+                eventKey="discard"
+                onSelect={HeaderActionsDropdown.discardChanges}
+              >
+                {t('Discard changes')}
+              </MenuItem>
+            </div>
+          )}
+
+        {userCanSave && <MenuItem divider />}
 
         <MenuItem onClick={forceRefreshAllCharts}>
           {t('Force refresh dashboard')}
@@ -138,9 +149,16 @@ class HeaderActionsDropdown extends React.PureComponent {
             {t('Edit dashboard metadata')}
           </MenuItem>
         )}
-        {editMode && (
-          <MenuItem href={emailLink}>{t('Email dashboard link')}</MenuItem>
-        )}
+
+        <URLShortLinkModal
+          url={getDashboardUrl(window.location.pathname, this.props.filters)}
+          emailSubject={emailSubject}
+          emailContent={emailBody}
+          addDangerToast={this.props.addDangerToast}
+          isMenuItem
+          triggerNode={<span>{t('Share dashboard')}</span>}
+        />
+
         {editMode && (
           <CssEditor
             triggerNode={<span>{t('Edit CSS')}</span>}
diff --git a/superset/assets/src/dashboard/util/getDashboardUrl.js 
b/superset/assets/src/dashboard/util/getDashboardUrl.js
new file mode 100644
index 0000000000..d26ca90330
--- /dev/null
+++ b/superset/assets/src/dashboard/util/getDashboardUrl.js
@@ -0,0 +1,6 @@
+/* eslint camelcase: 0 */
+
+export default function getDashboardUrl(pathname, filters = {}) {
+  const preselect_filters = encodeURIComponent(JSON.stringify(filters));
+  return `${pathname}?preselect_filters=${preselect_filters}`;
+}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to