You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2021/07/27 22:10:48 UTC

[superset] branch selfSubscribeReports updated: feat: add active email toggle and dropdown on dashboard (#15852)

This is an automated email from the ASF dual-hosted git repository.

elizabeth pushed a commit to branch selfSubscribeReports
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/selfSubscribeReports by this push:
     new 4d5f17d  feat: add active email toggle and dropdown on dashboard (#15852)
4d5f17d is described below

commit 4d5f17d6e1af382f048114d0247b19dfdc946eaf
Author: AAfghahi <48...@users.noreply.github.com>
AuthorDate: Tue Jul 27 18:09:48 2021 -0400

    feat: add active email toggle and dropdown on dashboard (#15852)
    
    * first draft
    
    * added testing props
    
    * created Api call
    
    * added click logic
    
    * made the fetch report into a action/reducer
    
    * abstracted report action
    
    * first commit
    
    * toggle started
    
    * on blue
    
    * event listener
    
    * toggle
    
    * toggle active + dropdown
    
    * fixed cypress tests
    
    * revisions
    
    * removed useState
    
    * added toggle active to state
---
 superset-frontend/src/common/components/index.tsx  |  8 +-
 .../HeaderReportActionsDropdown/index.tsx          | 99 ++++++++++++++++++++++
 .../src/dashboard/components/Header/index.jsx      | 41 +++++----
 .../src/dashboard/containers/DashboardHeader.jsx   |  6 +-
 .../src/reports/actions/reportState.js             | 48 ++++++++++-
 .../src/reports/reducers/reportState.js            | 28 +++++-
 6 files changed, 206 insertions(+), 24 deletions(-)

diff --git a/superset-frontend/src/common/components/index.tsx b/superset-frontend/src/common/components/index.tsx
index 58b9059..a509a73 100644
--- a/superset-frontend/src/common/components/index.tsx
+++ b/superset-frontend/src/common/components/index.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React from 'react';
+import React, { RefObject } from 'react';
 import { styled } from '@superset-ui/core';
 import {
   Dropdown,
@@ -217,6 +217,10 @@ export const MainNav = Object.assign(StyledNav, {
   ItemGroup: AntdMenu.ItemGroup,
 });
 
+interface ExtendedDropDownProps extends DropDownProps {
+  ref?: RefObject<HTMLDivElement>;
+}
+
 export const Input = styled(AntdInput)`
   border: 1px solid ${({ theme }) => theme.colors.secondary.light3};
   border-radius: ${({ theme }) => theme.borderRadius}px;
@@ -233,7 +237,7 @@ export const TextArea = styled(AntdInput.TextArea)`
 `;
 
 export const NoAnimationDropdown = (
-  props: DropDownProps & { children?: React.ReactNode },
+  props: ExtendedDropDownProps & { children?: React.ReactNode },
 ) => (
   <Dropdown
     overlayStyle={{ zIndex: 4000, animationDuration: '0s' }}
diff --git a/superset-frontend/src/components/ReportModal/HeaderReportActionsDropdown/index.tsx b/superset-frontend/src/components/ReportModal/HeaderReportActionsDropdown/index.tsx
new file mode 100644
index 0000000..5018729
--- /dev/null
+++ b/superset-frontend/src/components/ReportModal/HeaderReportActionsDropdown/index.tsx
@@ -0,0 +1,99 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { t } from '@superset-ui/core';
+import Icons from 'src/components/Icons';
+import { Switch } from 'src/components/Switch';
+import { AlertObject } from 'src/views/CRUD/alert/types';
+import { Menu, NoAnimationDropdown } from 'src/common/components';
+// function useOnClickOutside(ref: any, handler: any) {
+//   useEffect(() => {
+//     const listener = (event: any) => {
+//       if (ref.current || ref.current.contains(event.target)) {
+//         handler(event);
+//       }
+//     };
+//     document.addEventListener('mousedown', listener, { capture: true });
+//     document.addEventListener('touchstart', listener, { capture: true });
+//     return () => {
+//       document.removeEventListener('mousedown', listener, { capture: true });
+//       document.removeEventListener('touchstart', listener, { capture: true });
+//     };
+//   }, [ref, handler]);
+// }
+
+export default function HeaderReportActionsDropDown({
+  // showReportModal,
+  // hideReportModal,
+  // addDangerToast,
+  toggleActive,
+}: {
+  showReportModal: () => void;
+  hideReportModal: () => void;
+  // addDangerToast: () => void;
+  toggleActive: (data: AlertObject, checked: boolean) => void;
+}) {
+  // const ref: any = useRef();
+  const report = useSelector<any, AlertObject>(
+    state => state.reportState.report.result[0],
+  );
+
+  const toggleActiveKey = async (data: AlertObject, checked: boolean) => {
+    if (data?.id) {
+      toggleActive(data, checked);
+    }
+  };
+
+  const menu = () => (
+    <Menu selectable={false}>
+      <Menu.Item>
+        {t('Email reports active')}
+        <Switch
+          data-test="toggle-active"
+          checked={report.active}
+          onClick={(checked: boolean) => toggleActiveKey(report, checked)}
+          size="small"
+        />
+      </Menu.Item>
+      <Menu.Item>
+        <div role="button" tabIndex={0}>
+          {' '}
+          {t('Edit email report')}
+        </div>
+      </Menu.Item>
+      <Menu.Item>{t('Delete email report')}</Menu.Item>
+    </Menu>
+  );
+
+  return (
+    <NoAnimationDropdown
+      // ref={ref}
+      overlay={menu()}
+      trigger={['click']}
+      getPopupContainer={(triggerNode: any) =>
+        triggerNode.closest('.action-button')
+      }
+    >
+      <span role="button" className="action-button" tabIndex={0}>
+        <Icons.Calendar />
+      </span>
+    </NoAnimationDropdown>
+  );
+}
diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx b/superset-frontend/src/dashboard/components/Header/index.jsx
index 5d0d546..70ac4db 100644
--- a/superset-frontend/src/dashboard/components/Header/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/index.jsx
@@ -35,6 +35,7 @@ import EditableTitle from 'src/components/EditableTitle';
 import FaveStar from 'src/components/FaveStar';
 import { safeStringify } from 'src/utils/safeStringify';
 import HeaderActionsDropdown from 'src/dashboard/components/Header/HeaderActionsDropdown';
+import HeaderReportActionsDropdown from 'src/components/ReportModal/HeaderReportActionsDropdown';
 import PublishedStatus from 'src/dashboard/components/PublishedStatus';
 import UndoRedoKeyListeners from 'src/dashboard/components/UndoRedoKeyListeners';
 import PropertiesModal from 'src/dashboard/components/PropertiesModal';
@@ -159,7 +160,7 @@ class Header extends React.PureComponent {
     this.hidePropertiesModal = this.hidePropertiesModal.bind(this);
     this.showReportModal = this.showReportModal.bind(this);
     this.hideReportModal = this.hideReportModal.bind(this);
-    this.handleReportClick = this.handleReportClick.bind(this);
+    this.renderReportModal = this.renderReportModal.bind(this);
   }
 
   componentDidMount() {
@@ -379,11 +380,27 @@ class Header extends React.PureComponent {
     this.setState({ showingReportModal: false });
   }
 
-  handleReportClick() {
+  renderReportModal() {
     const attachedReportExists = this.props.report?.count > 0;
-    if (!attachedReportExists) {
-      this.showReportModal();
-    }
+    return attachedReportExists ? (
+      <HeaderReportActionsDropdown
+        showReportModal={this.showReportModal}
+        hideReportModal={this.hideReportModal}
+        toggleActive={this.props.toggleActive}
+      />
+    ) : (
+      <>
+        <span
+          role="button"
+          title={t('Schedule email report')}
+          tabIndex={0}
+          className="action-button"
+          onClick={this.showReportModal}
+        >
+          <Icons.Calendar />
+        </span>
+      </>
+    );
   }
 
   canAddReports() {
@@ -548,19 +565,7 @@ class Header extends React.PureComponent {
               </span>
             </>
           )}
-          {shouldShowReport && (
-            <>
-              <span
-                role="button"
-                title={t('Schedule email report')}
-                tabIndex={0}
-                className="action-button"
-                onClick={this.handleReportClick}
-              >
-                <Icons.Calendar />
-              </span>
-            </>
-          )}
+          {shouldShowReport && this.renderReportModal()}
 
           {this.state.showingPropertiesModal && (
             <PropertiesModal
diff --git a/superset-frontend/src/dashboard/containers/DashboardHeader.jsx b/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
index c57fceb..82e5ee7 100644
--- a/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
@@ -56,7 +56,10 @@ import {
 
 import { logEvent } from '../../logger/actions';
 import { DASHBOARD_HEADER_ID } from '../util/constants';
-import { fetchUISpecificReport } from '../../reports/actions/reportState';
+import {
+  fetchUISpecificReport,
+  toggleActive,
+} from '../../reports/actions/reportState';
 
 function mapStateToProps({
   dashboardLayout: undoableLayout,
@@ -127,6 +130,7 @@ function mapDispatchToProps(dispatch) {
       dashboardTitleChanged,
       updateDataMask,
       fetchUISpecificReport,
+      toggleActive,
     },
     dispatch,
   );
diff --git a/superset-frontend/src/reports/actions/reportState.js b/superset-frontend/src/reports/actions/reportState.js
index 847e677..8bed099 100644
--- a/superset-frontend/src/reports/actions/reportState.js
+++ b/superset-frontend/src/reports/actions/reportState.js
@@ -25,16 +25,17 @@ export const SET_REPORT = 'SET_REPORT';
 export function setReport(report) {
   return { type: SET_REPORT, report };
 }
+
 export function fetchUISpecificReport(
   userId,
-  search_field,
+  filter_field,
   creation_method,
   dashboardId,
 ) {
   const queryParams = rison.encode({
     filters: [
       {
-        col: search_field,
+        col: filter_field,
         opr: 'eq',
         value: dashboardId,
       },
@@ -68,3 +69,46 @@ export function fetchUISpecificReport(
       );
   };
 }
+
+export const EDIT_REPORT = 'EDIT_REPORT';
+
+export function reportEditor(report) {
+  return function (dispatch) {
+    SupersetClient.put({
+      endpoint: `/api/v1/report/${report.id}`,
+      postPayload: { report },
+    })
+      .then(() => dispatch({ type: EDIT_REPORT, report }))
+      .catch(() =>
+        dispatch(
+          addDangerToast(t('An error occurred while editing this report.')),
+        ),
+      );
+  };
+}
+
+export const TOGGLE_ACTIVE = 'TOGGLE_ACTIVE';
+export function toggleActiveKey(report, isActive) {
+  return { type: TOGGLE_ACTIVE, report, isActive };
+}
+export function toggleActive(report, isActive) {
+  return function toggleActiveThunk(dispatch) {
+    return SupersetClient.put({
+      endpoint: encodeURI(`/api/v1/report/${report.id}`),
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        active: isActive,
+      }),
+    })
+      .then(() => {
+        dispatch(toggleActiveKey(report, isActive));
+      })
+      .catch(() => {
+        dispatch(
+          addDangerToast(
+            t('We were unable to active or deactivate this report.'),
+          ),
+        );
+      });
+  };
+}
diff --git a/superset-frontend/src/reports/reducers/reportState.js b/superset-frontend/src/reports/reducers/reportState.js
index 16979ac..5946dbd 100644
--- a/superset-frontend/src/reports/reducers/reportState.js
+++ b/superset-frontend/src/reports/reducers/reportState.js
@@ -17,7 +17,8 @@
  * under the License.
  */
 /* eslint-disable camelcase */
-import { SET_REPORT } from '../actions/reportState';
+import { alterInArr } from 'src/reduxUtils';
+import { SET_REPORT, EDIT_REPORT, TOGGLE_ACTIVE } from '../actions/reportState';
 
 export default function reportStateReducer(state = {}, action) {
   const actionHandlers = {
@@ -27,6 +28,31 @@ export default function reportStateReducer(state = {}, action) {
         report: action.report,
       };
     },
+    [EDIT_REPORT]() {
+      return {
+        ...state,
+        report: action.report,
+      };
+    },
+    [TOGGLE_ACTIVE]() {
+      const { report, isActive } = action;
+      const existing = state.report.result.find(
+        result => result.id === report.id,
+      );
+      const newArr = alterInArr(
+        state.report,
+        'result',
+        existing,
+        {
+          active: isActive,
+        },
+        'id',
+      );
+      return {
+        ...state,
+        report: newArr,
+      };
+    },
   };
 
   if (action.type in actionHandlers) {