-
Notifications
You must be signed in to change notification settings - Fork 299
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#3348] feat(core,server): Add the list operation of the user #4055
base: main
Are you sure you want to change the base?
Conversation
public String[] listUsers(String metalake) throws NoSuchMetalakeException { | ||
return doWithNonAdminLock(() -> userGroupManager.listUsers(metalake)); | ||
} | ||
|
||
public User[] listUsersInfo(String metalake) throws NoSuchMetalakeException { | ||
return doWithNonAdminLock(() -> userGroupManager.listUsersInfo(metalake)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think listUsers
-> listUserNames
, listUsersInfo
-> listUsers
would be more appropriate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
a124b10
to
2f1476c
Compare
@lw-yang Could you take an another look? |
for (UserPO userPO : userPOs) { | ||
List<RolePO> rolePOs = RoleMetaService.getInstance().listRolesByUserId(userPO.getUserId()); | ||
userEntities.add(POConverters.fromUserPO(userPO, rolePOs, namespace)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the num of users > 1w is common situation, this may execute for a long time, maybe we should consider using cache in listUsers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @yuqi1129 Maybe we can have a common cache layer for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is better to use a paging query.
@jerqi can you please update the PR. |
9d07ea0
to
a57e4e3
Compare
@lw-yang @jerryshao Do you have any other suggestion? |
LOG.error("Listing user under metalake {} failed due to storage issues", metalake, ioe); | ||
throw new RuntimeException(ioe); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think you need to use tree lock here.
listUserNames
can be implemented based onlistUsers
, don't need to duplicate the codes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think you need to use tree lock here.
I have used tree lock in the UserOperations.
listUserNames
can be implemented based onlistUsers
, don't need to duplicate the codes.
OK, I have refactored the code.
userPOs.addAll( | ||
SessionUtils.doWithoutCommitAndFetchResult( | ||
UserMetaMapper.class, | ||
mapper -> mapper.listUserPOsByMetalakeId(metalakeId.get())))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't have to use transaction here, simply because we don't use select for update, so the transaction is not useful and will lead to dead lock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modified. But it shouldn't bring dead lock using the transaction.
@Getter | ||
@ToString | ||
@EqualsAndHashCode(callSuper = true) | ||
public class NameListResponse extends BaseResponse { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should implement validate()
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CatalogListResponse doesn't implement validate
, too. I created an issue #4197 to track it.
super(0); | ||
this.users = null; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
for (UserPO userPO : userPOs) { | ||
List<RolePO> rolePOs = RoleMetaService.getInstance().listRolesByUserId(userPO.getUserId()); | ||
userEntities.add(POConverters.fromUserPO(userPO, rolePOs, namespace)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really want to return all the roles for all the users? This will be extremely cost, is that really the frontend needs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, we adopts the lazily loading information about users/roles.
} catch (Throwable t) { | ||
throw t; | ||
} finally { | ||
SqlSessions.closeSqlSession(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to add this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
62c5484
to
8118c63
Compare
After discussing with @shaofengshi offline, we need the ability to list all the role information of the user. It will be convenient for users. I have supported the user lazily loading. So if we only want to load the user names, we don't need to load unnecessary information. |
4b728a5
to
b2d206d
Compare
Query all data at once or cache buffering in the service also has the problem of dirty data. I think paged queries are better. |
try { | ||
Namespace namespace = AuthorizationUtils.ofUserNamespace(metalake); | ||
return store.list(namespace, UserEntity.class, Entity.EntityType.USER).stream() | ||
.map(entity -> (User) entity) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you only get the user list here, right? Will you also fetch the roles for each user, I saw that User
and UserDTO
have roles
fields, will this be empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I only get the user list here. I won't fetch roles unless they are needed. Now the roles is empty for every user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can see the pull request. #4690
UserEntity roles
field don't be fetched directly. We will put a supplier method field instead. So we don't fetch the values directly if we only need the names.
If we need all the information of entities, we can also have the ability to put them. You can see the list details. Because the front side need the all the users information, so I provide the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean here why do we need to do a type conversion entity -> (User) entity
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, it's unnecessary. I ever designed other interface listEntitiesByRelation
. It need me to use type cast. For this place, we don't need. I have removed it.
+ " mt ON ut.metalake_id = mt.metalake_id" | ||
+ " WHERE mt.metalake_name = #{metalakeName}" | ||
+ " AND ut.deleted_at = 0 AND mt.deleted_at = 0"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This SQL will not throw an exception when metalake is not existed, it will only return an empty list. In your definition, you have NoSuchMetalakeException
defined, to throw an such exception, you have to add one more check to see if metalake is existed or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the checkMetalakeExists
.
ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class); | ||
Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, errorResponse2.getCode()); | ||
Assertions.assertEquals(RuntimeException.class.getSimpleName(), errorResponse2.getType()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you also need to add integration tests for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added IT in the AccessControlId.java.
Namespace namespace, | ||
Class<E> type, | ||
Entity.EntityType entityType, | ||
List<Field> allowMissingFields) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this design used for skipping some unnecessary fields? @yuqi1129 please also take a look about this design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because different upper layers may ignore different fields. For example, list roles
Some upper layers may ignore objects and objects count.
Some upper layers may only ignore objects.
Some upper layers may not ignore any field.
*/ | ||
<E extends Entity & HasIdentifier> List<E> list( | ||
Namespace namespace, Class<E> type, EntityType entityType) throws IOException; | ||
Namespace namespace, Class<E> type, EntityType entityType, List<Field> allowMissingFields) | ||
throws IOException; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can add a another inteface with default to fetch all the fields, then you don't have to change the code above for different catalogs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I would suggest to rename the parameter to like: List<Field> skippingFields
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok for me.
* @return the list of entities | ||
* @throws IOException if the list operation fails | ||
*/ | ||
<E extends Entity & HasIdentifier> List<E> list( | ||
Namespace namespace, Class<E> type, EntityType entityType) throws IOException; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mean is that these interface can be implemented like:
default XX list(xxxx) {
return list(xx, xx, xx, Lists.emptyList)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
+ " '[' || GROUP_CONCAT('\"' || rot.role_name || '\"') || ']' as roleNames," | ||
+ " '[' || GROUP_CONCAT('\"' || rot.role_id || '\"') || ']' as roleIds" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the usage of these code, can you explain it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
H2 doesn't support json_agg
function. This uses GROUP_CONCAT
to implement similar feature.
&& Objects.equal(getLastVersion(), combinedUserPO.getLastVersion()) | ||
&& Objects.equal(getDeletedAt(), combinedUserPO.getDeletedAt()) | ||
&& Objects.equal(getRoleIds(), combinedUserPO.getRoleIds()) | ||
&& Objects.equal(getRoleNames(), combinedUserPO.getRoleNames()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can use like super.equals(xxxx)
and super.hashCode()
to simplify the code here and below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I will do.
String metalakeName = namespace.level(0); | ||
|
||
if (skippingFields.contains(UserEntity.ROLE_IDS) | ||
&& skippingFields.contains(UserEntity.ROLE_NAMES)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that skippingFields
is not flexible enough, you still need to explicitly check these fields and execute by different branches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After considering twice, I extract an interface SupportsSkippingFields
to express the list user namespace
handler. I also wrap the logic which we select the handler according to skipping fields into SupportsSKippingFieldsHandlers
.
Now, we can match the skipping fields according to the handler which defined. It reduce the branches.
Moreover, we ask the developer must add high priority handler first.(You can see the code SupportsSkippingFieldsHandlers
for details). I can add the priority
method for the SupportsSkippingFields
. But for now, this is not a blocker. So just add a comment to notify others the correct usage for this interface.
What changes were proposed in this pull request?
Add the list operation of the user
Why are the changes needed?
Fix: #3348
Does this PR introduce any user-facing change?
I will add the document later.
How was this patch tested?
Add the new ut.