You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2026/04/22 18:15:07 UTC

(superset) branch master updated: feat(user_info): include Groups in user data payload when include_perms is True and show Groups on user_info page (#39450)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4ee3a0fc074 feat(user_info): include Groups in user data payload when include_perms is True and show Groups on user_info page (#39450)
4ee3a0fc074 is described below

commit 4ee3a0fc0741798ca3d1aafcd96958724133cb5b
Author: Declan Zhao <de...@robinhood.com>
AuthorDate: Wed Apr 22 14:14:59 2026 -0400

    feat(user_info): include Groups in user data payload when include_perms is True and show Groups on user_info page (#39450)
---
 .../src/SqlLab/reducers/getInitialState.test.ts             |  1 +
 .../src/dashboard/util/permissionUtils.test.ts              |  1 +
 .../src/pages/ChartCreation/ChartCreation.test.tsx          |  2 ++
 superset-frontend/src/pages/SqlLab/SqlLab.test.tsx          |  1 +
 superset-frontend/src/pages/UserInfo/UserInfo.test.tsx      | 13 +++++++++++--
 superset-frontend/src/pages/UserInfo/index.tsx              |  5 ++++-
 superset-frontend/src/types/bootstrapTypes.ts               |  1 +
 superset/views/utils.py                                     |  1 +
 tests/integration_tests/users/api_tests.py                  |  2 ++
 9 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.test.ts b/superset-frontend/src/SqlLab/reducers/getInitialState.test.ts
index c0eda235d2d..8533f4ec795 100644
--- a/superset-frontend/src/SqlLab/reducers/getInitialState.test.ts
+++ b/superset-frontend/src/SqlLab/reducers/getInitialState.test.ts
@@ -34,6 +34,7 @@ const apiData = {
     lastName: 'last name',
     permissions: {},
     roles: {},
+    groups: [],
   },
 };
 const apiDataWithTabState = {
diff --git a/superset-frontend/src/dashboard/util/permissionUtils.test.ts b/superset-frontend/src/dashboard/util/permissionUtils.test.ts
index e7d9d133900..2bf2ea369f5 100644
--- a/superset-frontend/src/dashboard/util/permissionUtils.test.ts
+++ b/superset-frontend/src/dashboard/util/permissionUtils.test.ts
@@ -41,6 +41,7 @@ const ownerUser: UserWithPermissionsAndRoles = {
   username: 'owner',
   permissions: {},
   roles: { Alpha: [['can_write', 'Dashboard']] },
+  groups: [],
 };
 
 const adminUser: UserWithPermissionsAndRoles = {
diff --git a/superset-frontend/src/pages/ChartCreation/ChartCreation.test.tsx b/superset-frontend/src/pages/ChartCreation/ChartCreation.test.tsx
index f3e1fc231c9..ba2518c3da0 100644
--- a/superset-frontend/src/pages/ChartCreation/ChartCreation.test.tsx
+++ b/superset-frontend/src/pages/ChartCreation/ChartCreation.test.tsx
@@ -64,6 +64,7 @@ const mockUser: UserWithPermissionsAndRoles = {
   userId: 1,
   username: 'admin',
   isAnonymous: false,
+  groups: [],
 };
 
 const mockUserWithDatasetWrite: UserWithPermissionsAndRoles = {
@@ -77,6 +78,7 @@ const mockUserWithDatasetWrite: UserWithPermissionsAndRoles = {
   userId: 1,
   username: 'admin',
   isAnonymous: false,
+  groups: [],
 };
 const history = createMemoryHistory();
 
diff --git a/superset-frontend/src/pages/SqlLab/SqlLab.test.tsx b/superset-frontend/src/pages/SqlLab/SqlLab.test.tsx
index 50b0a49e8b4..819e0323be0 100644
--- a/superset-frontend/src/pages/SqlLab/SqlLab.test.tsx
+++ b/superset-frontend/src/pages/SqlLab/SqlLab.test.tsx
@@ -48,6 +48,7 @@ const fakeApiResult = {
       lastName: 'last name',
       permissions: {},
       roles: {},
+      groups: [],
     },
   },
 };
diff --git a/superset-frontend/src/pages/UserInfo/UserInfo.test.tsx b/superset-frontend/src/pages/UserInfo/UserInfo.test.tsx
index f445d998a27..d15b7090e23 100644
--- a/superset-frontend/src/pages/UserInfo/UserInfo.test.tsx
+++ b/superset-frontend/src/pages/UserInfo/UserInfo.test.tsx
@@ -51,6 +51,7 @@ const mockUser: UserWithPermissionsAndRoles = {
       ['can_write', 'Chart'],
     ],
   },
+  groups: ['Engineering', 'Analytics'],
   createdOn: new Date().toISOString(),
   isAnonymous: false,
   permissions: {
@@ -61,12 +62,12 @@ const mockUser: UserWithPermissionsAndRoles = {
 
 // eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
 describe('UserInfo', () => {
-  const renderPage = async () =>
+  const renderPage = async (user: UserWithPermissionsAndRoles = mockUser) =>
     act(async () => {
       render(
         <MemoryRouter>
           <QueryParamProvider adapter={ReactRouter5Adapter}>
-            <UserInfo user={mockUser} />
+            <UserInfo user={user} />
           </QueryParamProvider>
         </MemoryRouter>,
         { useRedux: true, store },
@@ -97,12 +98,20 @@ describe('UserInfo', () => {
     expect(screen.getByText('johndoe')).toBeInTheDocument();
     expect(screen.getByText('Yes')).toBeInTheDocument();
     expect(screen.getByText('Admin')).toBeInTheDocument();
+    expect(screen.getByText('Engineering, Analytics')).toBeInTheDocument();
     expect(screen.getByText('12')).toBeInTheDocument();
     expect(await screen.findByText('John')).toBeInTheDocument();
     expect(screen.getByText('Doe')).toBeInTheDocument();
     expect(screen.getByText('john@example.com')).toBeInTheDocument();
   });
 
+  test('renders "None" when the user has no groups', async () => {
+    await renderPage({ ...mockUser, groups: [] });
+
+    expect(await screen.findByText('Groups')).toBeInTheDocument();
+    expect(screen.getByText('None')).toBeInTheDocument();
+  });
+
   test('calls the /me endpoint on mount', async () => {
     await renderPage();
     await waitFor(() => {
diff --git a/superset-frontend/src/pages/UserInfo/index.tsx b/superset-frontend/src/pages/UserInfo/index.tsx
index a5d70f6ff86..cc8d33d68e0 100644
--- a/superset-frontend/src/pages/UserInfo/index.tsx
+++ b/superset-frontend/src/pages/UserInfo/index.tsx
@@ -190,9 +190,12 @@ export function UserInfo({ user }: { user: UserWithPermissionsAndRoles }) {
               <Descriptions.Item label={t('Is Active?')}>
                 {user.isActive ? t('Yes') : t('No')}
               </Descriptions.Item>
-              <Descriptions.Item label={t('Role')}>
+              <Descriptions.Item label={t('Roles')}>
                 {user.roles ? Object.keys(user.roles).join(', ') : t('None')}
               </Descriptions.Item>
+              <Descriptions.Item label={t('Groups')}>
+                {user.groups.length ? user.groups.join(', ') : t('None')}
+              </Descriptions.Item>
               <Descriptions.Item label={t('Login count')}>
                 {user.loginCount}
               </Descriptions.Item>
diff --git a/superset-frontend/src/types/bootstrapTypes.ts b/superset-frontend/src/types/bootstrapTypes.ts
index daf8f46bacc..610767c389a 100644
--- a/superset-frontend/src/types/bootstrapTypes.ts
+++ b/superset-frontend/src/types/bootstrapTypes.ts
@@ -55,6 +55,7 @@ export interface PermissionsAndRoles {
     datasource_access?: string[];
   };
   roles: UserRoles;
+  groups: string[];
 }
 
 export type UserWithPermissionsAndRoles = User & PermissionsAndRoles;
diff --git a/superset/views/utils.py b/superset/views/utils.py
index 56f0a567cf9..8d8f1170285 100644
--- a/superset/views/utils.py
+++ b/superset/views/utils.py
@@ -133,6 +133,7 @@ def bootstrap_user_data(user: User, include_perms: bool = False) -> dict[str, An
         roles, permissions = get_permissions(user)
         payload["roles"] = roles
         payload["permissions"] = permissions
+        payload["groups"] = [group.name for group in getattr(user, "groups", [])]
 
     return payload
 
diff --git a/tests/integration_tests/users/api_tests.py b/tests/integration_tests/users/api_tests.py
index 0b6822ebbf3..85985f9f35b 100644
--- a/tests/integration_tests/users/api_tests.py
+++ b/tests/integration_tests/users/api_tests.py
@@ -50,6 +50,8 @@ class TestCurrentUserApi(SupersetTestCase):
         response = json.loads(rv.data.decode("utf-8"))
         roles = list(response["result"]["roles"].keys())
         assert "Admin" == roles.pop()
+        assert "groups" in response["result"]
+        assert isinstance(response["result"]["groups"], list)
 
     @patch("superset.security.manager.g")
     def test_get_my_roles_anonymous(self, mock_g):