mirror of
https://github.com/larksuite/cli.git
synced 2026-07-09 02:14:02 +08:00
feat(vc): refine meeting events pagination and output
This commit is contained in:
@@ -23,8 +23,8 @@ const (
|
||||
minVCMeetingEventsPageSize = 20
|
||||
maxVCMeetingEventsPageSize = 100
|
||||
maxVCMeetingEventSummarySize = 80
|
||||
defaultVCMeetingPageLimit = 20
|
||||
maxVCMeetingPageLimit = 40
|
||||
defaultVCMeetingPageLimit = 50
|
||||
maxVCMeetingPageLimit = 200
|
||||
)
|
||||
|
||||
// toUnixSeconds converts a supported CLI time input into a Unix seconds string.
|
||||
@@ -54,8 +54,8 @@ var VCMeetingEvents = common.Shortcut{
|
||||
{Name: "end", Desc: "time upper bound (ISO 8601, YYYY-MM-DD, or Unix seconds)"},
|
||||
{Name: "page-token", Desc: "page token for the next page"},
|
||||
{Name: "page-size", Default: "20", Desc: "page size, 20-100 (default 20)"},
|
||||
{Name: "page-all", Type: "bool", Desc: "automatically paginate through all pages (max 40)"},
|
||||
{Name: "page-limit", Type: "int", Default: "20", Desc: "max page limit when --page-all is set (default 20, max 40)"},
|
||||
{Name: "page-all", Type: "bool", Desc: "automatically paginate through all pages (uses max page limit 200 unless --page-limit is set)"},
|
||||
{Name: "page-limit", Type: "int", Default: "50", Desc: "max pages to fetch during auto-pagination; setting this flag also enables auto-pagination (default 50, max 200)"},
|
||||
},
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
if err := validateMeetingEventsMeetingID(runtime.Str("meeting-id")); err != nil {
|
||||
@@ -64,9 +64,6 @@ var VCMeetingEvents = common.Shortcut{
|
||||
if _, _, err := parseMeetingEventsTimeRange(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := common.ValidatePageSize(runtime, "page-size", defaultVCMeetingEventsSize, minVCMeetingEventsPageSize, maxVCMeetingEventsPageSize); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := meetingEventsPageLimit(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,7 +78,15 @@ var VCMeetingEvents = common.Shortcut{
|
||||
if err != nil {
|
||||
return common.NewDryRunAPI().Set("error", err.Error())
|
||||
}
|
||||
dryRun := common.NewDryRunAPI().GET(vcMeetingEventsAPIPath)
|
||||
autoPaginate, pageLimit, err := meetingEventsPaginationConfig(runtime)
|
||||
if err != nil {
|
||||
return common.NewDryRunAPI().Set("error", err.Error())
|
||||
}
|
||||
dryRun := common.NewDryRunAPI()
|
||||
if autoPaginate {
|
||||
dryRun = dryRun.Desc(fmt.Sprintf("Auto-paginates up to %d page(s)", pageLimit))
|
||||
}
|
||||
dryRun = dryRun.GET(vcMeetingEventsAPIPath)
|
||||
if flat := flattenQueryParams(params); len(flat) > 0 {
|
||||
dryRun.Params(flat)
|
||||
}
|
||||
@@ -96,6 +101,7 @@ var VCMeetingEvents = common.Shortcut{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events = compactMeetingEvents(events)
|
||||
rows := buildMeetingEventRows(events)
|
||||
outData := map[string]interface{}{
|
||||
"events": events,
|
||||
@@ -118,6 +124,27 @@ var VCMeetingEvents = common.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
func meetingEventsPageSize(runtime *common.RuntimeContext) (int, error) {
|
||||
if runtime.Bool("page-all") {
|
||||
return maxVCMeetingEventsPageSize, nil
|
||||
}
|
||||
pageSizeStr := strings.TrimSpace(runtime.Str("page-size"))
|
||||
if pageSizeStr == "" {
|
||||
return defaultVCMeetingEventsSize, nil
|
||||
}
|
||||
pageSize, err := strconv.Atoi(pageSizeStr)
|
||||
if err != nil {
|
||||
return 0, common.FlagErrorf("invalid --page-size %q: must be an integer", pageSizeStr)
|
||||
}
|
||||
if pageSize < minVCMeetingEventsPageSize {
|
||||
return minVCMeetingEventsPageSize, nil
|
||||
}
|
||||
if pageSize > maxVCMeetingEventsPageSize {
|
||||
return maxVCMeetingEventsPageSize, nil
|
||||
}
|
||||
return pageSize, nil
|
||||
}
|
||||
|
||||
func meetingEventsPageLimit(runtime *common.RuntimeContext) (int, error) {
|
||||
limit := runtime.Int("page-limit")
|
||||
if limit == 0 && !runtime.Cmd.Flags().Changed("page-limit") {
|
||||
@@ -129,6 +156,21 @@ func meetingEventsPageLimit(runtime *common.RuntimeContext) (int, error) {
|
||||
return limit, nil
|
||||
}
|
||||
|
||||
func meetingEventsPaginationConfig(runtime *common.RuntimeContext) (autoPaginate bool, pageLimit int, err error) {
|
||||
pageLimit, err = meetingEventsPageLimit(runtime)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
autoPaginate = runtime.Bool("page-all")
|
||||
if runtime.Cmd != nil && runtime.Cmd.Flags().Changed("page-limit") {
|
||||
autoPaginate = true
|
||||
}
|
||||
if runtime.Bool("page-all") && (runtime.Cmd == nil || !runtime.Cmd.Flags().Changed("page-limit")) {
|
||||
pageLimit = maxVCMeetingPageLimit
|
||||
}
|
||||
return autoPaginate, pageLimit, nil
|
||||
}
|
||||
|
||||
func validateMeetingEventsMeetingID(meetingID string) error {
|
||||
meetingID = strings.TrimSpace(meetingID)
|
||||
if meetingID == "" {
|
||||
@@ -175,7 +217,7 @@ func parseMeetingEventsTimeRange(runtime *common.RuntimeContext) (string, string
|
||||
}
|
||||
|
||||
func buildMeetingEventsParams(runtime *common.RuntimeContext, startTime, endTime string) (larkcore.QueryParams, error) {
|
||||
pageSize, err := common.ValidatePageSize(runtime, "page-size", defaultVCMeetingEventsSize, minVCMeetingEventsPageSize, maxVCMeetingEventsPageSize)
|
||||
pageSize, err := meetingEventsPageSize(runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -200,7 +242,11 @@ func fetchMeetingEvents(ctx context.Context, runtime *common.RuntimeContext, sta
|
||||
if err != nil {
|
||||
return nil, nil, false, "", err
|
||||
}
|
||||
if !runtime.Bool("page-all") {
|
||||
autoPaginate, pageLimit, err := meetingEventsPaginationConfig(runtime)
|
||||
if err != nil {
|
||||
return nil, nil, false, "", err
|
||||
}
|
||||
if !autoPaginate {
|
||||
data, err := runtime.DoAPIJSON(http.MethodGet, vcMeetingEventsAPIPath, params, nil)
|
||||
if err != nil {
|
||||
return nil, nil, false, "", err
|
||||
@@ -214,10 +260,6 @@ func fetchMeetingEvents(ctx context.Context, runtime *common.RuntimeContext, sta
|
||||
return data, events, hasMore, pageToken, nil
|
||||
}
|
||||
|
||||
pageLimit, err := meetingEventsPageLimit(runtime)
|
||||
if err != nil {
|
||||
return nil, nil, false, "", err
|
||||
}
|
||||
var (
|
||||
allEvents []interface{}
|
||||
lastData map[string]interface{}
|
||||
@@ -272,6 +314,35 @@ func flattenQueryParams(params larkcore.QueryParams) map[string]interface{} {
|
||||
return flat
|
||||
}
|
||||
|
||||
func compactMeetingEvents(events []interface{}) []interface{} {
|
||||
compacted := make([]interface{}, 0, len(events))
|
||||
for _, raw := range events {
|
||||
event, _ := raw.(map[string]interface{})
|
||||
if event == nil {
|
||||
continue
|
||||
}
|
||||
if payload := common.GetMap(event, "payload"); payload != nil {
|
||||
event["payload"] = compactMeetingPayload(payload)
|
||||
}
|
||||
compacted = append(compacted, event)
|
||||
}
|
||||
return compacted
|
||||
}
|
||||
|
||||
func compactMeetingPayload(payload map[string]interface{}) map[string]interface{} {
|
||||
if payload == nil {
|
||||
return nil
|
||||
}
|
||||
compacted := make(map[string]interface{}, len(payload))
|
||||
for key, value := range payload {
|
||||
if items, ok := value.([]interface{}); ok && len(items) == 0 {
|
||||
continue
|
||||
}
|
||||
compacted[key] = value
|
||||
}
|
||||
return compacted
|
||||
}
|
||||
|
||||
func buildMeetingEventRows(events []interface{}) []map[string]interface{} {
|
||||
rows := make([]map[string]interface{}, 0, len(events))
|
||||
for _, raw := range events {
|
||||
|
||||
@@ -23,7 +23,7 @@ func newMeetingEventsRuntime() *common.RuntimeContext {
|
||||
cmd.Flags().String("page-token", "", "")
|
||||
cmd.Flags().String("page-size", "", "")
|
||||
cmd.Flags().Bool("page-all", false, "")
|
||||
cmd.Flags().Int("page-limit", 20, "")
|
||||
cmd.Flags().Int("page-limit", 50, "")
|
||||
return common.TestNewRuntimeContext(cmd, defaultConfig())
|
||||
}
|
||||
|
||||
@@ -64,6 +64,32 @@ func participantJoinedEvent() map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func chatReceivedEvent() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"event_id": "event-2",
|
||||
"event_type": "chat_received",
|
||||
"event_time": "2026-04-17T08:05:00Z",
|
||||
"payload": map[string]interface{}{
|
||||
"activity_event_type": "chat_received",
|
||||
"participant_joined_items": []interface{}{},
|
||||
"participant_left_items": []interface{}{},
|
||||
"transcript_received_items": []interface{}{},
|
||||
"magic_share_started_items": []interface{}{},
|
||||
"magic_share_ended_items": []interface{}{},
|
||||
"chat_received_items": []interface{}{
|
||||
map[string]interface{}{
|
||||
"content": "hello",
|
||||
"message_type": 3,
|
||||
"operator": map[string]interface{}{
|
||||
"id": "u1",
|
||||
"user_name": "Alice",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatReceivedSummary_MultipleItems(t *testing.T) {
|
||||
payload := map[string]interface{}{
|
||||
"chat_received_items": []interface{}{
|
||||
@@ -149,30 +175,39 @@ func TestMeetingEvents_Validation_InvalidTimeRange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_Validation_InvalidPageSize(t *testing.T) {
|
||||
func TestMeetingEvents_Validation_PageSizeBelowMinDoesNotError(t *testing.T) {
|
||||
runtime := newMeetingEventsRuntime()
|
||||
_ = runtime.Cmd.Flags().Set("meeting-id", "7628568141510692381")
|
||||
_ = runtime.Cmd.Flags().Set("page-size", "10")
|
||||
|
||||
err := VCMeetingEvents.Validate(context.Background(), runtime)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for invalid page size")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no validation error for page-size clamp, got: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "must be between 20 and 100") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
func TestMeetingEvents_Validation_PageAllIgnoresInvalidPageSize(t *testing.T) {
|
||||
runtime := newMeetingEventsRuntime()
|
||||
_ = runtime.Cmd.Flags().Set("meeting-id", "7628568141510692381")
|
||||
_ = runtime.Cmd.Flags().Set("page-all", "true")
|
||||
_ = runtime.Cmd.Flags().Set("page-size", "10")
|
||||
|
||||
err := VCMeetingEvents.Validate(context.Background(), runtime)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no validation error when page-all ignores page-size, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_Validation_InvalidPageLimit(t *testing.T) {
|
||||
runtime := newMeetingEventsRuntime()
|
||||
_ = runtime.Cmd.Flags().Set("meeting-id", "7628568141510692381")
|
||||
_ = runtime.Cmd.Flags().Set("page-limit", "41")
|
||||
_ = runtime.Cmd.Flags().Set("page-limit", "201")
|
||||
|
||||
err := VCMeetingEvents.Validate(context.Background(), runtime)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for invalid page limit")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "must be between 1 and 40") {
|
||||
if !strings.Contains(err.Error(), "must be between 1 and 200") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -204,6 +239,49 @@ func TestBuildMeetingEventsParams(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMeetingEventsParams_PageSizeBelowMinClampsToMin(t *testing.T) {
|
||||
runtime := newMeetingEventsRuntime()
|
||||
_ = runtime.Cmd.Flags().Set("meeting-id", "7628568141510692381")
|
||||
_ = runtime.Cmd.Flags().Set("page-size", "10")
|
||||
|
||||
params, err := buildMeetingEventsParams(runtime, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("buildMeetingEventsParams() error = %v", err)
|
||||
}
|
||||
if got := params["page_size"][0]; got != "20" {
|
||||
t.Fatalf("page_size = %q, want %q when below min", got, "20")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMeetingEventsParams_PageSizeAboveMaxClampsToMax(t *testing.T) {
|
||||
runtime := newMeetingEventsRuntime()
|
||||
_ = runtime.Cmd.Flags().Set("meeting-id", "7628568141510692381")
|
||||
_ = runtime.Cmd.Flags().Set("page-size", "999")
|
||||
|
||||
params, err := buildMeetingEventsParams(runtime, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("buildMeetingEventsParams() error = %v", err)
|
||||
}
|
||||
if got := params["page_size"][0]; got != "100" {
|
||||
t.Fatalf("page_size = %q, want %q when above max", got, "100")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMeetingEventsParams_PageAllUsesMaxPageSize(t *testing.T) {
|
||||
runtime := newMeetingEventsRuntime()
|
||||
_ = runtime.Cmd.Flags().Set("meeting-id", "7628568141510692381")
|
||||
_ = runtime.Cmd.Flags().Set("page-all", "true")
|
||||
_ = runtime.Cmd.Flags().Set("page-size", "50")
|
||||
|
||||
params, err := buildMeetingEventsParams(runtime, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("buildMeetingEventsParams() error = %v", err)
|
||||
}
|
||||
if got := params["page_size"][0]; got != "100" {
|
||||
t.Fatalf("page_size = %q, want %q when page-all is set", got, "100")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_DryRun(t *testing.T) {
|
||||
f, stdout, _, _ := cmdutil.TestFactory(t, defaultConfig())
|
||||
err := mountAndRun(t, VCMeetingEvents, []string{
|
||||
@@ -235,6 +313,24 @@ func TestMeetingEvents_DryRun(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_DryRun_PageAllUsesMaxLimit(t *testing.T) {
|
||||
f, stdout, _, _ := cmdutil.TestFactory(t, defaultConfig())
|
||||
err := mountAndRun(t, VCMeetingEvents, []string{
|
||||
"+meeting-events",
|
||||
"--meeting-id", "7628568141510692381",
|
||||
"--page-all",
|
||||
"--dry-run",
|
||||
"--as", "user",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(stdout.String(), "Auto-paginates up to 200 page(s)") {
|
||||
t.Fatalf("dry-run output missing auto-pagination description: %s", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_ExecuteJSON_PageAll(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig())
|
||||
reg.Register(meetingEventsStub([]interface{}{participantJoinedEvent()}, true, "pt_2"))
|
||||
@@ -263,6 +359,30 @@ func TestMeetingEvents_ExecuteJSON_PageAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_ExecuteJSON_PageLimitEnablesPagination(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig())
|
||||
reg.Register(meetingEventsStub([]interface{}{participantJoinedEvent()}, true, "pt_2"))
|
||||
reg.Register(meetingEventsStub([]interface{}{participantJoinedEvent()}, false, ""))
|
||||
|
||||
err := mountAndRun(t, VCMeetingEvents, []string{
|
||||
"+meeting-events",
|
||||
"--meeting-id", "7628568141510692381",
|
||||
"--format", "json",
|
||||
"--page-limit", "2",
|
||||
"--as", "user",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
reg.Verify(t)
|
||||
|
||||
out := strings.ReplaceAll(stdout.String(), " ", "")
|
||||
out = strings.ReplaceAll(out, "\n", "")
|
||||
if count := strings.Count(out, `"event_type":"participant_joined"`); count != 2 {
|
||||
t.Fatalf("expected 2 aggregated events, got %d: %s", count, stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_ExecuteJSON(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig())
|
||||
reg.Register(meetingEventsStub([]interface{}{participantJoinedEvent()}, true, "1710000000000000000"))
|
||||
@@ -292,6 +412,38 @@ func TestMeetingEvents_ExecuteJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_ExecuteJSON_PrunesEmptySlices(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig())
|
||||
reg.Register(meetingEventsStub([]interface{}{chatReceivedEvent()}, false, ""))
|
||||
|
||||
err := mountAndRun(t, VCMeetingEvents, []string{
|
||||
"+meeting-events",
|
||||
"--meeting-id", "7628568141510692381",
|
||||
"--format", "json",
|
||||
"--as", "user",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
reg.Verify(t)
|
||||
|
||||
out := stdout.String()
|
||||
for _, unwanted := range []string{
|
||||
`"participant_joined_items": []`,
|
||||
`"participant_left_items": []`,
|
||||
`"transcript_received_items": []`,
|
||||
`"magic_share_started_items": []`,
|
||||
`"magic_share_ended_items": []`,
|
||||
} {
|
||||
if strings.Contains(out, unwanted) {
|
||||
t.Fatalf("json output should not contain %q: %s", unwanted, out)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(out, `"message_type": 3`) {
|
||||
t.Fatalf("json output should keep numeric fields: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingEvents_ExecutePretty(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig())
|
||||
reg.Register(meetingEventsStub([]interface{}{participantJoinedEvent()}, true, "1710000000000000000"))
|
||||
|
||||
Reference in New Issue
Block a user