From ece6b956facb44cc4ab10e17ff15bbe5914a6aea Mon Sep 17 00:00:00 2001 From: Maria A Nunez Date: Mon, 30 Mar 2026 11:13:09 -0400 Subject: [PATCH] Add single-channel guest count to support packet stats (#35846) Single-channel guests are excluded from billable seat counts for licensing. Include this metric in the support packet so support engineers can understand seat calculation discrepancies. Made-with: Cursor Co-authored-by: Mattermost Build --- server/channels/app/support_packet.go | 5 ++++ server/channels/app/support_packet_test.go | 14 +++++++++++ server/public/model/support_packet.go | 27 +++++++++++----------- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/server/channels/app/support_packet.go b/server/channels/app/support_packet.go index 069129d185d..89fd39c10d9 100644 --- a/server/channels/app/support_packet.go +++ b/server/channels/app/support_packet.go @@ -173,6 +173,11 @@ func (a *App) getSupportPacketStats(rctx request.CTX) (*model.FileData, error) { rErr = multierror.Append(errors.Wrap(err, "failed to get guest count")) } + stats.SingleChannelGuests, err = a.Srv().Store().User().AnalyticsGetSingleChannelGuestCount() + if err != nil { + rErr = multierror.Append(errors.Wrap(err, "failed to get single channel guest count")) + } + stats.BotAccounts, err = a.Srv().Store().User().Count(model.UserCountOptions{IncludeBotAccounts: true, ExcludeRegularUsers: true}) if err != nil { rErr = multierror.Append(errors.Wrap(err, "failed to get bot acount count")) diff --git a/server/channels/app/support_packet_test.go b/server/channels/app/support_packet_test.go index 3eedc886c7b..6a521c6cf9b 100644 --- a/server/channels/app/support_packet_test.go +++ b/server/channels/app/support_packet_test.go @@ -368,6 +368,7 @@ func TestGetSupportPacketStats(t *testing.T) { assert.Equal(t, int64(0), sp.MonthlyActiveUsers) assert.Equal(t, int64(0), sp.DeactivatedUsers) assert.Equal(t, int64(0), sp.Guests) + assert.Equal(t, int64(0), sp.SingleChannelGuests) assert.Equal(t, int64(0), sp.BotAccounts) assert.Equal(t, int64(0), sp.Posts) assert.Equal(t, int64(0), sp.Channels) @@ -437,6 +438,7 @@ func TestGetSupportPacketStats(t *testing.T) { assert.Equal(t, int64(0), sp.MonthlyActiveUsers) assert.Equal(t, int64(3), sp.DeactivatedUsers) assert.Equal(t, int64(2), sp.Guests) + assert.Equal(t, int64(0), sp.SingleChannelGuests) assert.Equal(t, int64(1), sp.BotAccounts) assert.Equal(t, int64(4), sp.Posts) // 1 from the bot creation and 3 created directly assert.Equal(t, int64(3), sp.Channels) // 2 from the team creation and 1 created directly @@ -446,6 +448,18 @@ func TestGetSupportPacketStats(t *testing.T) { assert.Equal(t, int64(1), sp.OutgoingWebhooks) }) + t.Run("single channel guests are counted when a guest is in exactly one channel", func(t *testing.T) { + th := Setup(t).InitBasic(t) + channel := th.CreateChannel(t, th.BasicTeam) + + guest := th.CreateGuest(t) + th.LinkUserToTeam(t, guest, th.BasicTeam) + th.AddUserToChannel(t, guest, channel) + + sp := generateStats(t, th.Context, th.App) + assert.Equal(t, int64(1), sp.SingleChannelGuests) + }) + t.Run("post count should be present if number of users extends AnalyticsSettings.MaxUsersForStatistics", func(t *testing.T) { // Setup a new test helper th := Setup(t).InitBasic(t) diff --git a/server/public/model/support_packet.go b/server/public/model/support_packet.go index 26e631a932a..78bc2d4a56f 100644 --- a/server/public/model/support_packet.go +++ b/server/public/model/support_packet.go @@ -83,19 +83,20 @@ type SupportPacketDiagnostics struct { } type SupportPacketStats struct { - RegisteredUsers int64 `yaml:"registered_users"` - ActiveUsers int64 `yaml:"active_users"` - DailyActiveUsers int64 `yaml:"daily_active_users"` - MonthlyActiveUsers int64 `yaml:"monthly_active_users"` - DeactivatedUsers int64 `yaml:"deactivated_users"` - Guests int64 `yaml:"guests"` - BotAccounts int64 `yaml:"bot_accounts"` - Posts int64 `yaml:"posts"` - Channels int64 `yaml:"channels"` - Teams int64 `yaml:"teams"` - SlashCommands int64 `yaml:"slash_commands"` - IncomingWebhooks int64 `yaml:"incoming_webhooks"` - OutgoingWebhooks int64 `yaml:"outgoing_webhooks"` + RegisteredUsers int64 `yaml:"registered_users"` + ActiveUsers int64 `yaml:"active_users"` + DailyActiveUsers int64 `yaml:"daily_active_users"` + MonthlyActiveUsers int64 `yaml:"monthly_active_users"` + DeactivatedUsers int64 `yaml:"deactivated_users"` + Guests int64 `yaml:"guests"` + SingleChannelGuests int64 `yaml:"single_channel_guests"` + BotAccounts int64 `yaml:"bot_accounts"` + Posts int64 `yaml:"posts"` + Channels int64 `yaml:"channels"` + Teams int64 `yaml:"teams"` + SlashCommands int64 `yaml:"slash_commands"` + IncomingWebhooks int64 `yaml:"incoming_webhooks"` + OutgoingWebhooks int64 `yaml:"outgoing_webhooks"` } // SupportPacketJobList contains the list of latest run enterprise job runs.