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 412f34560b2a9..cd19e7c82646d 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 b4803e8c8d186..bca0f32476caa 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -308,6 +308,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 48cd4583d4cb6..5a219c79f0480 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -2532,8 +2532,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