diff --git a/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseList_spec.jsx b/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseList_spec.jsx new file mode 100644 index 0000000000000..c85ab825d7597 --- /dev/null +++ b/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseList_spec.jsx @@ -0,0 +1,41 @@ +/** + * 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 thunk from 'redux-thunk'; +import configureStore from 'redux-mock-store'; +import { styledMount as mount } from 'spec/helpers/theming'; + +import DatabaseList from 'src/views/CRUD/data/database/DatabaseList'; +import SubMenu from 'src/components/Menu/SubMenu'; + +// store needed for withToasts(DatabaseList) +const mockStore = configureStore([thunk]); +const store = mockStore({}); + +describe('DatabaseList', () => { + const wrapper = mount(, { context: { store } }); + + it('renders', () => { + expect(wrapper.find(DatabaseList)).toExist(); + }); + + it('renders a SubMenu', () => { + expect(wrapper.find(SubMenu)).toExist(); + }); +}); diff --git a/superset-frontend/spec/javascripts/views/CRUD/dataset/DatasetList_spec.jsx b/superset-frontend/spec/javascripts/views/CRUD/data/dataset/DatasetList_spec.jsx similarity index 94% rename from superset-frontend/spec/javascripts/views/CRUD/dataset/DatasetList_spec.jsx rename to superset-frontend/spec/javascripts/views/CRUD/data/dataset/DatasetList_spec.jsx index 53de35d0e7a3f..db49a0ff4691d 100644 --- a/superset-frontend/spec/javascripts/views/CRUD/dataset/DatasetList_spec.jsx +++ b/superset-frontend/spec/javascripts/views/CRUD/data/dataset/DatasetList_spec.jsx @@ -17,20 +17,19 @@ * under the License. */ import React from 'react'; -import { mount } from 'enzyme'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; import fetchMock from 'fetch-mock'; -import { supersetTheme, ThemeProvider } from '@superset-ui/style'; +import { styledMount as mount } from 'spec/helpers/theming'; -import DatasetList from 'src/views/CRUD/dataset/DatasetList'; +import DatasetList from 'src/views/CRUD/data/dataset/DatasetList'; import ListView from 'src/components/ListView'; import Button from 'src/components/Button'; import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; import { act } from 'react-dom/test-utils'; -// store needed for withToasts(datasetTable) +// store needed for withToasts(DatasetList) const mockStore = configureStore([thunk]); const store = mockStore({}); @@ -69,8 +68,6 @@ fetchMock.get(databaseEndpoint, { async function mountAndWait(props) { const mounted = mount(, { context: { store }, - wrappingComponent: ThemeProvider, - wrappingComponentProps: { theme: supersetTheme }, }); await waitForComponentToPaint(mounted); diff --git a/superset-frontend/spec/javascripts/welcome/DashboardTable_spec.tsx b/superset-frontend/spec/javascripts/views/CRUD/welcome/DashboardTable_spec.tsx similarity index 97% rename from superset-frontend/spec/javascripts/welcome/DashboardTable_spec.tsx rename to superset-frontend/spec/javascripts/views/CRUD/welcome/DashboardTable_spec.tsx index b612fbbb68a30..135613a38d95c 100644 --- a/superset-frontend/spec/javascripts/welcome/DashboardTable_spec.tsx +++ b/superset-frontend/spec/javascripts/views/CRUD/welcome/DashboardTable_spec.tsx @@ -24,7 +24,7 @@ import fetchMock from 'fetch-mock'; import { supersetTheme, ThemeProvider } from '@superset-ui/style'; import ListView from 'src/components/ListView'; -import DashboardTable from 'src/welcome/DashboardTable'; +import DashboardTable from 'src/views/CRUD/welcome/DashboardTable'; // store needed for withToasts(DashboardTable) const mockStore = configureStore([thunk]); diff --git a/superset-frontend/spec/javascripts/welcome/Welcome_spec.tsx b/superset-frontend/spec/javascripts/views/CRUD/welcome/Welcome_spec.tsx similarity index 96% rename from superset-frontend/spec/javascripts/welcome/Welcome_spec.tsx rename to superset-frontend/spec/javascripts/views/CRUD/welcome/Welcome_spec.tsx index 0e7e5e19d64f9..bf23ef11afac6 100644 --- a/superset-frontend/spec/javascripts/welcome/Welcome_spec.tsx +++ b/superset-frontend/spec/javascripts/views/CRUD/welcome/Welcome_spec.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { Panel, Row, Tab } from 'react-bootstrap'; import { shallow } from 'enzyme'; -import Welcome from 'src/welcome/Welcome'; +import Welcome from 'src/views/CRUD/welcome/Welcome'; describe('Welcome', () => { const mockedProps = { diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx index e96d08c3d2f9d..b17645707d526 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx @@ -21,7 +21,7 @@ import { Modal } from 'react-bootstrap'; import { styled, supersetTheme } from '@superset-ui/style'; import { t } from '@superset-ui/translation'; import { noOp } from 'src/utils/common'; -import Button from 'src/views/CRUD/dataset/Button'; +import Button from 'src/views/CRUD/data/dataset/Button'; import Icon from '../Icon'; import { ErrorLevel, ErrorSource } from './types'; diff --git a/superset-frontend/src/components/Modal.tsx b/superset-frontend/src/components/Modal.tsx index fb7edb5ddd4e7..eed12150e6a27 100644 --- a/superset-frontend/src/components/Modal.tsx +++ b/superset-frontend/src/components/Modal.tsx @@ -20,7 +20,7 @@ import React from 'react'; import styled from '@superset-ui/style'; import { Modal as BaseModal } from 'react-bootstrap'; import { t } from '@superset-ui/translation'; -import Button from 'src/views/CRUD/dataset/Button'; +import Button from 'src/views/CRUD/data/dataset/Button'; interface ModalProps { children: React.ReactNode; diff --git a/superset-frontend/src/welcome/App.tsx b/superset-frontend/src/views/App.tsx similarity index 83% rename from superset-frontend/src/welcome/App.tsx rename to superset-frontend/src/views/App.tsx index 5bc624d043159..bf642bbaf93b5 100644 --- a/superset-frontend/src/welcome/App.tsx +++ b/superset-frontend/src/views/App.tsx @@ -30,14 +30,15 @@ import Menu from 'src/components/Menu/Menu'; import FlashProvider from 'src/components/FlashProvider'; import DashboardList from 'src/views/CRUD/dashboard/DashboardList'; import ChartList from 'src/views/CRUD/chart/ChartList'; -import DatasetList from 'src/views/CRUD/dataset/DatasetList'; +import DatasetList from 'src/views/CRUD/data/dataset/DatasetList'; +import DatasourceList from 'src/views/CRUD/data/database/DatabaseList'; -import messageToastReducer from '../messageToasts/reducers'; -import { initEnhancer } from '../reduxUtils'; -import setupApp from '../setup/setupApp'; -import setupPlugins from '../setup/setupPlugins'; -import Welcome from './Welcome'; -import ToastPresenter from '../messageToasts/containers/ToastPresenter'; +import messageToastReducer from 'src/messageToasts/reducers'; +import { initEnhancer } from 'src/reduxUtils'; +import setupApp from 'src/setup/setupApp'; +import setupPlugins from 'src/setup/setupPlugins'; +import Welcome from 'src/views/CRUD/welcome/Welcome'; +import ToastPresenter from 'src/messageToasts/containers/ToastPresenter'; setupApp(); setupPlugins(); @@ -85,6 +86,11 @@ const App = () => ( + + + + + diff --git a/superset-frontend/src/views/CRUD/data/common.ts b/superset-frontend/src/views/CRUD/data/common.ts new file mode 100644 index 0000000000000..b2c1940e062f7 --- /dev/null +++ b/superset-frontend/src/views/CRUD/data/common.ts @@ -0,0 +1,40 @@ +/** + * 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 { t } from '@superset-ui/translation'; + +export const commonMenuData = { + name: t('Data'), + children: [ + { + name: 'Datasets', + label: t('Datasets'), + url: '/tablemodelview/list/', + }, + { + name: 'Databases', + label: t('Databases'), + url: '/databaseview/list/', + }, + { + name: 'Saved Queries', + label: t('Saved Queries'), + url: '/sqllab/my_queries/', + }, + ], +}; diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx new file mode 100644 index 0000000000000..b13c211a1b272 --- /dev/null +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseList.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 React from 'react'; + +import withToasts from 'src/messageToasts/enhancers/withToasts'; +import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu'; +import { commonMenuData } from 'src/views/CRUD/data/common'; + +interface DatasourceListProps { + addDangerToast: (msg: string) => void; + addSuccessToast: (msg: string) => void; +} + +function DatasourceList({ + addDangerToast, + addSuccessToast, +}: DatasourceListProps) { + const menuData: SubMenuProps = { + activeChild: 'Databases', + ...commonMenuData, + }; + + return ( + <> + + + ); +} + +export default withToasts(DatasourceList); diff --git a/superset-frontend/src/views/CRUD/dataset/AddDatasetModal.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDatasetModal.tsx similarity index 100% rename from superset-frontend/src/views/CRUD/dataset/AddDatasetModal.tsx rename to superset-frontend/src/views/CRUD/data/dataset/AddDatasetModal.tsx diff --git a/superset-frontend/src/views/CRUD/dataset/Button.tsx b/superset-frontend/src/views/CRUD/data/dataset/Button.tsx similarity index 100% rename from superset-frontend/src/views/CRUD/dataset/Button.tsx rename to superset-frontend/src/views/CRUD/data/dataset/Button.tsx diff --git a/superset-frontend/src/views/CRUD/dataset/DatasetList.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx similarity index 98% rename from superset-frontend/src/views/CRUD/dataset/DatasetList.tsx rename to superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx index 8ee1e5f0a8c35..f7a5ab04f5aff 100644 --- a/superset-frontend/src/views/CRUD/dataset/DatasetList.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx @@ -35,6 +35,7 @@ import ListView, { Filters, } from 'src/components/ListView'; import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu'; +import { commonMenuData } from 'src/views/CRUD/data/common'; import AvatarIcon from 'src/components/AvatarIcon'; import Owner from 'src/types/Owner'; import withToasts from 'src/messageToasts/enhancers/withToasts'; @@ -441,20 +442,7 @@ const DatasetList: FunctionComponent = ({ const menuData: SubMenuProps = { activeChild: 'Datasets', - name: t('Data'), - children: [ - { - name: 'Datasets', - label: t('Datasets'), - url: '/tablemodelview/list/', - }, - { name: 'Databases', label: t('Databases'), url: '/databaseview/list/' }, - { - name: 'Saved Queries', - label: t('Saved Queries'), - url: '/sqllab/my_queries/', - }, - ], + ...commonMenuData, }; if (canCreate) { diff --git a/superset-frontend/src/welcome/DashboardTable.tsx b/superset-frontend/src/views/CRUD/welcome/DashboardTable.tsx similarity index 100% rename from superset-frontend/src/welcome/DashboardTable.tsx rename to superset-frontend/src/views/CRUD/welcome/DashboardTable.tsx diff --git a/superset-frontend/src/welcome/Welcome.tsx b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx similarity index 96% rename from superset-frontend/src/welcome/Welcome.tsx rename to superset-frontend/src/views/CRUD/welcome/Welcome.tsx index 3ec66e1d34b53..0b53ae60f1b98 100644 --- a/superset-frontend/src/welcome/Welcome.tsx +++ b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx @@ -21,8 +21,8 @@ import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap'; import { t } from '@superset-ui/translation'; import { useQueryParam, StringParam, QueryParamConfig } from 'use-query-params'; import { User } from 'src/types/bootstrapTypes'; -import RecentActivity from '../profile/components/RecentActivity'; -import Favorites from '../profile/components/Favorites'; +import RecentActivity from 'src/profile/components/RecentActivity'; +import Favorites from 'src/profile/components/Favorites'; import DashboardTable from './DashboardTable'; interface WelcomeProps { diff --git a/superset-frontend/src/welcome/index.tsx b/superset-frontend/src/views/index.tsx similarity index 100% rename from superset-frontend/src/welcome/index.tsx rename to superset-frontend/src/views/index.tsx diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js index 66b04d06f05ee..84d0479623a49 100644 --- a/superset-frontend/webpack.config.js +++ b/superset-frontend/webpack.config.js @@ -180,7 +180,7 @@ const config = { explore: addPreamble('/src/explore/index.jsx'), dashboard: addPreamble('/src/dashboard/index.jsx'), sqllab: addPreamble('/src/SqlLab/index.tsx'), - welcome: addPreamble('/src/welcome/index.tsx'), + crudViews: addPreamble('/src/views/index.tsx'), profile: addPreamble('/src/profile/index.tsx'), showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')], }, diff --git a/superset/config.py b/superset/config.py index a974f85ac1d05..db5aa6e08549a 100644 --- a/superset/config.py +++ b/superset/config.py @@ -309,6 +309,7 @@ def _try_json_readsha( # pylint: disable=unused-argument "SIP_38_VIZ_REARCHITECTURE": False, "TAGGING_SYSTEM": False, "SQLLAB_BACKEND_PERSISTENCE": False, + "SIP_34_DATABASE_UI": False, } # This is merely a default. diff --git a/superset/templates/superset/welcome.html b/superset/templates/superset/crud_views.html similarity index 96% rename from superset/templates/superset/welcome.html rename to superset/templates/superset/crud_views.html index 35c25705a2d68..57c49ae1ff303 100644 --- a/superset/templates/superset/welcome.html +++ b/superset/templates/superset/crud_views.html @@ -22,5 +22,5 @@ {% endblock %} {% block tail_js %} - {{ js_bundle("welcome") }} + {{ js_bundle("crudViews") }} {% endblock %} diff --git a/superset/views/base.py b/superset/views/base.py index 692f289b1f937..e139df082eeaf 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -333,8 +333,8 @@ def render_app_template(self) -> FlaskResponse: "common": common_bootstrap_payload(), } return self.render_template( - "superset/welcome.html", - entry="welcome", + "superset/crud_views.html", + entry="crudViews", bootstrap_data=json.dumps( payload, default=utils.pessimistic_json_iso_dttm_ser ), diff --git a/superset/views/core.py b/superset/views/core.py index cee8a618c83d6..d6ee6907fe7c7 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -2539,8 +2539,8 @@ def welcome(self) -> FlaskResponse: } return self.render_template( - "superset/welcome.html", - entry="welcome", + "superset/crud_views.html", + entry="crudViews", bootstrap_data=json.dumps( payload, default=utils.pessimistic_json_iso_dttm_ser ), diff --git a/superset/views/database/views.py b/superset/views/database/views.py index 654d2a2c81ec5..6987de9d54813 100644 --- a/superset/views/database/views.py +++ b/superset/views/database/views.py @@ -19,8 +19,9 @@ from typing import TYPE_CHECKING from flask import flash, g, redirect -from flask_appbuilder import SimpleFormView +from flask_appbuilder import expose, SimpleFormView from flask_appbuilder.models.sqla.interface import SQLAInterface +from flask_appbuilder.security.decorators import has_access from flask_babel import lazy_gettext as _ from werkzeug.wrappers import Response from wtforms.fields import StringField @@ -31,7 +32,9 @@ from superset.connectors.sqla.models import SqlaTable from superset.constants import RouteMethod from superset.exceptions import CertificateException +from superset.extensions import feature_flag_manager from superset.sql_parse import Table +from superset.typing import FlaskResponse from superset.utils import core as utils from superset.views.base import DeleteMixin, SupersetModelView, YamlExportMixin @@ -93,6 +96,17 @@ class DatabaseView( def _delete(self, pk: int) -> None: DeleteMixin._delete(self, pk) + @expose("/list/") + @has_access + def list(self) -> FlaskResponse: + if not ( + app.config["ENABLE_REACT_CRUD_VIEWS"] + and feature_flag_manager.is_feature_enabled("SIP_34_DATABASE_UI") + ): + return super().list() + + return super().render_app_template() + class CsvToDatabaseView(SimpleFormView): form = CsvToDatabaseForm