Fix #22067 : Domain inheritance issue after team removal (#22146)

* fix Domain inheritance issue after team removal

* add backend test and fix logic

* added playwright test

* update domain on reload

* fix test

* fix test

---------

Co-authored-by: Shrushti Polekar <shrushtipolekar@gmail.com>
This commit is contained in:
sonika-shah 2025-07-08 12:26:19 +05:30 committed by GitHub
parent 2177bc2e03
commit 56b1719494
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 194 additions and 2 deletions

View File

@ -1062,8 +1062,12 @@ public class UserRepository extends EntityRepository<User> {
List<EntityReference> origDomains =
EntityUtil.populateEntityReferences(listOrEmptyMutable(original.getDomains()));
// Skip domains inherited from teams,they are handled in setInheritedFields().
List<EntityReference> updatedDomains =
EntityUtil.populateEntityReferences(listOrEmptyMutable(updated.getDomains()));
EntityUtil.populateEntityReferences(listOrEmptyMutable(updated.getDomains())).stream()
.filter(domain -> domain.getInherited() == null || !domain.getInherited())
.collect(Collectors.toList());
updated.setDomains(updatedDomains);
// Remove Domains for the user
deleteTo(original.getId(), USER, Relationship.HAS, Entity.DOMAIN);

View File

@ -1705,6 +1705,91 @@ public class UserResourceTest extends EntityResourceTest<User, CreateUser> {
assertEquals(PIIMasker.MASKED_MAIL, noEmailUser.getEmail());
}
@Test
void testUpdateUser_RemovesInheritedDomainsFromRemovedTeams(TestInfo test)
throws HttpResponseException {
TeamResourceTest teamResourceTest = new TeamResourceTest();
// Create team1 with domain1
CreateTeam createTeam1 =
teamResourceTest.createRequest(test).withDomains(List.of(DOMAIN.getFullyQualifiedName()));
Team team1 = teamResourceTest.createEntity(createTeam1, ADMIN_AUTH_HEADERS);
// Create team2 with domain2
CreateTeam createTeam2 =
teamResourceTest
.createRequest(test, 1)
.withDomains(List.of(DOMAIN1.getFullyQualifiedName()));
Team team2 = teamResourceTest.createEntity(createTeam2, ADMIN_AUTH_HEADERS);
// Create user with both teams
CreateUser create = createRequest(test).withTeams(listOf(team1.getId(), team2.getId()));
User user = createEntity(create, ADMIN_AUTH_HEADERS);
// Verify user has both domains inherited from the teams
User createdUser = getEntity(user.getId(), FIELD_DOMAINS, ADMIN_AUTH_HEADERS);
assertNotNull(createdUser.getDomains());
assertEquals(2, createdUser.getDomains().size());
// Check that both domains are inherited
List<EntityReference> domains = createdUser.getDomains();
boolean hasDomain1 =
domains.stream().anyMatch(d -> d.getId().equals(DOMAIN.getId()) && d.getInherited());
boolean hasDomain2 =
domains.stream().anyMatch(d -> d.getId().equals(DOMAIN1.getId()) && d.getInherited());
assertTrue(hasDomain1, "User should inherit domain from team1");
assertTrue(hasDomain2, "User should inherit domain from team2");
// Scenario 1: Remove team2 from user - domain2 should be removed as well
String userJson = JsonUtils.pojoToJson(createdUser);
createdUser.setTeams(List.of(team1.getEntityReference()));
// Update user to only have team1
User updatedUser = patchEntity(createdUser.getId(), userJson, createdUser, ADMIN_AUTH_HEADERS);
updatedUser = getEntity(updatedUser.getId(), FIELD_DOMAINS, ADMIN_AUTH_HEADERS);
// Verify that domain2 is no longer in the domains list
assertEquals(1, updatedUser.getDomains().size());
assertEquals(DOMAIN.getId(), updatedUser.getDomains().get(0).getId());
assertTrue(updatedUser.getDomains().get(0).getInherited());
// Scenario 2: Create team3 with the same domain as team1 (domain1)
// and verify that when one team is removed, domain remains inherited
CreateTeam createTeam3 =
teamResourceTest
.createRequest(test, 2)
.withDomains(List.of(DOMAIN.getFullyQualifiedName()));
Team team3 = teamResourceTest.createEntity(createTeam3, ADMIN_AUTH_HEADERS);
// Add user to both teams that have the same domain
userJson = JsonUtils.pojoToJson(updatedUser);
updatedUser.setTeams(List.of(team1.getEntityReference(), team3.getEntityReference()));
User userWithBothTeams =
patchEntity(updatedUser.getId(), userJson, updatedUser, ADMIN_AUTH_HEADERS);
userWithBothTeams = getEntity(userWithBothTeams.getId(), FIELD_DOMAINS, ADMIN_AUTH_HEADERS);
// Still should have just domain1
assertEquals(1, userWithBothTeams.getDomains().size());
assertEquals(DOMAIN.getId(), userWithBothTeams.getDomains().get(0).getId());
assertTrue(userWithBothTeams.getDomains().get(0).getInherited());
// Remove team1, but keep team3 - domain1 should still be inherited from team3
userJson = JsonUtils.pojoToJson(userWithBothTeams);
userWithBothTeams.setTeams(List.of(team3.getEntityReference()));
User userWithTeam3 =
patchEntity(userWithBothTeams.getId(), userJson, userWithBothTeams, ADMIN_AUTH_HEADERS);
userWithTeam3 = getEntity(userWithTeam3.getId(), FIELD_DOMAINS, ADMIN_AUTH_HEADERS);
// Should still have domain1 inherited from team3
assertEquals(1, userWithTeam3.getDomains().size());
assertEquals(DOMAIN.getId(), userWithTeam3.getDomains().get(0).getId());
assertTrue(
userWithTeam3.getDomains().get(0).getInherited(),
"Domain should still be marked as inherited");
}
private DecodedJWT decodedJWT(String token) {
DecodedJWT jwt;
try {

View File

@ -109,6 +109,102 @@ test.describe('User with different Roles', () => {
);
});
test('Create team with domain and verify visibility of inherited domain in user profile after team removal', async ({
adminPage,
}) => {
await redirectToUserPage(adminPage);
await adminPage.waitForLoadState('networkidle');
await expect(adminPage.getByTestId('user-profile-teams')).toBeVisible();
await adminPage.getByTestId('edit-teams-button').click();
await expect(adminPage.getByTestId('team-select')).toBeVisible();
await adminPage.getByTestId('team-select').click();
await adminPage.waitForSelector('.ant-tree-select-dropdown', {
state: 'visible',
});
await adminPage.getByText(team.responseData.displayName).click();
const updateTeamsResponse = adminPage.waitForResponse(
(response) =>
response.url().includes('/api/v1/users/') &&
response.request().method() === 'PATCH'
);
await adminPage.getByTestId('teams-edit-save-btn').click();
await updateTeamsResponse;
await expect(adminPage.getByTestId('user-profile-teams')).toContainText(
team.responseData.displayName
);
await adminPage.getByText(team.responseData.displayName).first().click();
await adminPage.waitForLoadState('networkidle');
const domainResponse = adminPage.waitForResponse((response) =>
response.url().includes('/api/v1/domains/hierarchy')
);
await adminPage.getByTestId('add-domain').click();
await domainResponse;
await adminPage.getByText(domain.responseData.displayName).click();
const teamsResponse = adminPage.waitForResponse(
(response) =>
response.url().includes('/api/v1/teams/') &&
response.request().method() === 'PATCH'
);
await adminPage.getByText('Update').click();
await teamsResponse;
await redirectToUserPage(adminPage);
await adminPage.waitForLoadState('networkidle');
await expect(adminPage.getByTestId('user-profile-teams')).toContainText(
team.responseData.displayName
);
await expect(
adminPage.locator('[data-testid="header-domain-container"]')
).toContainText(domain.responseData.displayName);
await adminPage.getByTestId('edit-teams-button').click();
await adminPage.getByTestId('team-select').click();
await adminPage.waitForSelector('.ant-tree-select-dropdown', {
state: 'visible',
});
await adminPage
.locator('[title="' + team.responseData.displayName + '"]')
.first()
.click();
const userProfileResponse = adminPage.waitForResponse((response) =>
response.url().includes('/api/v1/users/')
);
await adminPage.getByTestId('teams-edit-save-btn').click({ force: true });
await userProfileResponse;
await expect(
adminPage.locator('[data-testid="header-domain-container"]')
).not.toContainText(domain.responseData.displayName);
});
test('User can search for a domain', async ({ adminPage }) => {
await redirectToUserPage(adminPage);

View File

@ -19,7 +19,7 @@ export const redirectToUserPage = async (page: Page) => {
await page.getByTestId('dropdown-profile').click();
// Hover on the profile avatar to close the name tooltip
await page.getByTestId('profile-avatar').hover();
await page.getByTestId('profile-avatar').first().hover();
await page.waitForSelector('.profile-dropdown', { state: 'visible' });

View File

@ -128,6 +128,13 @@ const UserPage = () => {
roles: response.roles,
isAdmin: response.isAdmin,
};
} else if (key === 'teams') {
// Handle teams update - this affects inherited domains
updatedKeyData = {
[key]: response[key],
// Also update domains since they are inherited from teams
domains: response.domains,
};
} else {
updatedKeyData = { [key]: response[key] };
}