This is an automated email from the ASF dual-hosted git repository.
jmclean pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 7e93ab094e [#6798] feat(CLI): Support TableFormat and PlainFormat for
Model, User and Group. (#6800)
7e93ab094e is described below
commit 7e93ab094ed88c80c19e95e14688dcd37425a55b
Author: Lord of Abyss <[email protected]>
AuthorDate: Wed May 7 15:39:43 2025 +0800
[#6798] feat(CLI): Support TableFormat and PlainFormat for Model, User and
Group. (#6800)
### What changes were proposed in this pull request?
Uniform CLI output format for Model, User and Group commands.
### Why are the changes needed?
Uniform CLI output format for Model, User and Group commands.
Fix: #6798
### Does this PR introduce _any_ user-facing change?
Users can output multiple entities using the CLI's table format.
### How was this patch tested?
local test + ut.
TableFormat test.
```bash
bin/gcli.sh model list -m demo_metalake --name model_catalog.schema
--output table
+--------+
| Name |
+--------+
| model2 |
+--------+
bin/gcli.sh model details -m demo_metalake --name
model_catalog.schema.model2 --output table
+--------+-------------+----------------+
| Name | Comment | Latest version |
+--------+-------------+----------------+
| model2 | test rename | 0 |
+--------+-------------+----------------+
bin/gcli.sh user list -m demo_metalake --output table
+-----------+
| Name |
+-----------+
| anonymous |
| testRole |
| test_user |
+-----------+
bin/gcli.sh user details -m demo_metalake --user testRole --output table
The user has no roles.
bin/gcli.sh group list -m demo_metalake --output table
+---------------+
| Name |
+---------------+
| group_no_role |
| test_group |
+---------------+
bin/gcli.sh group details -m demo_metalake --group test_group --output table
The group has no roles.
```
PlainFormat test.
```bash
bin/gcli.sh model list -m demo_metalake --name model_catalog.schema
model2
bin/gcli.sh model list -m demo_metalake --name model_catalog.schema
Model name model2, comment: test rename, latest version: 0
bin/gcli.sh user list -m demo_metalake
anonymous
testRole
test_user
bin/gcli.sh user details -m demo_metalake --user testRole
The user has no roles.
bin/gcli.sh group list -m demo_metalake
group_no_role
test_group
bin/gcli.sh group details -m demo_metalake --group test_group
The group has no roles.
```
---------
Signed-off-by: dependabot[bot] <[email protected]>
Signed-off-by: George T. C. Lai <[email protected]>
Co-authored-by: roryqi <[email protected]>
Co-authored-by: dependabot[bot]
<49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mchades <[email protected]>
Co-authored-by: George T. C. Lai <[email protected]>
Co-authored-by: yangyang zhong <[email protected]>
Co-authored-by: AndreVale69 <[email protected]>
Co-authored-by: Mini Yu <[email protected]>
Co-authored-by: Jerry Shao <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-authored-by: Eric Chang <[email protected]>
Co-authored-by: gavin.wang <[email protected]>
Co-authored-by: Kang <[email protected]>
Co-authored-by: Zhengke Zhou <[email protected]>
Co-authored-by: Justin Mclean <[email protected]>
Co-authored-by: Danhua Wang <[email protected]>
Co-authored-by: yunchi <[email protected]>
Co-authored-by: RickyMa <[email protected]>
Co-authored-by: Yuhui <[email protected]>
Co-authored-by: Qiming Teng <[email protected]>
Co-authored-by: Tian Lu <[email protected]>
Co-authored-by: tian bao <[email protected]>
Co-authored-by: Jimmy Lee <[email protected]>
Co-authored-by: Brijesh Thummar <[email protected]>
Co-authored-by: Qian Xia <[email protected]>
Co-authored-by: Xiaojian Sun <[email protected]>
Co-authored-by: Cyber Star <[email protected]>
---
.../gravitino/cli/commands/GroupDetails.java | 9 +-
.../apache/gravitino/cli/commands/ListGroups.java | 26 ++-
.../apache/gravitino/cli/commands/ListModel.java | 32 +++-
.../apache/gravitino/cli/commands/ListUsers.java | 26 ++-
.../gravitino/cli/commands/ModelDetails.java | 9 +-
.../apache/gravitino/cli/commands/UserDetails.java | 9 +-
.../apache/gravitino/cli/outputs/PlainFormat.java | 131 +++++++++++++++
.../apache/gravitino/cli/outputs/TableFormat.java | 175 +++++++++++++++++++++
.../gravitino/cli/output/TestPlainFormat.java | 94 +++++++++++
.../gravitino/cli/output/TestTableFormat.java | 138 ++++++++++++++++
10 files changed, 628 insertions(+), 21 deletions(-)
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/GroupDetails.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/GroupDetails.java
index 8667dc4b67..002f69ef8e 100644
---
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/GroupDetails.java
+++
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/GroupDetails.java
@@ -20,6 +20,7 @@
package org.apache.gravitino.cli.commands;
import java.util.List;
+import org.apache.gravitino.authorization.Group;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.ErrorMessages;
import org.apache.gravitino.client.GravitinoClient;
@@ -48,10 +49,12 @@ public class GroupDetails extends Command {
@Override
public void handle() {
List<String> roles = null;
+ Group groupObject = null;
try {
GravitinoClient client = buildClient(metalake);
- roles = client.getGroup(group).roles();
+ groupObject = client.getGroup(group);
+ roles = groupObject.roles();
} catch (NoSuchMetalakeException err) {
exitWithError(ErrorMessages.UNKNOWN_METALAKE);
} catch (NoSuchUserException err) {
@@ -60,10 +63,10 @@ public class GroupDetails extends Command {
exitWithError(exp.getMessage());
}
- if (roles == null) {
+ if (roles == null || roles.isEmpty()) {
printInformation("The group has no roles.");
} else {
- printResults(String.join(",", roles));
+ printResults(groupObject);
}
}
}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListGroups.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListGroups.java
index b737498115..67d7c268d5 100644
---
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListGroups.java
+++
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListGroups.java
@@ -19,6 +19,10 @@
package org.apache.gravitino.cli.commands;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.gravitino.Audit;
+import org.apache.gravitino.authorization.Group;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.ErrorMessages;
import org.apache.gravitino.client.GravitinoClient;
@@ -56,7 +60,27 @@ public class ListGroups extends Command {
if (groups.length == 0) {
printInformation("No groups found in metalake " + metalake);
} else {
- printResults(String.join(",", groups));
+ Group[] groupObjects =
Arrays.stream(groups).map(this::getGroup).toArray(Group[]::new);
+ printResults(groupObjects);
}
}
+
+ private Group getGroup(String name) {
+ return new Group() {
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public List<String> roles() {
+ return null;
+ }
+
+ @Override
+ public Audit auditInfo() {
+ return null;
+ }
+ };
+ }
}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListModel.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListModel.java
index fb39e08a93..00d74499a0 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListModel.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListModel.java
@@ -18,8 +18,8 @@
*/
package org.apache.gravitino.cli.commands;
-import com.google.common.base.Joiner;
import java.util.Arrays;
+import org.apache.gravitino.Audit;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.cli.CommandContext;
@@ -28,6 +28,7 @@ import org.apache.gravitino.client.GravitinoClient;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.exceptions.NoSuchMetalakeException;
import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.model.Model;
/** List the names of all models in a schema. */
public class ListModel extends Command {
@@ -69,11 +70,30 @@ public class ListModel extends Command {
exitWithError(err.getMessage());
}
- String output =
- models.length == 0
- ? "No models exist."
- : Joiner.on(",").join(Arrays.stream(models).map(model ->
model.name()).iterator());
+ if (models.length == 0) {
+ printInformation("No models exist.");
+ } else {
+ Model[] modelsArr =
Arrays.stream(models).map(this::getModel).toArray(Model[]::new);
+ printResults(modelsArr);
+ }
+ }
+
+ private Model getModel(NameIdentifier modelIdent) {
+ return new Model() {
+ @Override
+ public String name() {
+ return modelIdent.name();
+ }
+
+ @Override
+ public int latestVersion() {
+ return 0;
+ }
- printResults(output);
+ @Override
+ public Audit auditInfo() {
+ return null;
+ }
+ };
}
}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListUsers.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListUsers.java
index 250d71e2f1..6f3905f158 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListUsers.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListUsers.java
@@ -19,6 +19,10 @@
package org.apache.gravitino.cli.commands;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.gravitino.Audit;
+import org.apache.gravitino.authorization.User;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.ErrorMessages;
import org.apache.gravitino.client.GravitinoClient;
@@ -56,7 +60,27 @@ public class ListUsers extends Command {
if (users.length == 0) {
printInformation("No users exist.");
} else {
- printResults(String.join(",", users));
+ User[] userObjects =
Arrays.stream(users).map(this::getUser).toArray(User[]::new);
+ printResults(userObjects);
}
}
+
+ private User getUser(String user) {
+ return new User() {
+ @Override
+ public String name() {
+ return user;
+ }
+
+ @Override
+ public List<String> roles() {
+ return null;
+ }
+
+ @Override
+ public Audit auditInfo() {
+ return null;
+ }
+ };
+ }
}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelDetails.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelDetails.java
index 4c5f1a57d4..a25f069f8a 100644
---
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelDetails.java
+++
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelDetails.java
@@ -19,7 +19,6 @@
package org.apache.gravitino.cli.commands;
-import java.util.Arrays;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.ErrorMessages;
@@ -61,13 +60,11 @@ public class ModelDetails extends Command {
public void handle() {
NameIdentifier name = NameIdentifier.of(schema, model);
Model gModel = null;
- int[] versions = new int[0];
try {
GravitinoClient client = buildClient(metalake);
ModelCatalog modelCatalog = client.loadCatalog(catalog).asModelCatalog();
gModel = modelCatalog.getModel(name);
- versions = modelCatalog.listModelVersions(name);
} catch (NoSuchMetalakeException noSuchMetalakeException) {
exitWithError(ErrorMessages.UNKNOWN_METALAKE);
} catch (NoSuchCatalogException noSuchCatalogException) {
@@ -79,9 +76,7 @@ public class ModelDetails extends Command {
} catch (Exception err) {
exitWithError(err.getMessage());
}
- String basicInfo =
- String.format("Model name %s, latest version: %s%n", gModel.name(),
gModel.latestVersion());
- String versionInfo = Arrays.toString(versions);
- printResults(basicInfo + "versions: " + versionInfo);
+
+ printResults(gModel);
}
}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UserDetails.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UserDetails.java
index 26c11a0e12..ae7cb3a77d 100644
---
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UserDetails.java
+++
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UserDetails.java
@@ -20,6 +20,7 @@
package org.apache.gravitino.cli.commands;
import java.util.List;
+import org.apache.gravitino.authorization.User;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.ErrorMessages;
import org.apache.gravitino.client.GravitinoClient;
@@ -48,10 +49,12 @@ public class UserDetails extends Command {
@Override
public void handle() {
List<String> roles = null;
+ User userObject = null;
try {
GravitinoClient client = buildClient(metalake);
- roles = client.getUser(user).roles();
+ userObject = client.getUser(user);
+ roles = userObject.roles();
} catch (NoSuchMetalakeException err) {
exitWithError(ErrorMessages.UNKNOWN_METALAKE);
} catch (NoSuchUserException err) {
@@ -60,10 +63,10 @@ public class UserDetails extends Command {
exitWithError(exp.getMessage());
}
- if (roles.isEmpty()) {
+ if (roles == null || roles.isEmpty()) {
printInformation("The user has no roles.");
} else {
- printResults(String.join(",", roles));
+ printResults(userObject);
}
}
}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/outputs/PlainFormat.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/outputs/PlainFormat.java
index fcbcb2f35b..649c676e0b 100644
---
a/clients/cli/src/main/java/org/apache/gravitino/cli/outputs/PlainFormat.java
+++
b/clients/cli/src/main/java/org/apache/gravitino/cli/outputs/PlainFormat.java
@@ -25,7 +25,10 @@ import org.apache.gravitino.Audit;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Metalake;
import org.apache.gravitino.Schema;
+import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.User;
import org.apache.gravitino.cli.CommandContext;
+import org.apache.gravitino.model.Model;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.Table;
@@ -57,6 +60,18 @@ public abstract class PlainFormat<T> extends
BaseOutputFormat<T> {
new TablePlainFormat(context).output((Table) entity);
} else if (entity instanceof Table[]) {
new TableListPlainFormat(context).output((Table[]) entity);
+ } else if (entity instanceof Model) {
+ new ModelDetailPlainFormat(context).output((Model) entity);
+ } else if (entity instanceof Model[]) {
+ new ModelListPlainFormat(context).output((Model[]) entity);
+ } else if (entity instanceof User) {
+ new UserDetailsPlainFormat(context).output((User) entity);
+ } else if (entity instanceof User[]) {
+ new UserListPlainFormat(context).output((User[]) entity);
+ } else if (entity instanceof Group) {
+ new GroupDetailsPlainFormat(context).output((Group) entity);
+ } else if (entity instanceof Group[]) {
+ new GroupListPlainFormat(context).output((Group[]) entity);
} else if (entity instanceof Audit) {
new AuditPlainFormat(context).output((Audit) entity);
} else if (entity instanceof Column[]) {
@@ -273,4 +288,120 @@ public abstract class PlainFormat<T> extends
BaseOutputFormat<T> {
return NEWLINE_JOINER.join(header, data.toString());
}
}
+
+ /**
+ * Format a {@link Model} instance with detailed information. Output format:
model_name,
+ * model_comment and latest_version
+ */
+ static final class ModelDetailPlainFormat extends PlainFormat<Model> {
+
+ /**
+ * Creates a new {@link PlainFormat} with the specified command context.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public ModelDetailPlainFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(Model model) {
+ return String.format(
+ "Model name %s, latest version: %s%n", model.name(),
model.latestVersion());
+ }
+ }
+
+ /** Format an array of {@link Model} instances with their names. Output
format: model_name */
+ static final class ModelListPlainFormat extends PlainFormat<Model[]> {
+
+ /**
+ * Creates a new {@link PlainFormat} with the specified command context.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public ModelListPlainFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(Model[] models) {
+ return
COMMA_JOINER.join(Arrays.stream(models).map(Model::name).collect(Collectors.toList()));
+ }
+ }
+
+ /** Format a {@link User} instance with their details. Output format:
username, role */
+ static final class UserDetailsPlainFormat extends PlainFormat<User> {
+
+ /**
+ * Creates a new {@link PlainFormat} with the specified command context.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public UserDetailsPlainFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(User user) {
+ return COMMA_JOINER.join(user.roles());
+ }
+ }
+
+ /** Format an array of {@link User} instances with their names. Output
format: username */
+ static final class UserListPlainFormat extends PlainFormat<User[]> {
+
+ /**
+ * Creates a new {@link PlainFormat} with the specified command context.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public UserListPlainFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(User[] users) {
+ return
COMMA_JOINER.join(Arrays.stream(users).map(User::name).collect(Collectors.toList()));
+ }
+ }
+
+ /** Format a {@link Group} instance with their details. Output format: group
name, role */
+ static final class GroupDetailsPlainFormat extends PlainFormat<Group> {
+ /**
+ * Constructs a new {@link GroupDetailsPlainFormat} instance.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public GroupDetailsPlainFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(Group group) {
+ return COMMA_JOINER.join(group.roles());
+ }
+ }
+
+ /** Format an array of {@link Group} instances with their names. Output
format: group name */
+ static final class GroupListPlainFormat extends PlainFormat<Group[]> {
+ /**
+ * Constructs a new {@link GroupListPlainFormat} instance.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public GroupListPlainFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(Group[] groups) {
+ return
COMMA_JOINER.join(Arrays.stream(groups).map(Group::name).collect(Collectors.toList()));
+ }
+ }
}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/outputs/TableFormat.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/outputs/TableFormat.java
index e9ab7fd13b..c4e556abd3 100644
---
a/clients/cli/src/main/java/org/apache/gravitino/cli/outputs/TableFormat.java
+++
b/clients/cli/src/main/java/org/apache/gravitino/cli/outputs/TableFormat.java
@@ -49,7 +49,11 @@ import org.apache.gravitino.Audit;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Metalake;
import org.apache.gravitino.Schema;
+import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.User;
import org.apache.gravitino.cli.CommandContext;
+import org.apache.gravitino.cli.commands.Command;
+import org.apache.gravitino.model.Model;
import org.apache.gravitino.rel.Table;
/**
@@ -86,6 +90,18 @@ public abstract class TableFormat<T> extends
BaseOutputFormat<T> {
new TableDetailsTableFormat(context).output((Table) entity);
} else if (entity instanceof Table[]) {
new TableListTableFormat(context).output((Table[]) entity);
+ } else if (entity instanceof Model) {
+ new ModelDetailsTableFormat(context).output((Model) entity);
+ } else if (entity instanceof Model[]) {
+ new ModelListTableFormat(context).output((Model[]) entity);
+ } else if (entity instanceof User) {
+ new UserDetailsTableFormat(context).output((User) entity);
+ } else if (entity instanceof User[]) {
+ new UserListTableFormat(context).output((User[]) entity);
+ } else if (entity instanceof Group) {
+ new GroupDetailsTableFormat(context).output((Group) entity);
+ } else if (entity instanceof Group[]) {
+ new GroupListTableFormat(context).output((Group[]) entity);
} else if (entity instanceof Audit) {
new AuditTableFormat(context).output((Audit) entity);
} else if (entity instanceof org.apache.gravitino.rel.Column[]) {
@@ -727,4 +743,163 @@ public abstract class TableFormat<T> extends
BaseOutputFormat<T> {
columnComment);
}
}
+
+ /**
+ * Formats a single {@link Model} instance into a three-column table
display. Displays model
+ * details, including name, comment, and latest version.
+ */
+ static final class ModelDetailsTableFormat extends TableFormat<Model> {
+ /**
+ * Constructs a new {@link ModelDetailsTableFormat} with the specified
CommandContext.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public ModelDetailsTableFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(Model model) {
+ Column modelName = new Column(context, "name");
+ Column modelComment = new Column(context, "comment");
+ Column modelLatestVersion = new Column(context, "latest version");
+
+ modelName.addCell(model.name());
+ modelComment.addCell(model.comment());
+ modelLatestVersion.addCell(model.latestVersion());
+
+ return getTableFormat(modelName, modelComment, modelLatestVersion);
+ }
+ }
+
+ /**
+ * Formats an array of {@link Model} into a single-column table display.
Lists all model names in
+ * a vertical format.
+ */
+ static final class ModelListTableFormat extends TableFormat<Model[]> {
+ /**
+ * Constructs a new {@link ModelListTableFormat} with the specified
CommandContext.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public ModelListTableFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(Model[] models) {
+ Column modelName = new Column(context, "name");
+ Arrays.stream(models).forEach(model -> modelName.addCell(model.name()));
+
+ return getTableFormat(modelName);
+ }
+ }
+
+ /**
+ * Formats a single {@link User} instance into a two-column table display.
Displays user details,
+ * including name and roles.
+ */
+ static final class UserDetailsTableFormat extends TableFormat<User> {
+
+ /**
+ * Constructs a new {@link UserDetailsTableFormat} with the specified
CommandContext.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public UserDetailsTableFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(User user) {
+ Column columnName = new Column(context, "name");
+ Column columnRoles = new Column(context, "roles");
+
+ columnName.addCell(user.name());
+ columnRoles.addCell(Command.COMMA_JOINER.join(user.roles()));
+
+ return getTableFormat(columnName, columnRoles);
+ }
+ }
+
+ /**
+ * Formats an array of {@link User} into a single-column table display.
Lists all usernames in a
+ * vertical format.
+ */
+ static final class UserListTableFormat extends TableFormat<User[]> {
+
+ /**
+ * Constructs a new {@link UserListTableFormat} with the specified
CommandContext.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public UserListTableFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(User[] users) {
+ Column name = new Column(context, "name");
+ Arrays.stream(users).forEach(user -> name.addCell(user.name()));
+
+ return getTableFormat(name);
+ }
+ }
+
+ /**
+ * Formats a single {@link Group} instance into a two-column table display.
Displays group
+ * details, including name and roles.
+ */
+ static final class GroupDetailsTableFormat extends TableFormat<Group> {
+
+ /**
+ * Constructs a new {@link GroupDetailsTableFormat} with the specified
CommandContext.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public GroupDetailsTableFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(Group group) {
+ Column columnName = new Column(context, "name");
+ Column columnRoles = new Column(context, "roles");
+
+ columnName.addCell(group.name());
+ columnRoles.addCell(Command.COMMA_JOINER.join(group.roles()));
+
+ return getTableFormat(columnName, columnRoles);
+ }
+ }
+
+ /**
+ * Formats an array of {@link Group} into a single-column table display.
Lists all group names in
+ * a vertical format.
+ */
+ static final class GroupListTableFormat extends TableFormat<Group[]> {
+
+ /**
+ * Constructs a new {@link GroupListTableFormat} with the specified
CommandContext.
+ *
+ * @param context the {@link CommandContext} instance.
+ */
+ public GroupListTableFormat(CommandContext context) {
+ super(context);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getOutput(Group[] groups) {
+ Column name = new Column(context, "name");
+ Arrays.stream(groups).forEach(group -> name.addCell(group.name()));
+
+ return getTableFormat(name);
+ }
+ }
}
diff --git
a/clients/cli/src/test/java/org/apache/gravitino/cli/output/TestPlainFormat.java
b/clients/cli/src/test/java/org/apache/gravitino/cli/output/TestPlainFormat.java
index 9e81154ff6..c69424e116 100644
---
a/clients/cli/src/test/java/org/apache/gravitino/cli/output/TestPlainFormat.java
+++
b/clients/cli/src/test/java/org/apache/gravitino/cli/output/TestPlainFormat.java
@@ -27,12 +27,16 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
+import java.util.List;
import org.apache.gravitino.Audit;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Metalake;
import org.apache.gravitino.Schema;
+import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.User;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.outputs.PlainFormat;
+import org.apache.gravitino.model.Model;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.expressions.Expression;
@@ -43,6 +47,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
public class TestPlainFormat {
@@ -339,6 +344,70 @@ public class TestPlainFormat {
return mockTable;
}
+ @Test
+ void testModelDetailsWithPlainFormat() {
+ CommandContext mockContext = getMockContext();
+ Model mockModel = getMockModel("demo_model", "This is a demo model", 1);
+
+ PlainFormat.output(mockModel, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals("Model name demo_model, latest version: 1",
output);
+ }
+
+ @Test
+ void testListModelWithPlainFormat() {
+ CommandContext mockContext = getMockContext();
+ Model model1 = getMockModel("model1", "This is a model", 1);
+ Model model2 = getMockModel("model2", "This is another model", 2);
+ Model model3 = getMockModel("model3", "This is a third model", 3);
+
+ PlainFormat.output(new Model[] {model1, model2, model3}, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals("model1,model2,model3", output);
+ }
+
+ @Test
+ void testUserDetailsWithPlainFormat() {
+ CommandContext mockContext = getMockContext();
+ User mockUser = getMockUser("demo_user", ImmutableList.of("admin",
"user"));
+ PlainFormat.output(mockUser, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals("admin,user", output);
+ }
+
+ @Test
+ void testListUsersWithPlainFormat() {
+ CommandContext mockContext = getMockContext();
+ User user1 = getMockUser("user1", ImmutableList.of("admin", "user"));
+ User user2 = getMockUser("user2", ImmutableList.of("admin"));
+ User user3 = getMockUser("user3", ImmutableList.of("user"));
+
+ PlainFormat.output(new User[] {user1, user2, user3}, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals("user1,user2,user3", output);
+ }
+
+ @Test
+ void testGroupDetailsWithPlainFormat() {
+ CommandContext mockContext = getMockContext();
+ Group mockGroup = getMockGroup("demo_group", ImmutableList.of("admin",
"scientist"));
+ PlainFormat.output(mockGroup, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals("admin,scientist", output);
+ }
+
+ @Test
+ void testListGroupsWithPlainFormat() {
+ CommandContext mockContext = getMockContext();
+ Group group1 = getMockGroup("group1", ImmutableList.of("admin", "user"));
+ Group group2 = getMockGroup("group2", ImmutableList.of("admin"));
+ Group group3 = getMockGroup("group3", ImmutableList.of("user"));
+
+ PlainFormat.output(new Group[] {group1, group2, group3}, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals("group1,group2,group3", output);
+ }
+
private org.apache.gravitino.rel.Column getMockColumn(
String name,
Type dataType,
@@ -357,4 +426,29 @@ public class TestPlainFormat {
return mockColumn;
}
+
+ private Model getMockModel(String name, String comment, int lastVersion) {
+ Model mockModel = mock(Model.class);
+ when(mockModel.name()).thenReturn(name);
+ when(mockModel.comment()).thenReturn(comment);
+ when(mockModel.latestVersion()).thenReturn(lastVersion);
+
+ return mockModel;
+ }
+
+ private User getMockUser(String name, List<String> roles) {
+ User mockUser = mock(User.class);
+ when(mockUser.name()).thenReturn(name);
+ when(mockUser.roles()).thenReturn(roles);
+
+ return mockUser;
+ }
+
+ private Group getMockGroup(String name, List<String> roles) {
+ Group mockGroup = mock(Group.class);
+ when(mockGroup.name()).thenReturn(name);
+ when(mockGroup.roles()).thenReturn(roles);
+
+ return mockGroup;
+ }
}
diff --git
a/clients/cli/src/test/java/org/apache/gravitino/cli/output/TestTableFormat.java
b/clients/cli/src/test/java/org/apache/gravitino/cli/output/TestTableFormat.java
index 3f45459bc5..45909b89ce 100644
---
a/clients/cli/src/test/java/org/apache/gravitino/cli/output/TestTableFormat.java
+++
b/clients/cli/src/test/java/org/apache/gravitino/cli/output/TestTableFormat.java
@@ -29,13 +29,18 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
import org.apache.gravitino.Audit;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Metalake;
import org.apache.gravitino.Schema;
+import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.User;
import org.apache.gravitino.cli.CommandContext;
import org.apache.gravitino.cli.outputs.Column;
import org.apache.gravitino.cli.outputs.TableFormat;
+import org.apache.gravitino.model.Model;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.expressions.Expression;
import org.apache.gravitino.rel.expressions.FunctionExpression;
@@ -559,6 +564,114 @@ public class TestTableFormat {
output);
}
+ @Test
+ void testListModelWithTableFormat() {
+ CommandContext mockContext = getMockContext();
+ Model model1 = getMockModel("model1", "This is a demo model", 1);
+ Model model2 = getMockModel("model2", "This is another demo model", 2);
+ Model model3 = getMockModel("model3", "This is a third demo model", 3);
+
+ TableFormat.output(new Model[] {model1, model2, model3}, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals(
+ "+--------+\n"
+ + "| Name |\n"
+ + "+--------+\n"
+ + "| model1 |\n"
+ + "| model2 |\n"
+ + "| model3 |\n"
+ + "+--------+",
+ output);
+ }
+
+ @Test
+ void testModelDetailsWithTableFormat() {
+ CommandContext mockContext = getMockContext();
+ Model mockModel = getMockModel("demo_model", "This is a demo model", 1);
+
+ TableFormat.output(mockModel, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals(
+ "+------------+----------------------+----------------+\n"
+ + "| Name | Comment | Latest version |\n"
+ + "+------------+----------------------+----------------+\n"
+ + "| demo_model | This is a demo model | 1 |\n"
+ + "+------------+----------------------+----------------+",
+ output);
+ }
+
+ @Test
+ void testListUserWithTableFormat() {
+ CommandContext mockContext = getMockContext();
+ User user1 = getMockUser("user1", Arrays.asList("role1", "role2"));
+ User user2 = getMockUser("user2", Arrays.asList("role3", "role4"));
+ User user3 = getMockUser("user3", Arrays.asList("role5"));
+
+ TableFormat.output(new User[] {user1, user2, user3}, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals(
+ "+-------+\n"
+ + "| Name |\n"
+ + "+-------+\n"
+ + "| user1 |\n"
+ + "| user2 |\n"
+ + "| user3 |\n"
+ + "+-------+",
+ output);
+ }
+
+ @Test
+ void testUserDetailsWithTableFormat() {
+ CommandContext mockContext = getMockContext();
+ User mockUser = getMockUser("demo_user", Arrays.asList("role1", "role2"));
+
+ TableFormat.output(mockUser, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals(
+ "+-----------+--------------+\n"
+ + "| Name | Roles |\n"
+ + "+-----------+--------------+\n"
+ + "| demo_user | role1, role2 |\n"
+ + "+-----------+--------------+",
+ output);
+ }
+
+ @Test
+ void testListGroupWithTableFormat() {
+ CommandContext mockContext = getMockContext();
+ Group group1 = getMockGroup("group1", Arrays.asList("role1", "role2"));
+ Group group2 = getMockGroup("group2", Arrays.asList("role3", "role4"));
+ Group group3 = getMockGroup("group3", Arrays.asList("role5"));
+
+ TableFormat.output(new Group[] {group1, group2, group3}, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals(
+ "+--------+\n"
+ + "| Name |\n"
+ + "+--------+\n"
+ + "| group1 |\n"
+ + "| group2 |\n"
+ + "| group3 |\n"
+ + "+--------+",
+ output);
+ }
+
+ @Test
+ void testGroupDetailsWithTableFormat() {
+ CommandContext mockContext = getMockContext();
+ Group mockGroup = getMockGroup("demo_group", Arrays.asList("role1",
"role2"));
+
+ TableFormat.output(mockGroup, mockContext);
+ String output = new String(outContent.toByteArray(),
StandardCharsets.UTF_8).trim();
+ Assertions.assertEquals(
+ "+------------+--------------+\n"
+ + "| Name | Roles |\n"
+ + "+------------+--------------+\n"
+ + "| demo_group | role1, role2 |\n"
+ + "+------------+--------------+",
+ output);
+ }
+
@Test
void testOutputWithUnsupportType() {
CommandContext mockContext = getMockContext();
@@ -665,4 +778,29 @@ public class TestTableFormat {
return mockColumn;
}
+
+ private Model getMockModel(String name, String comment, int lastVersion) {
+ Model mockModel = mock(Model.class);
+ when(mockModel.name()).thenReturn(name);
+ when(mockModel.comment()).thenReturn(comment);
+ when(mockModel.latestVersion()).thenReturn(lastVersion);
+
+ return mockModel;
+ }
+
+ private User getMockUser(String name, List<String> roles) {
+ User mockUser = mock(User.class);
+ when(mockUser.name()).thenReturn(name);
+ when(mockUser.roles()).thenReturn(roles);
+
+ return mockUser;
+ }
+
+ private Group getMockGroup(String name, List<String> roles) {
+ Group mockGroup = mock(Group.class);
+ when(mockGroup.name()).thenReturn(name);
+ when(mockGroup.roles()).thenReturn(roles);
+
+ return mockGroup;
+ }
}