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(
-
+
,
{ 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 }) {
{user.isActive ? t('Yes') : t('No')}
-
+
{user.roles ? Object.keys(user.roles).join(', ') : t('None')}
+
+ {user.groups.length ? user.groups.join(', ') : t('None')}
+
{user.loginCount}
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):