diff --git a/superset-frontend/src/features/home/RightMenu.tsx b/superset-frontend/src/features/home/RightMenu.tsx
index d0b8e44ab756b..831ae85ba39f3 100644
--- a/superset-frontend/src/features/home/RightMenu.tsx
+++ b/superset-frontend/src/features/home/RightMenu.tsx
@@ -474,7 +474,7 @@ const RightMenu = ({
{navbarRight.user_profile_url && (
- {t('Profile')}
+ {t('Profile')}
)}
{navbarRight.user_info_url && (
diff --git a/superset-frontend/src/profile/components/CreatedContent.test.tsx b/superset-frontend/src/features/profile/CreatedContent.test.tsx
similarity index 95%
rename from superset-frontend/src/profile/components/CreatedContent.test.tsx
rename to superset-frontend/src/features/profile/CreatedContent.test.tsx
index 817448cf35ea7..df49e98aff90d 100644
--- a/superset-frontend/src/profile/components/CreatedContent.test.tsx
+++ b/superset-frontend/src/features/profile/CreatedContent.test.tsx
@@ -20,8 +20,8 @@ import React from 'react';
import { shallow } from 'enzyme';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
-import CreatedContent from 'src/profile/components/CreatedContent';
import TableLoader from 'src/components/TableLoader';
+import CreatedContent from './CreatedContent';
import { user } from './fixtures';
diff --git a/superset-frontend/src/profile/components/CreatedContent.tsx b/superset-frontend/src/features/profile/CreatedContent.tsx
similarity index 100%
rename from superset-frontend/src/profile/components/CreatedContent.tsx
rename to superset-frontend/src/features/profile/CreatedContent.tsx
diff --git a/superset-frontend/src/profile/components/Favorites.test.tsx b/superset-frontend/src/features/profile/Favorites.test.tsx
similarity index 96%
rename from superset-frontend/src/profile/components/Favorites.test.tsx
rename to superset-frontend/src/features/profile/Favorites.test.tsx
index 8b5eaf4e86c45..e21967f3bf170 100644
--- a/superset-frontend/src/profile/components/Favorites.test.tsx
+++ b/superset-frontend/src/features/profile/Favorites.test.tsx
@@ -20,8 +20,8 @@ import React from 'react';
import { shallow } from 'enzyme';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
-import Favorites from 'src/profile/components/Favorites';
import TableLoader from 'src/components/TableLoader';
+import Favorites from './Favorites';
import { user } from './fixtures';
diff --git a/superset-frontend/src/profile/components/Favorites.tsx b/superset-frontend/src/features/profile/Favorites.tsx
similarity index 98%
rename from superset-frontend/src/profile/components/Favorites.tsx
rename to superset-frontend/src/features/profile/Favorites.tsx
index 834f933071676..f38f174779689 100644
--- a/superset-frontend/src/profile/components/Favorites.tsx
+++ b/superset-frontend/src/features/profile/Favorites.tsx
@@ -22,7 +22,7 @@ import moment from 'moment';
import { t } from '@superset-ui/core';
import { DashboardResponse, BootstrapUser } from 'src/types/bootstrapTypes';
import TableLoader from '../../components/TableLoader';
-import { Chart } from '../types';
+import { Chart } from './types';
interface FavoritesProps {
user: BootstrapUser;
diff --git a/superset-frontend/src/profile/components/RecentActivity.test.tsx b/superset-frontend/src/features/profile/RecentActivity.test.tsx
similarity index 95%
rename from superset-frontend/src/profile/components/RecentActivity.test.tsx
rename to superset-frontend/src/features/profile/RecentActivity.test.tsx
index 73fdeb6e841c8..a64c209296743 100644
--- a/superset-frontend/src/profile/components/RecentActivity.test.tsx
+++ b/superset-frontend/src/features/profile/RecentActivity.test.tsx
@@ -18,8 +18,8 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
-import RecentActivity from 'src/profile/components/RecentActivity';
import TableLoader from 'src/components/TableLoader';
+import RecentActivity from './RecentActivity';
import { user } from './fixtures';
diff --git a/superset-frontend/src/profile/components/RecentActivity.tsx b/superset-frontend/src/features/profile/RecentActivity.tsx
similarity index 91%
rename from superset-frontend/src/profile/components/RecentActivity.tsx
rename to superset-frontend/src/features/profile/RecentActivity.tsx
index 2810fb3544963..d550d2a9531c4 100644
--- a/superset-frontend/src/profile/components/RecentActivity.tsx
+++ b/superset-frontend/src/features/profile/RecentActivity.tsx
@@ -20,10 +20,9 @@ import React from 'react';
import moment from 'moment';
import { t } from '@superset-ui/core';
import rison from 'rison';
-
-import TableLoader from '../../components/TableLoader';
-import { ActivityResult } from '../types';
-import { BootstrapUser } from '../../types/bootstrapTypes';
+import TableLoader from 'src/components/TableLoader';
+import { BootstrapUser } from 'src/types/bootstrapTypes';
+import { ActivityResult } from './types';
interface RecentActivityProps {
user: BootstrapUser;
diff --git a/superset-frontend/src/profile/components/Security.test.tsx b/superset-frontend/src/features/profile/Security.test.tsx
similarity index 97%
rename from superset-frontend/src/profile/components/Security.test.tsx
rename to superset-frontend/src/features/profile/Security.test.tsx
index 31eda0aae7652..af4706375c848 100644
--- a/superset-frontend/src/profile/components/Security.test.tsx
+++ b/superset-frontend/src/features/profile/Security.test.tsx
@@ -18,9 +18,9 @@
*/
import React from 'react';
import { styledMount as mount } from 'spec/helpers/theming';
-import Security from 'src/profile/components/Security';
import Label from 'src/components/Label';
import { user, userNoPerms } from './fixtures';
+import Security from './Security';
describe('Security', () => {
const mockedProps = {
diff --git a/superset-frontend/src/profile/components/Security.tsx b/superset-frontend/src/features/profile/Security.tsx
similarity index 100%
rename from superset-frontend/src/profile/components/Security.tsx
rename to superset-frontend/src/features/profile/Security.tsx
diff --git a/superset-frontend/src/profile/components/UserInfo.test.tsx b/superset-frontend/src/features/profile/UserInfo.test.tsx
similarity index 97%
rename from superset-frontend/src/profile/components/UserInfo.test.tsx
rename to superset-frontend/src/features/profile/UserInfo.test.tsx
index 9d8a904cb568b..6bbc2b52d1e8e 100644
--- a/superset-frontend/src/profile/components/UserInfo.test.tsx
+++ b/superset-frontend/src/features/profile/UserInfo.test.tsx
@@ -19,7 +19,7 @@
import React from 'react';
import Gravatar from 'react-gravatar';
import { mount } from 'enzyme';
-import UserInfo from 'src/profile/components/UserInfo';
+import UserInfo from './UserInfo';
import { user } from './fixtures';
diff --git a/superset-frontend/src/profile/components/UserInfo.tsx b/superset-frontend/src/features/profile/UserInfo.tsx
similarity index 100%
rename from superset-frontend/src/profile/components/UserInfo.tsx
rename to superset-frontend/src/features/profile/UserInfo.tsx
diff --git a/superset-frontend/src/profile/components/fixtures.tsx b/superset-frontend/src/features/profile/fixtures.tsx
similarity index 100%
rename from superset-frontend/src/profile/components/fixtures.tsx
rename to superset-frontend/src/features/profile/fixtures.tsx
diff --git a/superset-frontend/src/profile/types.ts b/superset-frontend/src/features/profile/types.ts
similarity index 100%
rename from superset-frontend/src/profile/types.ts
rename to superset-frontend/src/features/profile/types.ts
diff --git a/superset-frontend/src/profile/components/App.test.tsx b/superset-frontend/src/pages/Profile/Profile.test.tsx
similarity index 79%
rename from superset-frontend/src/profile/components/App.test.tsx
rename to superset-frontend/src/pages/Profile/Profile.test.tsx
index 4f7a4caa1e1f8..cf1446c29ac62 100644
--- a/superset-frontend/src/profile/components/App.test.tsx
+++ b/superset-frontend/src/pages/Profile/Profile.test.tsx
@@ -19,26 +19,25 @@
import React from 'react';
import { Row, Col } from 'src/components';
import { shallow } from 'enzyme';
-import App from 'src/profile/components/App';
+import Profile from 'src/pages/Profile';
+import { user } from 'src/features/profile/fixtures';
-import { user } from './fixtures';
-
-describe('App', () => {
+describe('Profile', () => {
const mockedProps = {
user,
};
it('is valid', () => {
- expect(React.isValidElement()).toBe(true);
+ expect(React.isValidElement()).toBe(true);
});
it('renders 2 Col', () => {
- const wrapper = shallow();
+ const wrapper = shallow();
expect(wrapper.find(Row)).toExist();
expect(wrapper.find(Col)).toHaveLength(2);
});
it('renders 4 Tabs', () => {
- const wrapper = shallow();
+ const wrapper = shallow();
expect(wrapper.find('[tab]')).toHaveLength(4);
});
});
diff --git a/superset-frontend/src/profile/components/App.tsx b/superset-frontend/src/pages/Profile/index.tsx
similarity index 90%
rename from superset-frontend/src/profile/components/App.tsx
rename to superset-frontend/src/pages/Profile/index.tsx
index 08130176fe61a..511017941a85b 100644
--- a/superset-frontend/src/profile/components/App.tsx
+++ b/superset-frontend/src/pages/Profile/index.tsx
@@ -21,11 +21,11 @@ import { t, styled } from '@superset-ui/core';
import { Row, Col } from 'src/components';
import Tabs from 'src/components/Tabs';
import { BootstrapUser } from 'src/types/bootstrapTypes';
-import Favorites from './Favorites';
-import UserInfo from './UserInfo';
-import Security from './Security';
-import RecentActivity from './RecentActivity';
-import CreatedContent from './CreatedContent';
+import Favorites from 'src/features/profile/Favorites';
+import UserInfo from 'src/features/profile/UserInfo';
+import Security from 'src/features/profile/Security';
+import RecentActivity from 'src/features/profile/RecentActivity';
+import CreatedContent from 'src/features/profile/CreatedContent';
interface AppProps {
user: BootstrapUser;
diff --git a/superset-frontend/src/profile/App.tsx b/superset-frontend/src/profile/App.tsx
deleted file mode 100644
index ed331ce73725c..0000000000000
--- a/superset-frontend/src/profile/App.tsx
+++ /dev/null
@@ -1,58 +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 { hot } from 'react-hot-loader/root';
-import thunk from 'redux-thunk';
-import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
-import { Provider } from 'react-redux';
-import { ThemeProvider } from '@superset-ui/core';
-import { GlobalStyles } from 'src/GlobalStyles';
-import App from 'src/profile/components/App';
-import messageToastReducer from 'src/components/MessageToasts/reducers';
-import { initEnhancer } from 'src/reduxUtils';
-import setupApp from 'src/setup/setupApp';
-import setupExtensions from 'src/setup/setupExtensions';
-import { theme } from 'src/preamble';
-import ToastContainer from 'src/components/MessageToasts/ToastContainer';
-import getBootstrapData from 'src/utils/getBootstrapData';
-
-setupApp();
-setupExtensions();
-
-const bootstrapData = getBootstrapData();
-
-const store = createStore(
- combineReducers({
- messageToasts: messageToastReducer,
- }),
- {},
- compose(applyMiddleware(thunk), initEnhancer(false)),
-);
-
-const Application = () => (
-
-
-
-
-
-
-
-);
-
-export default hot(Application);
diff --git a/superset-frontend/src/profile/index.tsx b/superset-frontend/src/profile/index.tsx
deleted file mode 100644
index c257009e64fd5..0000000000000
--- a/superset-frontend/src/profile/index.tsx
+++ /dev/null
@@ -1,23 +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 ReactDOM from 'react-dom';
-import App from './App';
-
-ReactDOM.render(, document.getElementById('app'));
diff --git a/superset-frontend/src/views/routes.tsx b/superset-frontend/src/views/routes.tsx
index 23a25a5d855b8..197284d3acb6a 100644
--- a/superset-frontend/src/views/routes.tsx
+++ b/superset-frontend/src/views/routes.tsx
@@ -119,6 +119,10 @@ const RowLevelSecurityList = lazy(
),
);
+const Profile = lazy(
+ () => import(/* webpackChunkName: "Profile" */ 'src/pages/Profile'),
+);
+
type Routes = {
path: string;
Component: React.ComponentType;
@@ -217,6 +221,10 @@ export const routes: Routes = [
path: '/rowlevelsecurity/list',
Component: RowLevelSecurityList,
},
+ {
+ path: '/profile',
+ Component: Profile,
+ },
];
if (isFeatureEnabled(FeatureFlag.TAGGING_SYSTEM)) {
diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js
index fb137c06cbe60..a47670d9d4023 100644
--- a/superset-frontend/webpack.config.js
+++ b/superset-frontend/webpack.config.js
@@ -212,7 +212,6 @@ const config = {
spa: addPreamble('/src/views/index.tsx'),
embedded: addPreamble('/src/embedded/index.tsx'),
sqllab: addPreamble('/src/SqlLab/index.tsx'),
- profile: addPreamble('/src/profile/index.tsx'),
showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
},
output,
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index 828be7840162b..c390a6f779ec4 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -182,6 +182,7 @@ def init_views(self) -> None:
from superset.views.key_value import KV
from superset.views.log.api import LogRestApi
from superset.views.log.views import LogModelView
+ from superset.views.profile import ProfileView
from superset.views.redirects import R
from superset.views.sql_lab.views import (
SavedQueryView,
@@ -309,6 +310,7 @@ def init_views(self) -> None:
appbuilder.add_view_no_menu(ExplorePermalinkView)
appbuilder.add_view_no_menu(KV)
appbuilder.add_view_no_menu(R)
+ appbuilder.add_view_no_menu(ProfileView)
appbuilder.add_view_no_menu(SavedQueryView)
appbuilder.add_view_no_menu(SavedQueryViewApi)
appbuilder.add_view_no_menu(SliceAsync)
diff --git a/superset/views/base.py b/superset/views/base.py
index a2c62df41bf7e..d1c865374a424 100644
--- a/superset/views/base.py
+++ b/superset/views/base.py
@@ -406,7 +406,7 @@ def menu_data(user: User) -> dict[str, Any]:
"user_login_url": appbuilder.get_url_for_login,
"user_profile_url": None
if user.is_anonymous or is_feature_enabled("MENU_HIDE_USER_INFO")
- else "/superset/profile/",
+ else "/profile/",
"locale": session.get("locale", "en"),
},
}
diff --git a/superset/views/core.py b/superset/views/core.py
index d377f94d653c3..366ec6d664e38 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -80,7 +80,6 @@
base_json_conv,
DatasourceType,
get_user_id,
- get_username,
ReservedUrlParameters,
)
from superset.views.base import (
@@ -985,24 +984,9 @@ def welcome(self) -> FlaskResponse:
@has_access
@event_logger.log_this
@expose("/profile/")
+ @deprecated(new_target="/profile")
def profile(self) -> FlaskResponse:
- """User profile page"""
- user = g.user if hasattr(g, "user") and g.user else None
- if not user or security_manager.is_guest_user(user) or user.is_anonymous:
- abort(404)
- payload = {
- "user": bootstrap_user_data(user, include_perms=True),
- "common": common_bootstrap_payload(user),
- }
-
- return self.render_template(
- "superset/basic.html",
- title=_("%(user)s's profile", user=get_username()),
- entry="profile",
- bootstrap_data=json.dumps(
- payload, default=utils.pessimistic_json_iso_dttm_ser
- ),
- )
+ return redirect("/profile/")
@has_access
@event_logger.log_this
diff --git a/superset/views/profile.py b/superset/views/profile.py
new file mode 100644
index 0000000000000..3308a0f645edc
--- /dev/null
+++ b/superset/views/profile.py
@@ -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.
+from flask import abort, g
+from flask_appbuilder import permission_name
+from flask_appbuilder.api import expose
+from flask_appbuilder.security.decorators import has_access
+
+from superset import event_logger, security_manager
+from superset.superset_typing import FlaskResponse
+
+from .base import BaseSupersetView
+
+
+class ProfileView(BaseSupersetView):
+ route_base = "/profile"
+ class_permission_name = "Profile"
+
+ @expose("/")
+ @has_access
+ @permission_name("read")
+ @event_logger.log_this
+ def root(self) -> FlaskResponse:
+ user = g.user if hasattr(g, "user") and g.user else None
+ if not user or security_manager.is_guest_user(user) or user.is_anonymous:
+ abort(404)
+ return super().render_app_template()
diff --git a/tests/integration_tests/core_tests.py b/tests/integration_tests/core_tests.py
index ddcfa189b2b28..f1b1025b0ecd2 100644
--- a/tests/integration_tests/core_tests.py
+++ b/tests/integration_tests/core_tests.py
@@ -26,12 +26,10 @@
from urllib.parse import quote
import pandas as pd
-import prison
import pytest
import pytz
import sqlalchemy as sqla
from flask_babel import lazy_gettext as _
-from sqlalchemy import Table
from sqlalchemy.exc import SQLAlchemyError
import superset.utils.database
@@ -43,10 +41,9 @@
from superset.connectors.sqla.models import SqlaTable
from superset.db_engine_specs.base import BaseEngineSpec
from superset.db_engine_specs.mssql import MssqlEngineSpec
-from superset.exceptions import QueryObjectValidationError, SupersetException
+from superset.exceptions import SupersetException
from superset.extensions import async_query_manager, cache_manager
from superset.models import core as models
-from superset.models.annotations import Annotation, AnnotationLayer
from superset.models.cache import CacheKey
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
@@ -56,9 +53,8 @@
from superset.utils import core as utils
from superset.utils.core import backend
from superset.utils.database import get_example_database
-from superset.views import core as views
from superset.views.database.views import DatabaseView
-from tests.integration_tests.conftest import CTAS_SCHEMA_NAME, with_feature_flags
+from tests.integration_tests.conftest import with_feature_flags
from tests.integration_tests.fixtures.birth_names_dashboard import (
load_birth_names_dashboard_with_slices,
load_birth_names_data,
@@ -67,12 +63,10 @@
load_energy_table_data,
load_energy_table_with_slice,
)
-from tests.integration_tests.fixtures.public_role import public_role_like_gamma
from tests.integration_tests.fixtures.world_bank_dashboard import (
load_world_bank_dashboard_with_slices,
load_world_bank_data,
)
-from tests.integration_tests.insert_chart_mixin import InsertChartMixin
from tests.integration_tests.test_app import app
from .base_tests import SupersetTestCase
@@ -88,7 +82,7 @@ def cleanup():
yield
-class TestCore(SupersetTestCase, InsertChartMixin):
+class TestCore(SupersetTestCase):
def setUp(self):
self.table_ids = {
tbl.table_name: tbl.id for tbl in (db.session.query(SqlaTable).all())
@@ -109,25 +103,6 @@ def insert_dashboard_created_by(self, username: str) -> Dashboard:
)
return dashboard
- def insert_chart_created_by(self, username: str) -> Slice:
- user = self.get_user(username)
- dataset = db.session.query(SqlaTable).first()
- chart = self.insert_chart(
- f"create_title_test",
- [user.id],
- dataset.id,
- created_by=user,
- )
- return chart
-
- @pytest.fixture()
- def insert_dashboard_created_by_admin(self):
- with self.create_app().app_context():
- dashboard = self.insert_dashboard_created_by("admin")
- yield dashboard
- db.session.delete(dashboard)
- db.session.commit()
-
@pytest.fixture()
def insert_dashboard_created_by_gamma(self):
dashboard = self.insert_dashboard_created_by("gamma")
@@ -135,14 +110,6 @@ def insert_dashboard_created_by_gamma(self):
db.session.delete(dashboard)
db.session.commit()
- @pytest.fixture()
- def insert_chart_created_by_admin(self):
- with self.create_app().app_context():
- chart = self.insert_chart_created_by("admin")
- yield chart
- db.session.delete(chart)
- db.session.commit()
-
def test_login(self):
resp = self.get_resp("/login/", data=dict(username="admin", password="general"))
self.assertNotIn("User confirmation needed", resp)
@@ -515,100 +482,6 @@ def test_fetch_datasource_metadata(self):
for k in keys:
self.assertIn(k, resp.keys())
- @pytest.mark.usefixtures("insert_dashboard_created_by_admin")
- @pytest.mark.usefixtures("insert_chart_created_by_admin")
- @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
- def test_user_profile(self, username="admin"):
- self.login(username=username)
- slc = self.get_slice("Girls", db.session)
- dashboard = db.session.query(Dashboard).filter_by(slug="births").first()
- # Set a favorite dashboard
- self.client.post(f"/api/v1/dashboard/{dashboard.id}/favorites/", json={})
- # Set a favorite chart
- self.client.post(f"/api/v1/chart/{slc.id}/favorites/", json={})
-
- # Get favorite dashboards:
- request_query = {
- "columns": ["created_on_delta_humanized", "dashboard_title", "url"],
- "filters": [{"col": "id", "opr": "dashboard_is_favorite", "value": True}],
- "keys": ["none"],
- "order_column": "changed_on",
- "order_direction": "desc",
- "page": 0,
- "page_size": 100,
- }
- url = f"/api/v1/dashboard/?q={prison.dumps(request_query)}"
- resp = self.client.get(url)
- assert resp.json["count"] == 1
- assert resp.json["result"][0]["dashboard_title"] == "USA Births Names"
-
- # Get Favorite Charts
- request_query = {
- "filters": [{"col": "id", "opr": "chart_is_favorite", "value": True}],
- "order_column": "slice_name",
- "order_direction": "asc",
- "page": 0,
- "page_size": 25,
- }
- url = f"api/v1/chart/?q={prison.dumps(request_query)}"
- resp = self.client.get(url)
- assert resp.json["count"] == 1
- assert resp.json["result"][0]["id"] == slc.id
-
- # Get recent activity
- url = "/api/v1/log/recent_activity/?q=(page_size:50)"
- resp = self.client.get(url)
- # TODO data for recent activity varies for sqlite, we should be able to assert
- # the returned data
- assert resp.status_code == 200
-
- # Get dashboards created by the user
- request_query = {
- "columns": ["created_on_delta_humanized", "dashboard_title", "url"],
- "filters": [
- {"col": "created_by", "opr": "dashboard_created_by_me", "value": "me"}
- ],
- "keys": ["none"],
- "order_column": "changed_on",
- "order_direction": "desc",
- "page": 0,
- "page_size": 100,
- }
- url = f"/api/v1/dashboard/?q={prison.dumps(request_query)}"
- resp = self.client.get(url)
- assert resp.json["result"][0]["dashboard_title"] == "create_title_test"
-
- # Get charts created by the user
- request_query = {
- "columns": ["created_on_delta_humanized", "slice_name", "url"],
- "filters": [
- {"col": "created_by", "opr": "chart_created_by_me", "value": "me"}
- ],
- "keys": ["none"],
- "order_column": "changed_on_delta_humanized",
- "order_direction": "desc",
- "page": 0,
- "page_size": 100,
- }
- url = f"/api/v1/chart/?q={prison.dumps(request_query)}"
- resp = self.client.get(url)
- assert resp.json["count"] == 1
- assert resp.json["result"][0]["slice_name"] == "create_title_test"
-
- resp = self.get_resp(f"/superset/profile/")
- self.assertIn('"app"', resp)
-
- def test_user_profile_gamma(self):
- self.login(username="gamma")
- resp = self.get_resp(f"/superset/profile/")
- self.assertIn('"app"', resp)
-
- @pytest.mark.usefixtures("public_role_like_gamma")
- def test_user_profile_anonymous(self):
- self.logout()
- resp = self.client.get("/superset/profile/")
- assert resp.status_code == 404
-
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
def test_slice_id_is_always_logged_correctly_on_web_request(self):
# explore case
@@ -1330,6 +1203,11 @@ def test_has_table_by_name(self):
is True
)
+ def test_redirect_new_profile(self):
+ self.login(username="admin")
+ resp = self.client.get("/superset/profile/")
+ assert resp.status_code == 302
+
if __name__ == "__main__":
unittest.main()
diff --git a/tests/integration_tests/profile_tests.py b/tests/integration_tests/profile_tests.py
new file mode 100644
index 0000000000000..aa5448139e707
--- /dev/null
+++ b/tests/integration_tests/profile_tests.py
@@ -0,0 +1,164 @@
+# 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 prison
+import pytest
+
+from superset import db
+from superset.connectors.sqla.models import SqlaTable
+from superset.models.dashboard import Dashboard
+from superset.models.slice import Slice
+from tests.integration_tests.fixtures.birth_names_dashboard import (
+ load_birth_names_dashboard_with_slices,
+ load_birth_names_data,
+)
+from tests.integration_tests.fixtures.public_role import public_role_like_gamma
+from tests.integration_tests.insert_chart_mixin import InsertChartMixin
+
+from .base_tests import SupersetTestCase
+
+
+class TestProfile(SupersetTestCase, InsertChartMixin):
+ def insert_dashboard_created_by(self, username: str) -> Dashboard:
+ user = self.get_user(username)
+ dashboard = self.insert_dashboard(
+ f"create_title_test",
+ f"create_slug_test",
+ [user.id],
+ created_by=user,
+ )
+ return dashboard
+
+ @pytest.fixture()
+ def insert_dashboard_created_by_admin(self):
+ with self.create_app().app_context():
+ dashboard = self.insert_dashboard_created_by("admin")
+ yield dashboard
+ db.session.delete(dashboard)
+ db.session.commit()
+
+ def insert_chart_created_by(self, username: str) -> Slice:
+ user = self.get_user(username)
+ dataset = db.session.query(SqlaTable).first()
+ chart = self.insert_chart(
+ f"create_title_test",
+ [user.id],
+ dataset.id,
+ created_by=user,
+ )
+ return chart
+
+ @pytest.fixture()
+ def insert_chart_created_by_admin(self):
+ with self.create_app().app_context():
+ chart = self.insert_chart_created_by("admin")
+ yield chart
+ db.session.delete(chart)
+ db.session.commit()
+
+ @pytest.mark.usefixtures("insert_dashboard_created_by_admin")
+ @pytest.mark.usefixtures("insert_chart_created_by_admin")
+ @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
+ def test_user_profile(self, username="admin"):
+ self.login(username=username)
+ slc = self.get_slice("Girls", db.session)
+ dashboard = db.session.query(Dashboard).filter_by(slug="births").first()
+ # Set a favorite dashboard
+ self.client.post(f"/api/v1/dashboard/{dashboard.id}/favorites/", json={})
+ # Set a favorite chart
+ self.client.post(f"/api/v1/chart/{slc.id}/favorites/", json={})
+
+ # Get favorite dashboards:
+ request_query = {
+ "columns": ["created_on_delta_humanized", "dashboard_title", "url"],
+ "filters": [{"col": "id", "opr": "dashboard_is_favorite", "value": True}],
+ "keys": ["none"],
+ "order_column": "changed_on",
+ "order_direction": "desc",
+ "page": 0,
+ "page_size": 100,
+ }
+ url = f"/api/v1/dashboard/?q={prison.dumps(request_query)}"
+ resp = self.client.get(url)
+ assert resp.json["count"] == 1
+ assert resp.json["result"][0]["dashboard_title"] == "USA Births Names"
+
+ # Get Favorite Charts
+ request_query = {
+ "filters": [{"col": "id", "opr": "chart_is_favorite", "value": True}],
+ "order_column": "slice_name",
+ "order_direction": "asc",
+ "page": 0,
+ "page_size": 25,
+ }
+ url = f"api/v1/chart/?q={prison.dumps(request_query)}"
+ resp = self.client.get(url)
+ assert resp.json["count"] == 1
+ assert resp.json["result"][0]["id"] == slc.id
+
+ # Get recent activity
+ url = "/api/v1/log/recent_activity/?q=(page_size:50)"
+ resp = self.client.get(url)
+ # TODO data for recent activity varies for sqlite, we should be able to assert
+ # the returned data
+ assert resp.status_code == 200
+
+ # Get dashboards created by the user
+ request_query = {
+ "columns": ["created_on_delta_humanized", "dashboard_title", "url"],
+ "filters": [
+ {"col": "created_by", "opr": "dashboard_created_by_me", "value": "me"}
+ ],
+ "keys": ["none"],
+ "order_column": "changed_on",
+ "order_direction": "desc",
+ "page": 0,
+ "page_size": 100,
+ }
+ url = f"/api/v1/dashboard/?q={prison.dumps(request_query)}"
+ resp = self.client.get(url)
+ assert resp.json["result"][0]["dashboard_title"] == "create_title_test"
+
+ # Get charts created by the user
+ request_query = {
+ "columns": ["created_on_delta_humanized", "slice_name", "url"],
+ "filters": [
+ {"col": "created_by", "opr": "chart_created_by_me", "value": "me"}
+ ],
+ "keys": ["none"],
+ "order_column": "changed_on_delta_humanized",
+ "order_direction": "desc",
+ "page": 0,
+ "page_size": 100,
+ }
+ url = f"/api/v1/chart/?q={prison.dumps(request_query)}"
+ resp = self.client.get(url)
+ assert resp.json["count"] == 1
+ assert resp.json["result"][0]["slice_name"] == "create_title_test"
+
+ resp = self.get_resp(f"/profile/")
+ self.assertIn('"app"', resp)
+
+ def test_user_profile_gamma(self):
+ self.login(username="gamma")
+ resp = self.get_resp(f"/profile/")
+ self.assertIn('"app"', resp)
+
+ @pytest.mark.usefixtures("public_role_like_gamma")
+ def test_user_profile_anonymous(self):
+ self.logout()
+ resp = self.client.get("/profile/")
+ assert resp.status_code == 404