From 05764efaaaf7bf1df0a80f6350ee4c1bb4a6192f Mon Sep 17 00:00:00 2001 From: xuzhebin Date: Sat, 21 Aug 2021 14:18:42 +0800 Subject: [PATCH 01/12] fix(dashboard-ui): undo and redo buttons weird alignment --- superset-frontend/src/components/Button/index.tsx | 5 +++-- superset-frontend/src/dashboard/components/Header/index.jsx | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/components/Button/index.tsx b/superset-frontend/src/components/Button/index.tsx index 62e1214d3dffc..fb323c5d7bc05 100644 --- a/superset-frontend/src/components/Button/index.tsx +++ b/superset-frontend/src/components/Button/index.tsx @@ -63,6 +63,7 @@ export interface ButtonProps { htmlType?: 'button' | 'submit' | 'reset'; cta?: boolean; loading?: boolean | { delay?: number | undefined } | undefined; + showMarginRight?: boolean; } export default function Button(props: ButtonProps) { @@ -76,6 +77,7 @@ export default function Button(props: ButtonProps) { cta, children, href, + showMarginRight = true, ...restProps } = props; @@ -154,8 +156,7 @@ export default function Button(props: ButtonProps) { } else { renderedChildren = Children.toArray(children); } - - const firstChildMargin = renderedChildren.length > 1 ? theme.gridUnit * 2 : 0; + const firstChildMargin = showMarginRight && (renderedChildren.length > 1) ? theme.gridUnit * 2 : 0; const button = (   From c307e64531973c0f1626f2d98e254452072a1da2 Mon Sep 17 00:00:00 2001 From: Ville Brofeldt <33317356+villebro@users.noreply.github.com> Date: Fri, 20 Aug 2021 11:43:57 +0300 Subject: [PATCH 02/12] fix: call external metadata endpoint with correct rison object (#16369) --- superset-frontend/src/datasource/DatasourceEditor.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx b/superset-frontend/src/datasource/DatasourceEditor.jsx index c59b6ad70b8fa..4bf2690cfa8fd 100644 --- a/superset-frontend/src/datasource/DatasourceEditor.jsx +++ b/superset-frontend/src/datasource/DatasourceEditor.jsx @@ -493,11 +493,14 @@ class DatasourceEditor extends React.PureComponent { schema_name: datasource.schema, table_name: datasource.table_name, }; - const endpoint = `/datasource/external_metadata_by_name/?q=${rison.encode( + Object.entries(params).forEach(([key, value]) => { // rison can't encode the undefined value - Object.keys(params).map(key => - params[key] === undefined ? null : params[key], - ), + if (value === undefined) { + params[key] = null; + } + }); + const endpoint = `/datasource/external_metadata_by_name/?q=${rison.encode( + params, )}`; this.setState({ metadataLoading: true }); From 0c8cc34668bb77fa53c7f01541b213e5df8510e2 Mon Sep 17 00:00:00 2001 From: "Hugh A. Miles II" Date: Fri, 20 Aug 2021 12:31:21 -0400 Subject: [PATCH 03/12] feat: Add new dev commands to Makefile (#16327) * updating makefile * Update Makefile --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 85027a175f58b..ef0d3edbec13f 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ update-py: update-js: # Install js packages - cd superset-frontend; npm install + cd superset-frontend; npm ci venv: # Create a virtual environment and activate it (recommended) @@ -81,3 +81,9 @@ py-lint: pre-commit js-format: cd superset-frontend; npm run prettier + +flask-app: + flask run -p 8088 --with-threads --reload --debugger + +node-app: + cd superset-frontend; npm run dev-server From 6b24c774699fcee46e15767bfb976589e4683d65 Mon Sep 17 00:00:00 2001 From: Ke Zhu Date: Fri, 20 Aug 2021 12:54:46 -0400 Subject: [PATCH 04/12] docs: document FLASK_APP_MUTATOR (#16286) --- .../pages/docs/installation/configuring.mdx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/src/pages/docs/installation/configuring.mdx b/docs/src/pages/docs/installation/configuring.mdx index 2c141d4dd50c8..707aaeef7209e 100644 --- a/docs/src/pages/docs/installation/configuring.mdx +++ b/docs/src/pages/docs/installation/configuring.mdx @@ -201,6 +201,25 @@ CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager ] ``` +### Flask app Configuration Hook + +`FLASK_APP_MUTATOR` is a configuration function that can be provided in your environment, receives +the app object and can alter it in any way. For example, add `FLASK_APP_MUTATOR` into your +`superset_config.py` to setup session cookie expiration time to 24 hours: + +``` +def make_session_permanent(): + ''' + Enable maxAge for the cookie 'session' + ''' + session.permanent = True + +# Set up max age of session to 24 hours +PERMANENT_SESSION_LIFETIME = timedelta(hours=24) +def FLASK_APP_MUTATOR(app: Flask) -> None: + app.before_request_funcs.setdefault(None, []).append(make_session_permanent) +``` + ### Feature Flags To support a diverse set of users, Superset has some features that are not enabled by default. For From 85117cdccf3dea09094e53b35b61218a3b4fc316 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Fri, 20 Aug 2021 10:03:31 -0700 Subject: [PATCH 05/12] fix: update table ID in query context on chart import (#16374) * fix: update table ID in query context on chart import * Fix test --- .../charts/commands/importers/v1/__init__.py | 7 +++++ .../charts/commands_tests.py | 28 +++++++++++++++++++ .../fixtures/importexport.py | 1 + 3 files changed, 36 insertions(+) diff --git a/superset/charts/commands/importers/v1/__init__.py b/superset/charts/commands/importers/v1/__init__.py index 0e2b5b3a8adf7..5c2b535a7b34a 100644 --- a/superset/charts/commands/importers/v1/__init__.py +++ b/superset/charts/commands/importers/v1/__init__.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. +import json from typing import Any, Dict, Set from marshmallow import Schema @@ -95,4 +96,10 @@ def _import( } ) config["params"].update({"datasource": dataset.uid}) + if config["query_context"]: + # TODO (betodealmeida): export query_context as object, not string + query_context = json.loads(config["query_context"]) + query_context["datasource"] = {"id": dataset.id, "type": "table"} + config["query_context"] = json.dumps(query_context) + import_chart(session, config, overwrite=overwrite) diff --git a/tests/integration_tests/charts/commands_tests.py b/tests/integration_tests/charts/commands_tests.py index cd6e01f3dc1d4..238a54e29c7f4 100644 --- a/tests/integration_tests/charts/commands_tests.py +++ b/tests/integration_tests/charts/commands_tests.py @@ -191,6 +191,34 @@ def test_import_v1_chart(self): ) assert dataset.table_name == "imported_dataset" assert chart.table == dataset + assert json.loads(chart.query_context) == { + "datasource": {"id": dataset.id, "type": "table"}, + "force": False, + "queries": [ + { + "time_range": " : ", + "filters": [], + "extras": { + "time_grain_sqla": None, + "having": "", + "having_druid": [], + "where": "", + }, + "applied_time_extras": {}, + "columns": [], + "metrics": [], + "annotation_layers": [], + "row_limit": 5000, + "timeseries_limit": 0, + "order_desc": True, + "url_params": {}, + "custom_params": {}, + "custom_form_data": {}, + } + ], + "result_format": "json", + "result_type": "full", + } database = ( db.session.query(Database).filter_by(uuid=database_config["uuid"]).one() diff --git a/tests/integration_tests/fixtures/importexport.py b/tests/integration_tests/fixtures/importexport.py index 951ecf9bb4350..78f643c587af1 100644 --- a/tests/integration_tests/fixtures/importexport.py +++ b/tests/integration_tests/fixtures/importexport.py @@ -444,6 +444,7 @@ }, "viz_type": "deck_path", }, + "query_context": '{"datasource":{"id":12,"type":"table"},"force":false,"queries":[{"time_range":" : ","filters":[],"extras":{"time_grain_sqla":null,"having":"","having_druid":[],"where":""},"applied_time_extras":{},"columns":[],"metrics":[],"annotation_layers":[],"row_limit":5000,"timeseries_limit":0,"order_desc":true,"url_params":{},"custom_params":{},"custom_form_data":{}}],"result_format":"json","result_type":"full"}', "cache_timeout": None, "uuid": "0c23747a-6528-4629-97bf-e4b78d3b9df1", "version": "1.0.0", From 92342ebb8a4a0d845de979513bcec7d659cf8f83 Mon Sep 17 00:00:00 2001 From: Lyndsi Kay Williams <55605634+lyndsiWilliams@users.noreply.github.com> Date: Fri, 20 Aug 2021 12:08:36 -0500 Subject: [PATCH 06/12] 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 --- .../spec/fixtures/mockReportState.js | 38 +++++ .../spec/fixtures/mockStateWithoutUser.tsx | 46 ++++++ .../spec/helpers/reducerIndex.ts | 2 + .../components/ExploreChartHeader_spec.jsx | 87 ---------- .../src/components/ReportModal/index.test.tsx | 25 ++- .../src/components/ReportModal/index.tsx | 2 +- .../components/Header/Header.test.tsx | 155 +++++++++++++++++- .../src/reports/actions/reports.js | 3 +- 8 files changed, 263 insertions(+), 95 deletions(-) create mode 100644 superset-frontend/spec/fixtures/mockReportState.js create mode 100644 superset-frontend/spec/fixtures/mockStateWithoutUser.tsx delete mode 100644 superset-frontend/spec/javascripts/explore/components/ExploreChartHeader_spec.jsx diff --git a/superset-frontend/spec/fixtures/mockReportState.js b/superset-frontend/spec/fixtures/mockReportState.js new file mode 100644 index 0000000000000..075af8bfe0962 --- /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 0000000000000..bc92df4df75d0 --- /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 e84b7f6f5119a..113368389509a 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 c6eda704e16ec..0000000000000 --- 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(); - }); - - it('is valid', () => { - expect(React.isValidElement()).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 99b1eadcc4970..44e3d0ef65c51 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(, { useRedux: true }); + }); + afterAll(() => { // @ts-ignore isFeatureEnabledMock.restore(); }); - it('inputs respond correctly', () => { - render(, { 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 292d3b7414b2e..e24d1d756c63f 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 68af9380e9b42..8a9ecdb5be46f 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 55cea9dbaa7c9..7b3bc814ca0e8 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'; From 5e60eb2560410ed1a28af2f02a16f08cbb852334 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Fri, 20 Aug 2021 12:03:05 -0700 Subject: [PATCH 07/12] fix: import dashboard w/o metadata (#16360) --- superset/dashboards/commands/importers/v1/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/superset/dashboards/commands/importers/v1/__init__.py b/superset/dashboards/commands/importers/v1/__init__.py index 6e17aef9e5431..1720e01ab8bcc 100644 --- a/superset/dashboards/commands/importers/v1/__init__.py +++ b/superset/dashboards/commands/importers/v1/__init__.py @@ -67,7 +67,9 @@ def _import( for file_name, config in configs.items(): if file_name.startswith("dashboards/"): chart_uuids.update(find_chart_uuids(config["position"])) - dataset_uuids.update(find_native_filter_datasets(config["metadata"])) + dataset_uuids.update( + find_native_filter_datasets(config.get("metadata", {})) + ) # discover datasets associated with charts for file_name, config in configs.items(): From eb485117999c23390c477e5a0e1ad5f33cc9cfea Mon Sep 17 00:00:00 2001 From: Phillip Kelley-Dotson Date: Fri, 20 Aug 2021 15:59:53 -0700 Subject: [PATCH 08/12] initial commit (#16380) --- superset-frontend/src/datasource/DatasourceEditor.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/superset-frontend/src/datasource/DatasourceEditor.jsx b/superset-frontend/src/datasource/DatasourceEditor.jsx index 4bf2690cfa8fd..e11b8310bb75c 100644 --- a/superset-frontend/src/datasource/DatasourceEditor.jsx +++ b/superset-frontend/src/datasource/DatasourceEditor.jsx @@ -324,15 +324,19 @@ class DatasourceEditor extends React.PureComponent { datasource: { ...props.datasource, metrics: props.datasource.metrics?.map(metric => { + const { + certified_by: certifiedByMetric, + certification_details: certificationDetails, + } = metric; const { certification: { details, certified_by: certifiedBy } = {}, warning_markdown: warningMarkdown, } = JSON.parse(metric.extra || '{}') || {}; return { ...metric, - certification_details: details || '', + certification_details: certificationDetails || details, warning_markdown: warningMarkdown || '', - certified_by: certifiedBy, + certified_by: certifiedBy || certifiedByMetric, }; }), }, @@ -935,7 +939,6 @@ class DatasourceEditor extends React.PureComponent { const { datasource } = this.state; const { metrics } = datasource; const sortedMetrics = metrics?.length ? this.sortMetrics(metrics) : []; - return ( Date: Fri, 20 Aug 2021 16:08:54 -0700 Subject: [PATCH 09/12] fix: big number default date format (#16383) --- superset-frontend/package-lock.json | 14 +++++++------- superset-frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 8fa99d0229fa2..1ef93bce0e862 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -36,7 +36,7 @@ "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.85", "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.85", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.86", "@superset-ui/legacy-preset-chart-deckgl": "^0.4.11", "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", "@superset-ui/plugin-chart-echarts": "^0.17.85", @@ -12480,9 +12480,9 @@ } }, "node_modules/@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.85", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.85.tgz", - "integrity": "sha512-Txuk8L/flifPESHWNpZ8vwXinHAPCXXEp2fZdB/6CRyB6TVfMs2RmKqnEyzO5xBGgqeTfdH6xeuVn+8Hfdcvrg==", + "version": "0.17.86", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.86.tgz", + "integrity": "sha512-b9TZ7VJ3IK+ii6VDllfOCDujLR++rYNXcnNW/XgCZP9+ZwbHBIzGqC7APdUid7qDJM/PZwN6USXSnYf5MIYPLw==", "dependencies": { "@data-ui/xy-chart": "^0.0.84", "@superset-ui/chart-controls": "0.17.85", @@ -61935,9 +61935,9 @@ } }, "@superset-ui/legacy-preset-chart-big-number": { - "version": "0.17.85", - "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.85.tgz", - "integrity": "sha512-Txuk8L/flifPESHWNpZ8vwXinHAPCXXEp2fZdB/6CRyB6TVfMs2RmKqnEyzO5xBGgqeTfdH6xeuVn+8Hfdcvrg==", + "version": "0.17.86", + "resolved": "https://registry.npmjs.org/@superset-ui/legacy-preset-chart-big-number/-/legacy-preset-chart-big-number-0.17.86.tgz", + "integrity": "sha512-b9TZ7VJ3IK+ii6VDllfOCDujLR++rYNXcnNW/XgCZP9+ZwbHBIzGqC7APdUid7qDJM/PZwN6USXSnYf5MIYPLw==", "requires": { "@data-ui/xy-chart": "^0.0.84", "@superset-ui/chart-controls": "0.17.85", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 0ffeb44658ca2..20e1afc52bb4d 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -88,7 +88,7 @@ "@superset-ui/legacy-plugin-chart-sunburst": "^0.17.85", "@superset-ui/legacy-plugin-chart-treemap": "^0.17.85", "@superset-ui/legacy-plugin-chart-world-map": "^0.17.85", - "@superset-ui/legacy-preset-chart-big-number": "^0.17.85", + "@superset-ui/legacy-preset-chart-big-number": "^0.17.86", "@superset-ui/legacy-preset-chart-deckgl": "^0.4.11", "@superset-ui/legacy-preset-chart-nvd3": "^0.17.85", "@superset-ui/plugin-chart-echarts": "^0.17.85", From c1c0d2cfbed4d11ccde01f9b369caa3bcfb3c83b Mon Sep 17 00:00:00 2001 From: Phillip Kelley-Dotson Date: Fri, 20 Aug 2021 16:46:55 -0700 Subject: [PATCH 10/12] initial commit (#16366) --- .../explore/components/controls/CollectionControl/index.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx b/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx index 8cadbf27fb09a..384d23ed03a52 100644 --- a/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx +++ b/superset-frontend/src/explore/components/controls/CollectionControl/index.jsx @@ -80,8 +80,9 @@ class CollectionControl extends React.Component { } onChange(i, value) { - Object.assign(this.props.value[i], value); - this.props.onChange(this.props.value); + const newValue = [...this.props.value]; + newValue[i] = { ...this.props.value[i], ...value }; + this.props.onChange(newValue); } onAdd() { From b54ae086cb0e572e5abe48073cc6d07c5955cfa5 Mon Sep 17 00:00:00 2001 From: xuzhebin Date: Sun, 22 Aug 2021 20:14:50 +0800 Subject: [PATCH 11/12] fix(Explore control pane): keyboard nav & focus --- .../src/explore/components/ControlHeader.jsx | 9 +++++---- .../src/explore/components/controls/SelectControl.jsx | 3 +-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/superset-frontend/src/explore/components/ControlHeader.jsx b/superset-frontend/src/explore/components/ControlHeader.jsx index 9a45917f6cf77..73f734e7cef8b 100644 --- a/superset-frontend/src/explore/components/ControlHeader.jsx +++ b/superset-frontend/src/explore/components/ControlHeader.jsx @@ -37,6 +37,7 @@ const propTypes = { tooltipOnClick: PropTypes.func, warning: PropTypes.string, danger: PropTypes.string, + hideNativeTab: PropTypes.bool }; const defaultProps = { @@ -44,6 +45,7 @@ const defaultProps = { renderTrigger: false, hovered: false, name: undefined, + hideNativeTab:false }; class ControlHeader extends React.Component { @@ -93,8 +95,7 @@ class ControlHeader extends React.Component { const labelClass = this.props.validationErrors.length > 0 ? 'text-danger' : ''; - const { theme } = this.props; - + const { theme, hideNativeTab } = this.props; return (
@@ -106,8 +107,8 @@ class ControlHeader extends React.Component { > {this.props.leftNode && {this.props.leftNode}} - {this.props.showHeader && } + {this.props.showHeader && } {isMulti ? ( ) : ( From 8e252dde1493387fd34d2d443d55440168497c6c Mon Sep 17 00:00:00 2001 From: xuzhebin Date: Mon, 23 Aug 2021 10:57:10 +0800 Subject: [PATCH 12/12] lint: lint frontend --- superset-frontend/src/components/Button/index.tsx | 3 ++- .../src/explore/components/ControlHeader.jsx | 14 ++++++++------ .../explore/components/controls/SelectControl.jsx | 4 +++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/superset-frontend/src/components/Button/index.tsx b/superset-frontend/src/components/Button/index.tsx index fb323c5d7bc05..5199d26f4f8a2 100644 --- a/superset-frontend/src/components/Button/index.tsx +++ b/superset-frontend/src/components/Button/index.tsx @@ -156,7 +156,8 @@ export default function Button(props: ButtonProps) { } else { renderedChildren = Children.toArray(children); } - const firstChildMargin = showMarginRight && (renderedChildren.length > 1) ? theme.gridUnit * 2 : 0; + const firstChildMargin = + showMarginRight && renderedChildren.length > 1 ? theme.gridUnit * 2 : 0; const button = ( {this.props.leftNode && {this.props.leftNode}} {this.props.label} {' '} diff --git a/superset-frontend/src/explore/components/controls/SelectControl.jsx b/superset-frontend/src/explore/components/controls/SelectControl.jsx index a46a7edbdb8bd..9a456eda69173 100644 --- a/superset-frontend/src/explore/components/controls/SelectControl.jsx +++ b/superset-frontend/src/explore/components/controls/SelectControl.jsx @@ -300,7 +300,9 @@ export default class SelectControl extends React.PureComponent { } `} > - {this.props.showHeader && } + {this.props.showHeader && ( + + )} {isMulti ? ( ) : (