mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-11 00:35:22 +00:00
This commit is contained in:
parent
c51711d292
commit
6cd30ce945
@ -61,6 +61,10 @@ public final class CatalogExceptionMessage {
|
|||||||
return String.format("User %s is deactivated", id);
|
return String.format("User %s is deactivated", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String userAlreadyPartOfTeam(String userName, String teamName) {
|
||||||
|
return String.format("User '%s' is already part of the team '%s'", userName, teamName);
|
||||||
|
}
|
||||||
|
|
||||||
public static String invalidColumnFQN(String fqn) {
|
public static String invalidColumnFQN(String fqn) {
|
||||||
return String.format("Invalid fully qualified column name %s", fqn);
|
return String.format("Invalid fully qualified column name %s", fqn);
|
||||||
}
|
}
|
||||||
|
@ -87,8 +87,7 @@ public class DashboardRepository extends EntityRepository<Dashboard> {
|
|||||||
.withId(original.getId())
|
.withId(original.getId())
|
||||||
.withFullyQualifiedName(original.getFullyQualifiedName())
|
.withFullyQualifiedName(original.getFullyQualifiedName())
|
||||||
.withName(original.getName())
|
.withName(original.getName())
|
||||||
.withService(original.getService())
|
.withService(original.getService());
|
||||||
.withId(original.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntityReference getService(Dashboard dashboard) throws IOException {
|
private EntityReference getService(Dashboard dashboard) throws IOException {
|
||||||
|
@ -21,6 +21,7 @@ import java.net.URI;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -29,6 +30,7 @@ import org.jdbi.v3.sqlobject.transaction.Transaction;
|
|||||||
import org.openmetadata.catalog.Entity;
|
import org.openmetadata.catalog.Entity;
|
||||||
import org.openmetadata.catalog.entity.teams.Team;
|
import org.openmetadata.catalog.entity.teams.Team;
|
||||||
import org.openmetadata.catalog.entity.teams.User;
|
import org.openmetadata.catalog.entity.teams.User;
|
||||||
|
import org.openmetadata.catalog.exception.CatalogExceptionMessage;
|
||||||
import org.openmetadata.catalog.resources.teams.UserResource;
|
import org.openmetadata.catalog.resources.teams.UserResource;
|
||||||
import org.openmetadata.catalog.type.ChangeDescription;
|
import org.openmetadata.catalog.type.ChangeDescription;
|
||||||
import org.openmetadata.catalog.type.EntityReference;
|
import org.openmetadata.catalog.type.EntityReference;
|
||||||
@ -83,6 +85,12 @@ public class UserRepository extends EntityRepository<User> {
|
|||||||
user.setRoles(rolesRef);
|
user.setRoles(rolesRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restorePatchAttributes(User original, User updated) {
|
||||||
|
// Patch can't make changes to following fields. Ignore the changes
|
||||||
|
updated.withId(original.getId()).withName(original.getName());
|
||||||
|
}
|
||||||
|
|
||||||
private List<EntityReference> getTeamDefaultRoles(User user) throws IOException {
|
private List<EntityReference> getTeamDefaultRoles(User user) throws IOException {
|
||||||
List<EntityReference> teamsRef = listOrEmpty(user.getTeams());
|
List<EntityReference> teamsRef = listOrEmpty(user.getTeams());
|
||||||
List<EntityReference> defaultRoles = new ArrayList<>();
|
List<EntityReference> defaultRoles = new ArrayList<>();
|
||||||
@ -137,6 +145,22 @@ public class UserRepository extends EntityRepository<User> {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTeamJoinable(String teamId) throws IOException {
|
||||||
|
Team team = daoCollection.teamDAO().findEntityById(UUID.fromString(teamId), Include.NON_DELETED);
|
||||||
|
return team.getIsJoinable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate if the user is already part of the given team */
|
||||||
|
public void validateTeamAddition(String userId, String teamId) throws IOException {
|
||||||
|
User user = dao.findEntityById(UUID.fromString(userId));
|
||||||
|
List<EntityReference> teams = getTeams(user);
|
||||||
|
Optional<EntityReference> team = teams.stream().filter(t -> t.getId().equals(UUID.fromString(teamId))).findFirst();
|
||||||
|
if (team.isPresent()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
CatalogExceptionMessage.userAlreadyPartOfTeam(user.getName(), team.get().getDisplayName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<EntityReference> getOwns(User user) throws IOException {
|
private List<EntityReference> getOwns(User user) throws IOException {
|
||||||
// Compile entities owned by the user
|
// Compile entities owned by the user
|
||||||
List<EntityReference> ownedEntities =
|
List<EntityReference> ownedEntities =
|
||||||
@ -287,6 +311,11 @@ public class UserRepository extends EntityRepository<User> {
|
|||||||
entity.setName(name);
|
entity.setName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityReference getOwner() {
|
||||||
|
return Entity.getEntityReference(entity);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUpdateDetails(String updatedBy, long updatedAt) {
|
public void setUpdateDetails(String updatedBy, long updatedAt) {
|
||||||
entity.setUpdatedBy(updatedBy);
|
entity.setUpdatedBy(updatedBy);
|
||||||
|
@ -372,6 +372,25 @@ public class UserResource extends EntityResource<User, UserRepository> {
|
|||||||
if (path.equals("/isAdmin") || path.equals("/isBot")) {
|
if (path.equals("/isAdmin") || path.equals("/isBot")) {
|
||||||
SecurityUtil.authorizeAdmin(authorizer, securityContext, ADMIN | BOT);
|
SecurityUtil.authorizeAdmin(authorizer, securityContext, ADMIN | BOT);
|
||||||
}
|
}
|
||||||
|
// if path contains team, check if team is joinable by any user
|
||||||
|
if (patchOpObject.containsKey("op")
|
||||||
|
&& patchOpObject.getString("op").equals("add")
|
||||||
|
&& path.startsWith("/teams/")) {
|
||||||
|
JsonObject value = null;
|
||||||
|
try {
|
||||||
|
value = patchOpObject.getJsonObject("value");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// ignore exception if value is not an object
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
String teamId = value.getString("id");
|
||||||
|
dao.validateTeamAddition(id, teamId);
|
||||||
|
if (!dao.isTeamJoinable(teamId)) {
|
||||||
|
// Only admin can join closed teams
|
||||||
|
SecurityUtil.authorizeAdmin(authorizer, securityContext, ADMIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return patchInternal(uriInfo, securityContext, id, patch);
|
return patchInternal(uriInfo, securityContext, id, patch);
|
||||||
|
@ -1015,7 +1015,10 @@ public abstract class EntityResourceTest<T, K> extends CatalogApplicationTest {
|
|||||||
// Create entity without description, owner
|
// Create entity without description, owner
|
||||||
T entity = createEntity(createRequest(getEntityName(test), "", null, null), ADMIN_AUTH_HEADERS);
|
T entity = createEntity(createRequest(getEntityName(test), "", null, null), ADMIN_AUTH_HEADERS);
|
||||||
EntityInterface<T> entityInterface = getEntityInterface(entity);
|
EntityInterface<T> entityInterface = getEntityInterface(entity);
|
||||||
|
// user will always have the same user assigned as the owner
|
||||||
|
if (!entityInterface.getEntityType().equals(Entity.USER)) {
|
||||||
assertListNull(entityInterface.getOwner());
|
assertListNull(entityInterface.getOwner());
|
||||||
|
}
|
||||||
|
|
||||||
entity = getEntity(entityInterface.getId(), ADMIN_AUTH_HEADERS);
|
entity = getEntity(entityInterface.getId(), ADMIN_AUTH_HEADERS);
|
||||||
entityInterface = getEntityInterface(entity);
|
entityInterface = getEntityInterface(entity);
|
||||||
|
@ -792,7 +792,10 @@ public class UserResourceTest extends EntityResourceTest<User, CreateUser> {
|
|||||||
@Override
|
@Override
|
||||||
public void validateCreatedEntity(User user, CreateUser createRequest, Map<String, String> authHeaders) {
|
public void validateCreatedEntity(User user, CreateUser createRequest, Map<String, String> authHeaders) {
|
||||||
validateCommonEntityFields(
|
validateCommonEntityFields(
|
||||||
getEntityInterface(user), createRequest.getDescription(), TestUtils.getPrincipal(authHeaders), null);
|
getEntityInterface(user),
|
||||||
|
createRequest.getDescription(),
|
||||||
|
TestUtils.getPrincipal(authHeaders),
|
||||||
|
Entity.getEntityReference(user));
|
||||||
|
|
||||||
assertEquals(createRequest.getName(), user.getName());
|
assertEquals(createRequest.getName(), user.getName());
|
||||||
assertEquals(createRequest.getDisplayName(), user.getDisplayName());
|
assertEquals(createRequest.getDisplayName(), user.getDisplayName());
|
||||||
@ -825,7 +828,10 @@ public class UserResourceTest extends EntityResourceTest<User, CreateUser> {
|
|||||||
@Override
|
@Override
|
||||||
public void compareEntities(User expected, User updated, Map<String, String> authHeaders) {
|
public void compareEntities(User expected, User updated, Map<String, String> authHeaders) {
|
||||||
validateCommonEntityFields(
|
validateCommonEntityFields(
|
||||||
getEntityInterface(expected), expected.getDescription(), TestUtils.getPrincipal(authHeaders), null);
|
getEntityInterface(expected),
|
||||||
|
expected.getDescription(),
|
||||||
|
TestUtils.getPrincipal(authHeaders),
|
||||||
|
Entity.getEntityReference(expected));
|
||||||
|
|
||||||
assertEquals(expected.getName(), expected.getName());
|
assertEquals(expected.getName(), expected.getName());
|
||||||
assertEquals(expected.getDisplayName(), expected.getDisplayName());
|
assertEquals(expected.getDisplayName(), expected.getDisplayName());
|
||||||
|
@ -72,6 +72,7 @@ const TeamDetails = ({
|
|||||||
handleTeamUsersSearchAction,
|
handleTeamUsersSearchAction,
|
||||||
teamUserPaginHandler,
|
teamUserPaginHandler,
|
||||||
handleJoinTeamClick,
|
handleJoinTeamClick,
|
||||||
|
handleLeaveTeamClick,
|
||||||
handleAddUser,
|
handleAddUser,
|
||||||
removeUserFromTeam,
|
removeUserFromTeam,
|
||||||
}: TeamDetailsProp) => {
|
}: TeamDetailsProp) => {
|
||||||
@ -164,12 +165,6 @@ const TeamDetails = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveUser = () => {
|
|
||||||
removeUserFromTeam(deletingUser.user?.id as string).then(() => {
|
|
||||||
setDeletingUser(DELETE_USER_INITIAL_STATE);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAlreadyJoinedTeam = (teamId: string) => {
|
const isAlreadyJoinedTeam = (teamId: string) => {
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
return currentUser.teams?.find((team) => team.id === teamId);
|
return currentUser.teams?.find((team) => team.id === teamId);
|
||||||
@ -196,6 +191,7 @@ const TeamDetails = ({
|
|||||||
newTeams.push({
|
newTeams.push({
|
||||||
id: currentTeam.id,
|
id: currentTeam.id,
|
||||||
type: OwnerType.TEAM,
|
type: OwnerType.TEAM,
|
||||||
|
name: currentTeam.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedData: User = {
|
const updatedData: User = {
|
||||||
@ -209,6 +205,36 @@ const TeamDetails = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const leaveTeam = (): Promise<void> => {
|
||||||
|
if (currentUser && currentTeam) {
|
||||||
|
let newTeams = cloneDeep(currentUser.teams ?? []);
|
||||||
|
newTeams = newTeams.filter((team) => team.id !== currentTeam.id);
|
||||||
|
|
||||||
|
const updatedData: User = {
|
||||||
|
...currentUser,
|
||||||
|
teams: newTeams,
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = compare(currentUser, updatedData);
|
||||||
|
|
||||||
|
return handleLeaveTeamClick(currentUser.id, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveUser = () => {
|
||||||
|
if (deletingUser.leave) {
|
||||||
|
leaveTeam().then(() => {
|
||||||
|
setDeletingUser(DELETE_USER_INITIAL_STATE);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
removeUserFromTeam(deletingUser.user?.id as string).then(() => {
|
||||||
|
setDeletingUser(DELETE_USER_INITIAL_STATE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleManageSave = (owner: TableDetail['owner']) => {
|
const handleManageSave = (owner: TableDetail['owner']) => {
|
||||||
if (currentTeam) {
|
if (currentTeam) {
|
||||||
const updatedData: Team = {
|
const updatedData: Team = {
|
||||||
@ -240,6 +266,7 @@ const TeamDetails = ({
|
|||||||
/**
|
/**
|
||||||
* Take user id as input to find out the user data and set it for delete
|
* Take user id as input to find out the user data and set it for delete
|
||||||
* @param id - user id
|
* @param id - user id
|
||||||
|
* @param leave - if "Leave Team" action is in progress
|
||||||
*/
|
*/
|
||||||
const deleteUserHandler = (id: string, leave = false) => {
|
const deleteUserHandler = (id: string, leave = false) => {
|
||||||
const user = [...(currentTeam?.users as Array<UserTeams>)].find(
|
const user = [...(currentTeam?.users as Array<UserTeams>)].find(
|
||||||
|
@ -38,6 +38,7 @@ const TeamsAndUsers = ({
|
|||||||
handleUserSearchTerm,
|
handleUserSearchTerm,
|
||||||
handleDeleteUser,
|
handleDeleteUser,
|
||||||
handleJoinTeamClick,
|
handleJoinTeamClick,
|
||||||
|
handleLeaveTeamClick,
|
||||||
isRightPannelLoading,
|
isRightPannelLoading,
|
||||||
hasAccess,
|
hasAccess,
|
||||||
isTeamVisible,
|
isTeamVisible,
|
||||||
@ -210,6 +211,7 @@ const TeamsAndUsers = ({
|
|||||||
handleAddTeam={handleAddTeam}
|
handleAddTeam={handleAddTeam}
|
||||||
handleAddUser={handleAddUser}
|
handleAddUser={handleAddUser}
|
||||||
handleJoinTeamClick={handleJoinTeamClick}
|
handleJoinTeamClick={handleJoinTeamClick}
|
||||||
|
handleLeaveTeamClick={handleLeaveTeamClick}
|
||||||
handleTeamUsersSearchAction={handleTeamUsersSearchAction}
|
handleTeamUsersSearchAction={handleTeamUsersSearchAction}
|
||||||
hasAccess={hasAccess}
|
hasAccess={hasAccess}
|
||||||
isAddingTeam={isAddingTeam}
|
isAddingTeam={isAddingTeam}
|
||||||
|
@ -66,6 +66,7 @@ export interface TeamsAndUsersProps {
|
|||||||
descriptionHandler: (value: boolean) => void;
|
descriptionHandler: (value: boolean) => void;
|
||||||
onDescriptionUpdate: (value: string) => void;
|
onDescriptionUpdate: (value: string) => void;
|
||||||
handleJoinTeamClick: (id: string, data: Operation[]) => void;
|
handleJoinTeamClick: (id: string, data: Operation[]) => void;
|
||||||
|
handleLeaveTeamClick: (id: string, data: Operation[]) => Promise<void>;
|
||||||
isAddingUsers: boolean;
|
isAddingUsers: boolean;
|
||||||
getUniqueUserList: () => Array<UserTeams>;
|
getUniqueUserList: () => Array<UserTeams>;
|
||||||
addUsersToTeam: (data: Array<UserTeams>) => void;
|
addUsersToTeam: (data: Array<UserTeams>) => void;
|
||||||
@ -107,4 +108,5 @@ export interface TeamDetailsProp {
|
|||||||
handleAddUser: (data: boolean) => void;
|
handleAddUser: (data: boolean) => void;
|
||||||
removeUserFromTeam: (id: string) => Promise<void>;
|
removeUserFromTeam: (id: string) => Promise<void>;
|
||||||
handleJoinTeamClick: (id: string, data: Operation[]) => void;
|
handleJoinTeamClick: (id: string, data: Operation[]) => void;
|
||||||
|
handleLeaveTeamClick: (id: string, data: Operation[]) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -135,11 +135,13 @@ const jsonData = {
|
|||||||
'feed-post-error': 'Error while posting the message!',
|
'feed-post-error': 'Error while posting the message!',
|
||||||
|
|
||||||
'join-team-error': 'Error while joining the team!',
|
'join-team-error': 'Error while joining the team!',
|
||||||
|
'leave-team-error': 'Error while leaving the team!',
|
||||||
},
|
},
|
||||||
'api-success-messages': {
|
'api-success-messages': {
|
||||||
'create-conversation': 'Conversation created successfully!',
|
'create-conversation': 'Conversation created successfully!',
|
||||||
|
|
||||||
'join-team-success': 'Team joined successfully!',
|
'join-team-success': 'Team joined successfully!',
|
||||||
|
'leave-team-success': 'Left the team successfully!',
|
||||||
|
|
||||||
'delete-test': 'Test deleted successfully!',
|
'delete-test': 'Test deleted successfully!',
|
||||||
'delete-message': 'Message deleted successfully!',
|
'delete-message': 'Message deleted successfully!',
|
||||||
|
@ -403,6 +403,31 @@ const TeamsAndUsersPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLeaveTeamClick = (id: string, data: Operation[]) => {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
updateUserDetail(id, data)
|
||||||
|
.then((res: AxiosResponse) => {
|
||||||
|
if (res.data) {
|
||||||
|
AppState.updateUserDetails(res.data);
|
||||||
|
fetchCurrentTeam(teamAndUser);
|
||||||
|
showSuccessToast(
|
||||||
|
jsonData['api-success-messages']['leave-team-success']
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
throw jsonData['api-error-messages']['leave-team-error'];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {
|
||||||
|
showErrorToast(
|
||||||
|
err,
|
||||||
|
jsonData['api-error-messages']['leave-team-error']
|
||||||
|
);
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle current team route
|
* Handle current team route
|
||||||
* @param name - team name
|
* @param name - team name
|
||||||
@ -620,6 +645,7 @@ const TeamsAndUsersPage = () => {
|
|||||||
handleAddUser={handleAddUser}
|
handleAddUser={handleAddUser}
|
||||||
handleDeleteUser={handleDeleteUser}
|
handleDeleteUser={handleDeleteUser}
|
||||||
handleJoinTeamClick={handleJoinTeamClick}
|
handleJoinTeamClick={handleJoinTeamClick}
|
||||||
|
handleLeaveTeamClick={handleLeaveTeamClick}
|
||||||
handleTeamUsersSearchAction={handleTeamUsersSearchAction}
|
handleTeamUsersSearchAction={handleTeamUsersSearchAction}
|
||||||
handleUserSearchTerm={handleUserSearchTerm}
|
handleUserSearchTerm={handleUserSearchTerm}
|
||||||
hasAccess={isAuthDisabled || isAdminUser}
|
hasAccess={isAuthDisabled || isAdminUser}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user