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):