mirror of
https://github.com/mattermost/mattermost.git
synced 2026-08-27 05:37:15 -05:00
MM-43084 update product limits through webhook and websocket (#20076)
* Define interfaces for updating subscriptions and product limits from CWS webhook.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||
)
|
||||
|
||||
func (api *API) InitCloud() {
|
||||
@@ -486,6 +487,21 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
case model.EventTypeSubscriptionChanged:
|
||||
// event.ProductLimits is nil if there was no change
|
||||
if event.ProductLimits != nil {
|
||||
if pluginsEnvironment := c.App.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||
pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
|
||||
hooks.OnCloudLimitsUpdated(event.ProductLimits)
|
||||
return true
|
||||
}, plugin.OnCloudLimitsUpdatedID)
|
||||
}
|
||||
}
|
||||
if err := c.App.Cloud().UpdateSubscriptionFromHook(event.ProductLimits, event.Subscription); err != nil {
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.subscription.update_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
c.Logger.Info("Updated subscription from webhook event")
|
||||
|
||||
default:
|
||||
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.cws_webhook_event_missing_error", nil, "", http.StatusNotFound)
|
||||
|
||||
@@ -1179,3 +1179,9 @@ func (api *PluginAPI) RequestTrialLicense(requesterID string, users int, termsAc
|
||||
|
||||
return api.app.Channels().RequestTrialLicense(requesterID, users, termsAccepted, receiveEmailsAccepted)
|
||||
}
|
||||
|
||||
// GetCloudLimits returns any limits associated with the cloud instance
|
||||
func (api *PluginAPI) GetCloudLimits() (*model.ProductLimits, error) {
|
||||
limits, err := api.app.Cloud().GetCloudLimits("")
|
||||
return limits, err
|
||||
}
|
||||
|
||||
@@ -1302,3 +1302,47 @@ func TestHookOnSendDailyTelemetry(t *testing.T) {
|
||||
|
||||
require.True(t, hookCalled)
|
||||
}
|
||||
|
||||
func TestHookOnCloudLimitsUpdated(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
tearDown, pluginIDs, _ := SetAppEnvironmentWithPlugins(t,
|
||||
[]string{
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnCloudLimitsUpdated(_ *model.ProductLimits) {
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
|
||||
require.Len(t, pluginIDs, 1)
|
||||
pluginID := pluginIDs[0]
|
||||
|
||||
require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID))
|
||||
|
||||
hookCalled := false
|
||||
th.App.GetPluginsEnvironment().RunMultiPluginHook(func(hooks plugin.Hooks) bool {
|
||||
hooks.OnCloudLimitsUpdated(nil)
|
||||
|
||||
hookCalled = true
|
||||
return hookCalled
|
||||
}, plugin.OnCloudLimitsUpdatedID)
|
||||
|
||||
require.True(t, hookCalled)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
type CloudInterface interface {
|
||||
GetCloudProducts(userID string, includeLegacyProducts bool) ([]*model.Product, error)
|
||||
GetCloudLimits(userID string) (*model.ProductLimits, error)
|
||||
UpdateSubscriptionFromHook(*model.ProductLimits, *model.Subscription) error
|
||||
|
||||
CreateCustomerPayment(userID string) (*model.StripeSetupIntent, error)
|
||||
ConfirmCustomerPayment(userID string, confirmRequest *model.ConfirmPaymentMethodRequest) error
|
||||
|
||||
@@ -292,3 +292,17 @@ func (_m *CloudInterface) UpdateCloudCustomerAddress(userID string, address *mod
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateSubscriptionFromHook provides a mock function with given fields: _a0, _a1
|
||||
func (_m *CloudInterface) UpdateSubscriptionFromHook(_a0 *model.ProductLimits, _a1 *model.Subscription) error {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.ProductLimits, *model.Subscription) error); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
@@ -475,6 +475,10 @@
|
||||
"id": "api.cloud.request_error",
|
||||
"translation": "Error processing request to CWS."
|
||||
},
|
||||
{
|
||||
"id": "api.cloud.subscription.update_error",
|
||||
"translation": "Error updating subscription from webhook."
|
||||
},
|
||||
{
|
||||
"id": "api.command.admin_only.app_error",
|
||||
"translation": "Integrations have been limited to admins only."
|
||||
|
||||
@@ -10,6 +10,7 @@ const (
|
||||
EventTypeFailedPaymentNoCard = "failed-payment-no-card"
|
||||
EventTypeSendAdminWelcomeEmail = "send-admin-welcome-email"
|
||||
EventTypeSendUpgradeConfirmationEmail = "send-upgrade-confirmation-email"
|
||||
EventTypeSubscriptionChanged = "subscription-changed"
|
||||
EventTypeTrialWillEnd = "trial-will-end"
|
||||
EventTypeTrialEnded = "trial-ended"
|
||||
)
|
||||
@@ -166,6 +167,8 @@ type CWSWebhookPayload struct {
|
||||
Event string `json:"event"`
|
||||
FailedPayment *FailedPayment `json:"failed_payment"`
|
||||
CloudWorkspaceOwner *CloudWorkspaceOwner `json:"cloud_workspace_owner"`
|
||||
ProductLimits *ProductLimits `json:"product_limits"`
|
||||
Subscription *Subscription `json:"subscription"`
|
||||
SubscriptionTrialEndUnixTimeStamp int64 `json:"trial_end_time_stamp"`
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ const (
|
||||
WebsocketWarnMetricStatusReceived = "warn_metric_status_received"
|
||||
WebsocketWarnMetricStatusRemoved = "warn_metric_status_removed"
|
||||
WebsocketEventCloudPaymentStatusUpdated = "cloud_payment_status_updated"
|
||||
WebsocketEventCloudSubscriptionChanged = "cloud_subscription_changed"
|
||||
WebsocketEventThreadUpdated = "thread_updated"
|
||||
WebsocketEventThreadFollowChanged = "thread_follow_changed"
|
||||
WebsocketEventThreadReadChanged = "thread_read_changed"
|
||||
|
||||
@@ -1147,6 +1147,11 @@ type API interface {
|
||||
//
|
||||
// Minimum server version: 5.36
|
||||
RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError
|
||||
|
||||
// GetCloudLimits gets limits associated with a cloud workspace, if any
|
||||
//
|
||||
// Minimum server version: 7.0
|
||||
GetCloudLimits() (*model.ProductLimits, error)
|
||||
}
|
||||
|
||||
var handshake = plugin.HandshakeConfig{
|
||||
|
||||
@@ -1225,3 +1225,10 @@ func (api *apiTimerLayer) RequestTrialLicense(requesterID string, users int, ter
|
||||
api.recordTime(startTime, "RequestTrialLicense", _returnsA == nil)
|
||||
return _returnsA
|
||||
}
|
||||
|
||||
func (api *apiTimerLayer) GetCloudLimits() (*model.ProductLimits, error) {
|
||||
startTime := timePkg.Now()
|
||||
_returnsA, _returnsB := api.apiImpl.GetCloudLimits()
|
||||
api.recordTime(startTime, "GetCloudLimits", _returnsB == nil)
|
||||
return _returnsA, _returnsB
|
||||
}
|
||||
|
||||
@@ -774,6 +774,39 @@ func (s *hooksRPCServer) OnSendDailyTelemetry(args *Z_OnSendDailyTelemetryArgs,
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
hookNameToId["OnCloudLimitsUpdated"] = OnCloudLimitsUpdatedID
|
||||
}
|
||||
|
||||
type Z_OnCloudLimitsUpdatedArgs struct {
|
||||
A *model.ProductLimits
|
||||
}
|
||||
|
||||
type Z_OnCloudLimitsUpdatedReturns struct {
|
||||
}
|
||||
|
||||
func (g *hooksRPCClient) OnCloudLimitsUpdated(limits *model.ProductLimits) {
|
||||
_args := &Z_OnCloudLimitsUpdatedArgs{limits}
|
||||
_returns := &Z_OnCloudLimitsUpdatedReturns{}
|
||||
if g.implemented[OnCloudLimitsUpdatedID] {
|
||||
if err := g.client.Call("Plugin.OnCloudLimitsUpdated", _args, _returns); err != nil {
|
||||
g.log.Error("RPC call OnCloudLimitsUpdated to plugin failed.", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *hooksRPCServer) OnCloudLimitsUpdated(args *Z_OnCloudLimitsUpdatedArgs, returns *Z_OnCloudLimitsUpdatedReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
OnCloudLimitsUpdated(limits *model.ProductLimits)
|
||||
}); ok {
|
||||
hook.OnCloudLimitsUpdated(args.A)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("Hook OnCloudLimitsUpdated called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_RegisterCommandArgs struct {
|
||||
A *model.Command
|
||||
}
|
||||
@@ -5616,3 +5649,32 @@ func (s *apiRPCServer) RequestTrialLicense(args *Z_RequestTrialLicenseArgs, retu
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Z_GetCloudLimitsArgs struct {
|
||||
}
|
||||
|
||||
type Z_GetCloudLimitsReturns struct {
|
||||
A *model.ProductLimits
|
||||
B error
|
||||
}
|
||||
|
||||
func (g *apiRPCClient) GetCloudLimits() (*model.ProductLimits, error) {
|
||||
_args := &Z_GetCloudLimitsArgs{}
|
||||
_returns := &Z_GetCloudLimitsReturns{}
|
||||
if err := g.client.Call("Plugin.GetCloudLimits", _args, _returns); err != nil {
|
||||
log.Printf("RPC call to GetCloudLimits API failed: %s", err.Error())
|
||||
}
|
||||
return _returns.A, _returns.B
|
||||
}
|
||||
|
||||
func (s *apiRPCServer) GetCloudLimits(args *Z_GetCloudLimitsArgs, returns *Z_GetCloudLimitsReturns) error {
|
||||
if hook, ok := s.impl.(interface {
|
||||
GetCloudLimits() (*model.ProductLimits, error)
|
||||
}); ok {
|
||||
returns.A, returns.B = hook.GetCloudLimits()
|
||||
returns.B = encodableError(returns.B)
|
||||
} else {
|
||||
return encodableError(fmt.Errorf("API GetCloudLimits called but not implemented."))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ const (
|
||||
RunDataRetentionID = 24
|
||||
OnInstallID = 25
|
||||
OnSendDailyTelemetryID = 26
|
||||
OnCloudLimitsUpdatedID = 27
|
||||
TotalHooksID = iota
|
||||
)
|
||||
|
||||
@@ -264,4 +265,9 @@ type Hooks interface {
|
||||
//
|
||||
// Minimum server version: 6.5
|
||||
OnSendDailyTelemetry()
|
||||
|
||||
// OnCloudLimitsUpdated is invoked product limits change, for example when plan tiers change
|
||||
//
|
||||
// Minimum server version: 7.0
|
||||
OnCloudLimitsUpdated(limits *model.ProductLimits)
|
||||
}
|
||||
|
||||
@@ -206,3 +206,9 @@ func (hooks *hooksTimerLayer) OnSendDailyTelemetry() {
|
||||
hooks.hooksImpl.OnSendDailyTelemetry()
|
||||
hooks.recordTime(startTime, "OnSendDailyTelemetry", true)
|
||||
}
|
||||
|
||||
func (hooks *hooksTimerLayer) OnCloudLimitsUpdated(limits *model.ProductLimits) {
|
||||
startTime := timePkg.Now()
|
||||
hooks.hooksImpl.OnCloudLimitsUpdated(limits)
|
||||
hooks.recordTime(startTime, "OnCloudLimitsUpdated", true)
|
||||
}
|
||||
|
||||
@@ -980,6 +980,29 @@ func (_m *API) GetChannelsForTeamForUser(teamID string, userID string, includeDe
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetCloudLimits provides a mock function with given fields:
|
||||
func (_m *API) GetCloudLimits() (*model.ProductLimits, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *model.ProductLimits
|
||||
if rf, ok := ret.Get(0).(func() *model.ProductLimits); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.ProductLimits)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetCommand provides a mock function with given fields: commandID
|
||||
func (_m *API) GetCommand(commandID string) (*model.Command, error) {
|
||||
ret := _m.Called(commandID)
|
||||
|
||||
@@ -166,6 +166,11 @@ func (_m *Hooks) OnActivate() error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// OnCloudLimitsUpdated provides a mock function with given fields: limits
|
||||
func (_m *Hooks) OnCloudLimitsUpdated(limits *model.ProductLimits) {
|
||||
_m.Called(limits)
|
||||
}
|
||||
|
||||
// OnConfigurationChange provides a mock function with given fields:
|
||||
func (_m *Hooks) OnConfigurationChange() error {
|
||||
ret := _m.Called()
|
||||
|
||||
Reference in New Issue
Block a user