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/08/20 17:09:25 UTC

[superset] branch master updated: test: Functional RTL for email report modal II (#16148)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 518c3c9  test: Functional RTL for email report modal II (#16148)
518c3c9 is described below

commit 518c3c9ae088f780f1874c63eab416ee1cbb4a61
Author: Lyndsi Kay Williams <55...@users.noreply.github.com>
AuthorDate: Fri Aug 20 12:08:36 2021 -0500

    test: Functional RTL for email report modal II (#16148)
    
    * Email Report Modal validation testing
    
    * Starting RTL testing for email report
    
    * Calendar icon now rendering!
    
    * Create report testing in dashboard
    
    * make linter happy
    
    * Fixing weird error
    
    * Removed ExploreChartHeader_spec
    
    * Fixed dashboard header test
    
    * revert changes from merge
    
    * Fix tests
    
    Co-authored-by: Elizabeth Thompson <es...@gmail.com>
---
 superset-frontend/spec/fixtures/mockReportState.js |  38 +++++
 .../spec/fixtures/mockStateWithoutUser.tsx         |  46 ++++++
 superset-frontend/spec/helpers/reducerIndex.ts     |   2 +
 .../explore/components/ExploreChartHeader_spec.jsx |  87 ------------
 .../src/components/ReportModal/index.test.tsx      |  25 +++-
 .../src/components/ReportModal/index.tsx           |   2 +-
 .../dashboard/components/Header/Header.test.tsx    | 155 ++++++++++++++++++++-
 superset-frontend/src/reports/actions/reports.js   |   3 +-
 8 files changed, 263 insertions(+), 95 deletions(-)

diff --git a/superset-frontend/spec/fixtures/mockReportState.js b/superset-frontend/spec/fixtures/mockReportState.js
new file mode 100644
index 0000000..075af8b
--- /dev/null
+++ b/superset-frontend/spec/fixtures/mockReportState.js
@@ -0,0 +1,38 @@
+/**
+ * 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 dashboardInfo from './mockDashboardInfo';
+import { user } from '../javascripts/sqllab/fixtures';
+
+export default {
+  active: true,
+  creation_method: 'dashboards',
+  crontab: '0 12 * * 1',
+  dashboard: dashboardInfo.id,
+  name: 'Weekly Report',
+  owners: [user.userId],
+  recipients: [
+    {
+      recipient_config_json: {
+        target: user.email,
+      },
+      type: 'Email',
+    },
+  ],
+  type: 'Report',
+};
diff --git a/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx b/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx
new file mode 100644
index 0000000..bc92df4
--- /dev/null
+++ b/superset-frontend/spec/fixtures/mockStateWithoutUser.tsx
@@ -0,0 +1,46 @@
+/**
+ * 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 datasources from 'spec/fixtures/mockDatasource';
+import messageToasts from 'spec/javascripts/messageToasts/mockMessageToasts';
+import {
+  nativeFiltersInfo,
+  mockDataMaskInfo,
+} from 'spec/javascripts/dashboard/fixtures/mockNativeFilters';
+import chartQueries from 'spec/fixtures/mockChartQueries';
+import { dashboardLayout } from 'spec/fixtures/mockDashboardLayout';
+import dashboardInfo from 'spec/fixtures/mockDashboardInfo';
+import { emptyFilters } from 'spec/fixtures/mockDashboardFilters';
+import dashboardState from 'spec/fixtures/mockDashboardState';
+import { sliceEntitiesForChart } from 'spec/fixtures/mockSliceEntities';
+import reports from 'spec/fixtures/mockReportState';
+
+export default {
+  datasources,
+  sliceEntities: sliceEntitiesForChart,
+  charts: chartQueries,
+  nativeFilters: nativeFiltersInfo,
+  dataMask: mockDataMaskInfo,
+  dashboardInfo,
+  dashboardFilters: emptyFilters,
+  dashboardState,
+  dashboardLayout,
+  messageToasts,
+  impressionId: 'mock_impression_id',
+  reports,
+};
diff --git a/superset-frontend/spec/helpers/reducerIndex.ts b/superset-frontend/spec/helpers/reducerIndex.ts
index e84b7f6..1133683 100644
--- a/superset-frontend/spec/helpers/reducerIndex.ts
+++ b/superset-frontend/spec/helpers/reducerIndex.ts
@@ -30,6 +30,7 @@ import saveModal from 'src/explore/reducers/saveModalReducer';
 import explore from 'src/explore/reducers/exploreReducer';
 import sqlLab from 'src/SqlLab/reducers/sqlLab';
 import localStorageUsageInKilobytes from 'src/SqlLab/reducers/localStorageUsage';
+import reports from 'src/reports/reducers/reports';
 
 const impressionId = (state = '') => state;
 
@@ -53,5 +54,6 @@ export default {
   explore,
   sqlLab,
   localStorageUsageInKilobytes,
+  reports,
   common: () => common,
 };
diff --git a/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx b/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx
deleted file mode 100644
index c6eda70..0000000
--- a/superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * 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 { shallow } from 'enzyme';
-
-import { ExploreChartHeader } from 'src/explore/components/ExploreChartHeader';
-import ExploreActionButtons from 'src/explore/components/ExploreActionButtons';
-import EditableTitle from 'src/components/EditableTitle';
-
-const saveSliceStub = jest.fn();
-const updateChartTitleStub = jest.fn();
-const fetchUISpecificReportStub = jest.fn();
-const mockProps = {
-  actions: {
-    saveSlice: saveSliceStub,
-    updateChartTitle: updateChartTitleStub,
-  },
-  can_overwrite: true,
-  can_download: true,
-  isStarred: true,
-  slice: {
-    form_data: {
-      viz_type: 'line',
-    },
-  },
-  table_name: 'foo',
-  form_data: {
-    viz_type: 'table',
-  },
-  user: {
-    createdOn: '2021-04-27T18:12:38.952304',
-    email: 'admin',
-    firstName: 'admin',
-    isActive: true,
-    lastName: 'admin',
-    permissions: {},
-    roles: { Admin: Array(173) },
-    userId: 1,
-    username: 'admin',
-  },
-  timeout: 1000,
-  chart: {
-    id: 0,
-    queryResponse: {},
-  },
-  fetchUISpecificReport: fetchUISpecificReportStub,
-  chartHeight: '30px',
-};
-
-describe('ExploreChartHeader', () => {
-  let wrapper;
-  beforeEach(() => {
-    wrapper = shallow(<ExploreChartHeader {...mockProps} />);
-  });
-
-  it('is valid', () => {
-    expect(React.isValidElement(<ExploreChartHeader {...mockProps} />)).toBe(
-      true,
-    );
-  });
-
-  it('renders', () => {
-    expect(wrapper.find(EditableTitle)).toExist();
-    expect(wrapper.find(ExploreActionButtons)).toExist();
-  });
-
-  it('should update title but not save', () => {
-    const editableTitle = wrapper.find(EditableTitle);
-    expect(editableTitle.props().onSaveTitle).toBe(updateChartTitleStub);
-  });
-});
diff --git a/superset-frontend/src/components/ReportModal/index.test.tsx b/superset-frontend/src/components/ReportModal/index.test.tsx
index 99b1ead..44e3d0e 100644
--- a/superset-frontend/src/components/ReportModal/index.test.tsx
+++ b/superset-frontend/src/components/ReportModal/index.test.tsx
@@ -55,13 +55,17 @@ describe('Email Report Modal', () => {
         (featureFlag: FeatureFlag) => featureFlag === FeatureFlag.ALERT_REPORTS,
       );
   });
+
+  beforeEach(() => {
+    render(<ReportModal {...defaultProps} />, { useRedux: true });
+  });
+
   afterAll(() => {
     // @ts-ignore
     isFeatureEnabledMock.restore();
   });
-  it('inputs respond correctly', () => {
-    render(<ReportModal {...defaultProps} />, { useRedux: true });
 
+  it('inputs respond correctly', () => {
     // ----- Report name textbox
     // Initial value
     const reportNameTextbox = screen.getByTestId('report-name-test');
@@ -86,4 +90,21 @@ describe('Email Report Modal', () => {
     const crontabInputs = screen.getAllByRole('combobox');
     expect(crontabInputs).toHaveLength(5);
   });
+
+  it('does not allow user to create a report without a name', () => {
+    // Grab name textbox and add button
+    const reportNameTextbox = screen.getByTestId('report-name-test');
+    const addButton = screen.getByRole('button', { name: /add/i });
+
+    // Add button should be enabled while name textbox has text
+    expect(reportNameTextbox).toHaveDisplayValue('Weekly Report');
+    expect(addButton).toBeEnabled();
+
+    // Clear the text from the name textbox
+    userEvent.clear(reportNameTextbox);
+
+    // Add button should now be disabled, blocking user from creation
+    expect(reportNameTextbox).toHaveDisplayValue('');
+    expect(addButton).toBeDisabled();
+  });
 });
diff --git a/superset-frontend/src/components/ReportModal/index.tsx b/superset-frontend/src/components/ReportModal/index.tsx
index 292d3b7..e24d1d7 100644
--- a/superset-frontend/src/components/ReportModal/index.tsx
+++ b/superset-frontend/src/components/ReportModal/index.tsx
@@ -53,7 +53,7 @@ import {
   StyledRadioGroup,
 } from './styles';
 
-interface ReportObject {
+export interface ReportObject {
   id?: number;
   active: boolean;
   crontab: string;
diff --git a/superset-frontend/src/dashboard/components/Header/Header.test.tsx b/superset-frontend/src/dashboard/components/Header/Header.test.tsx
index 68af938..8a9ecdb 100644
--- a/superset-frontend/src/dashboard/components/Header/Header.test.tsx
+++ b/superset-frontend/src/dashboard/components/Header/Header.test.tsx
@@ -19,7 +19,12 @@
 import React from 'react';
 import { render, screen, fireEvent } from 'spec/helpers/testing-library';
 import userEvent from '@testing-library/user-event';
+import sinon from 'sinon';
 import fetchMock from 'fetch-mock';
+import * as actions from 'src/reports/actions/reports';
+import * as featureFlags from 'src/featureFlags';
+import { ReportObject } from 'src/components/ReportModal';
+import mockState from 'spec/fixtures/mockStateWithoutUser';
 import { HeaderProps } from './types';
 import Header from '.';
 
@@ -40,15 +45,16 @@ const createProps = () => ({
   },
   user: {
     createdOn: '2021-04-27T18:12:38.952304',
-    email: 'admin',
+    email: 'admin@test.com',
     firstName: 'admin',
     isActive: true,
     lastName: 'admin',
     permissions: {},
-    roles: { Admin: Array(173) },
+    roles: { Admin: [['menu_access', 'Manage']] },
     userId: 1,
     username: 'admin',
   },
+  reports: {},
   dashboardTitle: 'Dashboard Title',
   charts: {},
   layout: {},
@@ -107,8 +113,10 @@ const redoProps = {
   redoLength: 1,
 };
 
+const REPORT_ENDPOINT = 'glob:*/api/v1/report*';
+
 fetchMock.get('glob:*/csstemplateasyncmodelview/api/read', {});
-fetchMock.get('glob:*/api/v1/report*', {});
+fetchMock.get(REPORT_ENDPOINT, {});
 
 function setup(props: HeaderProps) {
   return (
@@ -315,3 +323,144 @@ test('should refresh the charts', async () => {
   userEvent.click(screen.getByText('Refresh dashboard'));
   expect(mockedProps.onRefresh).toHaveBeenCalledTimes(1);
 });
+
+describe('Email Report Modal', () => {
+  let isFeatureEnabledMock: any;
+  let dispatch: any;
+
+  beforeEach(async () => {
+    isFeatureEnabledMock = jest
+      .spyOn(featureFlags, 'isFeatureEnabled')
+      .mockImplementation(() => true);
+    dispatch = sinon.spy();
+  });
+
+  afterAll(() => {
+    isFeatureEnabledMock.mockRestore();
+  });
+
+  it('creates a new email report', async () => {
+    // ---------- Render/value setup ----------
+    const mockedProps = createProps();
+    render(setup(mockedProps), { useRedux: true });
+
+    const reportValues = {
+      active: true,
+      creation_method: 'dashboards',
+      crontab: '0 12 * * 1',
+      dashboard: mockedProps.dashboardInfo.id,
+      name: 'Weekly Report',
+      owners: [mockedProps.user.userId],
+      recipients: [
+        {
+          recipient_config_json: {
+            target: mockedProps.user.email,
+          },
+          type: 'Email',
+        },
+      ],
+      type: 'Report',
+    };
+    // This is needed to structure the reportValues to match the fetchMock return
+    const stringyReportValues = `{"active":true,"creation_method":"dashboards","crontab":"0 12 * * 1","dashboard":${mockedProps.dashboardInfo.id},"name":"Weekly Report","owners":[${mockedProps.user.userId}],"recipients":[{"recipient_config_json":{"target":"${mockedProps.user.email}"},"type":"Email"}],"type":"Report"}`;
+    // Watch for report POST
+    fetchMock.post(REPORT_ENDPOINT, reportValues);
+
+    screen.logTestingPlaygroundURL();
+    // ---------- Begin tests ----------
+    // Click calendar icon to open email report modal
+    const emailReportModalButton = screen.getByRole('button', {
+      name: /schedule email report/i,
+    });
+    userEvent.click(emailReportModalButton);
+
+    // Click "Add" button to create a new email report
+    const addButton = screen.getByRole('button', { name: /add/i });
+    userEvent.click(addButton);
+
+    // Mock addReport from Redux
+    const makeRequest = () => {
+      const request = actions.addReport(reportValues as ReportObject);
+      return request(dispatch);
+    };
+
+    return makeRequest().then(() => {
+      // 🐞 ----- There are 2 POST calls at this point ----- 🐞
+
+      // addReport's mocked POST return should match the mocked values
+      expect(fetchMock.lastOptions()?.body).toEqual(stringyReportValues);
+      // Dispatch should be called once for addReport
+      expect(dispatch.callCount).toBe(2);
+      const reportCalls = fetchMock.calls(REPORT_ENDPOINT);
+      expect(reportCalls).toHaveLength(2);
+    });
+  });
+
+  it('edits an existing email report', async () => {
+    // TODO (lyndsiWilliams): This currently does not work, see TODOs below
+    //  The modal does appear with the edit title, but the PUT call is not registering
+
+    // ---------- Render/value setup ----------
+    const mockedProps = createProps();
+    const editedReportValues = {
+      active: true,
+      creation_method: 'dashboards',
+      crontab: '0 12 * * 1',
+      dashboard: mockedProps.dashboardInfo.id,
+      name: 'Weekly Report edit',
+      owners: [mockedProps.user.userId],
+      recipients: [
+        {
+          recipient_config_json: {
+            target: mockedProps.user.email,
+          },
+          type: 'Email',
+        },
+      ],
+      type: 'Report',
+    };
+
+    // getMockStore({ reports: reportValues });
+    render(setup(mockedProps), {
+      useRedux: true,
+      initialState: mockState,
+    });
+    // TODO (lyndsiWilliams): currently fetchMock detects this PUT
+    //  address as 'glob:*/api/v1/report/undefined', is not detected
+    //  on fetchMock.calls()
+    fetchMock.put(`glob:*/api/v1/report*`, editedReportValues);
+
+    // Mock fetchUISpecificReport from Redux
+    // const makeFetchRequest = () => {
+    //   const request = actions.fetchUISpecificReport(
+    //     mockedProps.user.userId,
+    //     'dashboard_id',
+    //     'dashboards',
+    //     mockedProps.dashboardInfo.id,
+    //   );
+    //   return request(dispatch);
+    // };
+
+    // makeFetchRequest();
+
+    dispatch(actions.setReport(editedReportValues));
+
+    // ---------- Begin tests ----------
+    // Click calendar icon to open email report modal
+    const emailReportModalButton = screen.getByRole('button', {
+      name: /schedule email report/i,
+    });
+    userEvent.click(emailReportModalButton);
+
+    const nameTextbox = screen.getByTestId('report-name-test');
+    userEvent.type(nameTextbox, ' edit');
+
+    const saveButton = screen.getByRole('button', { name: /save/i });
+    userEvent.click(saveButton);
+
+    // TODO (lyndsiWilliams): There should be a report in state at this porint,
+    // which would render the HeaderReportActionsDropDown under the calendar icon
+    // BLOCKER: I cannot get report to populate, as its data is handled through redux
+    expect.anything();
+  });
+});
diff --git a/superset-frontend/src/reports/actions/reports.js b/superset-frontend/src/reports/actions/reports.js
index 55cea9d..7b3bc81 100644
--- a/superset-frontend/src/reports/actions/reports.js
+++ b/superset-frontend/src/reports/actions/reports.js
@@ -98,7 +98,7 @@ const structureFetchAction = (dispatch, getState) => {
 
 export const ADD_REPORT = 'ADD_REPORT';
 
-export const addReport = report => dispatch => {
+export const addReport = report => dispatch =>
   SupersetClient.post({
     endpoint: `/api/v1/report/`,
     jsonPayload: report,
@@ -118,7 +118,6 @@ export const addReport = report => dispatch => {
         ),
       );
     });
-};
 
 export const EDIT_REPORT = 'EDIT_REPORT';