diff --git a/api4/license.go b/api4/license.go index bc89f7e1e2..749fd71ead 100644 --- a/api4/license.go +++ b/api4/license.go @@ -9,7 +9,9 @@ import ( "fmt" "io" "net/http" + "os" + "github.com/mattermost/mattermost-server/v6/services/telemetry" "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/utils" @@ -24,6 +26,7 @@ func (api *API) InitLicense() { api.BaseRoutes.APIRoot.Handle("/license", api.APISessionRequired(removeLicense)).Methods("DELETE") api.BaseRoutes.APIRoot.Handle("/license/renewal", api.APISessionRequired(requestRenewalLink)).Methods("GET") api.BaseRoutes.APIRoot.Handle("/license/client", api.APIHandler(getClientLicense)).Methods("GET") + api.BaseRoutes.APIRoot.Handle("/license/review", api.APIHandler(requestTrueUpReview)).Methods("POST") } func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) { @@ -296,3 +299,83 @@ func getPrevTrialLicense(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJSON(clientLicense))) } + +func requestTrueUpReview(c *Context, w http.ResponseWriter, r *http.Request) { + license := c.App.Channels().License() + if license == nil { + return + } + + userId := c.AppContext.Session().UserId + subscription, err := c.App.Cloud().GetSubscription(userId) + if err != nil { + fmt.Printf("1: %+v", err) + return + } + + reviewProfile := model.TrueUpReviewProfile{} + + // Server Data + reviewProfile.ServerId = c.App.TelemetryId() + reviewProfile.ServerVersion = model.CurrentVersion + reviewProfile.ServerInstallationType = os.Getenv(telemetry.EnvVarInstallType) + + // License Data + reviewProfile.LicenseId = license.Id + reviewProfile.LicensedSeats = subscription.Seats + reviewProfile.LicensePlan = license.SkuName + + activeUserCount, err := c.App.Srv().GetStore().Status().GetTotalActiveUsersCount() + if err != nil { + fmt.Printf("2: %+v", err) + return + } + + // Customer Info & Usage Analytics + reviewProfile.CustomerName = license.Customer.Name + reviewProfile.ActiveUsers = activeUserCount + + // Webhook, call, board, playbook counts + var totalWebHookCount int64 = 0 + incomingWebhookCount, err := c.App.Srv().Store().Webhook().GetIncomingTotal() + if err != nil { + fmt.Printf("3: %+v", err) + return + } + outgoingWebhookCount, err := c.App.Srv().Store().Webhook().GetOutgoingTotal() + if err != nil { + fmt.Printf("4: %+v", err) + return + } + + totalWebHookCount += incomingWebhookCount + totalWebHookCount += outgoingWebhookCount + + reviewProfile.TotalWebhooks = totalWebHookCount + reviewProfile.TotalCalls = 0 + reviewProfile.TotalBoards = 0 + reviewProfile.TotalPlaybooks = 0 + + // Plugin Data + trueUpReviewPlugins := model.TrueUpReviewPlugins{} + if pluginResponse, err := c.App.GetPlugins(); err == nil { + for _, plugin := range pluginResponse.Active { + trueUpReviewPlugins.ActivePluginNames = append(trueUpReviewPlugins.ActivePluginNames, plugin.Name) + trueUpReviewPlugins.TotalActivePlugins += 1 + } + + for _, plugin := range pluginResponse.Inactive { + trueUpReviewPlugins.InactivePluginNames = append(trueUpReviewPlugins.InactivePluginNames, plugin.Name) + trueUpReviewPlugins.TotalInactivePlugins += 1 + } + } + + reviewProfile.Plugins = trueUpReviewPlugins + + json, err := json.Marshal(reviewProfile) + if err != nil { + fmt.Printf("5: %+v", err) + return + } + w.Write(json) +} diff --git a/model/true_up_review_profile.go b/model/true_up_review_profile.go index 87f0741390..67b51a3e88 100644 --- a/model/true_up_review_profile.go +++ b/model/true_up_review_profile.go @@ -7,20 +7,23 @@ type TrueUpReviewProfile struct { ServerId string `json:"server_id"` ServerVersion string `json:"server_version"` ServerInstallationType string `json:"server_installation_type"` - CustomerName *string `json:"customer_name"` // Might not be availabe? LicenseId string `json:"licnes_id"` - LicnesedSeats int `json:"license_seats"` + LicensedSeats int `json:"licensed_seats"` LicensePlan string `json:"license_plan"` - ActiveUsers int `json:"active_users"` + CustomerName string `json:"customer_name"` + ActiveUsers int64 `json:"active_users"` AuthenticationFeatures []string `json:"authentication_features"` Plugins TrueUpReviewPlugins `json:"plugins"` - TotalWebhooks int `json:"webhooks_count"` + TotalWebhooks int64 `json:"webhooks_count"` TotalPlaybooks int `json:"playbooks_count"` TotalBoards int `json:"boards_count"` TotalCalls int `json:"calls_count"` } type TrueUpReviewPlugins struct { - TotalPlugins int `json:"total_plugins"` - PluginNames []string `json:"plugin_names"` + TotalPlugins int `json:"total_plugins"` + TotalActivePlugins int `json:"total_active_plugins"` + TotalInactivePlugins int `json:"total_inactive_plugins"` + ActivePluginNames []string `json:"active_plugin_names"` + InactivePluginNames []string `json:"inactive_plugin_names"` } diff --git a/services/telemetry/telemetry.go b/services/telemetry/telemetry.go index 9db3a36d00..67a0ca1209 100644 --- a/services/telemetry/telemetry.go +++ b/services/telemetry/telemetry.go @@ -81,6 +81,8 @@ const ( TrackLicense = "license" TrackServer = "server" TrackPlugins = "plugins" + + TrackTrueUpReview = "true_up_review" ) type ServerIface interface { diff --git a/store/sqlstore/webhook_store.go b/store/sqlstore/webhook_store.go index d9f08120ac..df10c2f1a0 100644 --- a/store/sqlstore/webhook_store.go +++ b/store/sqlstore/webhook_store.go @@ -400,3 +400,39 @@ func (s SqlWebhookStore) AnalyticsOutgoingCount(teamId string) (int64, error) { } return count, nil } + +func (s SqlWebhookStore) GetIncomingTotal() (int64, error) { + queryBuilder := + s.getQueryBuilder(). + Select("COUNT(*)"). + From("IncomingWebhooks") + + queryString, args, err := queryBuilder.ToSql() + if err != nil { + return 0, errors.Wrap(err, "incoming_webhook_tosql") + } + + var count int64 + if err := s.GetReplicaX().Get(&count, queryString, args...); err != nil { + return 0, errors.Wrap(err, "failed to count total IncomingWebooks") + } + return count, nil +} + +func (s SqlWebhookStore) GetOutgoingTotal() (int64, error) { + queryBuilder := + s.getQueryBuilder(). + Select("COUNT(*)"). + From("OutgoingWebhooks") + + queryString, args, err := queryBuilder.ToSql() + if err != nil { + return 0, errors.Wrap(err, "outgoing_webhook_tosql") + } + + var count int64 + if err := s.GetReplicaX().Get(&count, queryString, args...); err != nil { + return 0, errors.Wrap(err, "failed to count total OutgoingWebhooks") + } + return count, nil +} diff --git a/store/store.go b/store/store.go index 6a2a7d6e1d..3014d7bcf6 100644 --- a/store/store.go +++ b/store/store.go @@ -615,6 +615,9 @@ type WebhookStore interface { AnalyticsOutgoingCount(teamID string) (int64, error) InvalidateWebhookCache(webhook string) ClearCaches() + + GetOutgoingTotal() (int64, error) + GetIncomingTotal() (int64, error) } type CommandStore interface { diff --git a/store/storetest/mocks/WebhookStore.go b/store/storetest/mocks/WebhookStore.go index 5abfc36738..b6ce04cc3d 100644 --- a/store/storetest/mocks/WebhookStore.go +++ b/store/storetest/mocks/WebhookStore.go @@ -227,6 +227,27 @@ func (_m *WebhookStore) GetIncomingListByUser(userID string, offset int, limit i return r0, r1 } +// GetIncomingTotal provides a mock function with given fields: +func (_m *WebhookStore) GetIncomingTotal() (int64, error) { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetOutgoing provides a mock function with given fields: id func (_m *WebhookStore) GetOutgoing(id string) (*model.OutgoingWebhook, error) { ret := _m.Called(id) @@ -388,6 +409,27 @@ func (_m *WebhookStore) GetOutgoingListByUser(userID string, offset int, limit i return r0, r1 } +// GetOutgoingTotal provides a mock function with given fields: +func (_m *WebhookStore) GetOutgoingTotal() (int64, error) { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // InvalidateWebhookCache provides a mock function with given fields: webhook func (_m *WebhookStore) InvalidateWebhookCache(webhook string) { _m.Called(webhook)