[MM-48542] Removing integration limits (#21282)
* Removing integration limits * Remove freemium limit test * Remove test assertion regarding cloud limits * Remove GetIntegrationsUsage * Removing integrations usage notifications * This shouldn't be removed * Removing client call and websocket event * Remove old translations Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0509e78744
Коммит
7f419ea091
@@ -69,8 +69,6 @@ type AppIface interface {
|
||||
// If includeRemovedMembers is true, then channel members who left or were removed from the channel will
|
||||
// be included; otherwise, they will be excluded.
|
||||
ChannelMembersToAdd(since int64, channelID *string, includeRemovedMembers bool) ([]*model.UserChannelIDPair, *model.AppError)
|
||||
// CheckFreemiumLimitsForConfigSave returns an error if the configuration being saved violates a cloud plan's limits
|
||||
CheckFreemiumLimitsForConfigSave(oldConfig, newConfig *model.Config) *model.AppError
|
||||
// CheckProviderAttributes returns the empty string if the patch can be applied without
|
||||
// overriding attributes set by the user's login provider; otherwise, the name of the offending
|
||||
// field is returned.
|
||||
@@ -184,8 +182,6 @@ type AppIface interface {
|
||||
GetFilteredUsersStats(options *model.UserCountOptions) (*model.UsersStats, *model.AppError)
|
||||
// GetGroupsByTeam returns the paged list and the total count of group associated to the given team.
|
||||
GetGroupsByTeam(teamID string, opts model.GroupSearchOpts) ([]*model.GroupWithSchemeAdmin, int, *model.AppError)
|
||||
// GetIntegrationsUsage returns usage information on enabled integrations
|
||||
GetIntegrationsUsage() (*model.IntegrationsUsage, *model.AppError)
|
||||
// GetKnownUsers returns the list of user ids of users with any direct
|
||||
// relationship with a user. That means any user sharing any channel, including
|
||||
// direct and group channels.
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
func (a *App) checkIntegrationLimitsForConfigSave(oldConfig, newConfig *model.Config) *model.AppError {
|
||||
pluginIds := []string{}
|
||||
for pluginId, newState := range newConfig.PluginSettings.PluginStates {
|
||||
oldState, ok := oldConfig.PluginSettings.PluginStates[pluginId]
|
||||
if newState.Enable && !(ok && oldState.Enable) {
|
||||
pluginIds = append(pluginIds, pluginId)
|
||||
}
|
||||
}
|
||||
|
||||
if len(pluginIds) > 0 {
|
||||
return a.checkIfIntegrationsMeetFreemiumLimits(pluginIds)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *Channels) getInstalledIntegrations() ([]*model.InstalledIntegration, *model.AppError) {
|
||||
out := []*model.InstalledIntegration{}
|
||||
|
||||
pluginsEnvironment := ch.GetPluginsEnvironment()
|
||||
if pluginsEnvironment == nil {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
plugins, err := pluginsEnvironment.Available()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("getInstalledIntegrations", "app.plugin.sync.read_local_folder.app_error", nil, "", 0).Wrap(err)
|
||||
}
|
||||
|
||||
pluginStates := ch.cfgSvc.Config().PluginSettings.PluginStates
|
||||
for _, p := range plugins {
|
||||
if _, ok := model.InstalledIntegrationsIgnoredPlugins[p.Manifest.Id]; !ok {
|
||||
enabled := false
|
||||
if state, ok := pluginStates[p.Manifest.Id]; ok {
|
||||
enabled = state.Enable
|
||||
}
|
||||
|
||||
integration := &model.InstalledIntegration{
|
||||
Type: "plugin",
|
||||
ID: p.Manifest.Id,
|
||||
Name: p.Manifest.Name,
|
||||
Version: p.Manifest.Version,
|
||||
Enabled: enabled,
|
||||
}
|
||||
|
||||
out = append(out, integration)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort result alphabetically, by display name.
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name)
|
||||
})
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *App) checkIfIntegrationsMeetFreemiumLimits(originalPluginIds []string) *model.AppError {
|
||||
if !a.License().IsCloud() {
|
||||
return nil
|
||||
}
|
||||
|
||||
pluginIds := map[string]bool{}
|
||||
for _, pluginId := range originalPluginIds {
|
||||
if _, ok := model.InstalledIntegrationsIgnoredPlugins[pluginId]; !ok {
|
||||
pluginIds[pluginId] = true
|
||||
}
|
||||
}
|
||||
|
||||
limits, err := a.Cloud().GetCloudLimits("")
|
||||
if err != nil {
|
||||
a.Log().Error("Error fetching cloud limits for enabled integrations", mlog.Err(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
if limits == nil || limits.Integrations == nil || limits.Integrations.Enabled == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
installed, appErr := a.ch.getInstalledIntegrations()
|
||||
if appErr != nil {
|
||||
a.Log().Error("Failed to get installed integrations to check cloud limit", mlog.Err(appErr))
|
||||
return nil
|
||||
}
|
||||
|
||||
enableCount := len(pluginIds)
|
||||
for _, integration := range installed {
|
||||
if _, ok := pluginIds[integration.ID]; !ok && integration.Enabled {
|
||||
enableCount++
|
||||
}
|
||||
}
|
||||
|
||||
limit := *limits.Integrations.Enabled
|
||||
if enableCount > limit {
|
||||
return model.NewAppError("checkIfIntegrationMeetsFreemiumLimits", "app.install_integration.reached_max_limit.error", map[string]any{"NumIntegrations": limit}, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetIntegrationsUsage(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
samplePluginCode := `
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`
|
||||
|
||||
setupMultiPluginAPITest(t,
|
||||
[]string{samplePluginCode, samplePluginCode, samplePluginCode, samplePluginCode, samplePluginCode, samplePluginCode, samplePluginCode}, []string{
|
||||
`{"id": "otherplugin", "name": "Other Plugin", "version": "1.2.0", "server": {"executable": "backend.exe"}}`,
|
||||
`{"id": "mattermost-autolink", "name": "Autolink", "version": "1.2.0", "server": {"executable": "backend.exe"}}`,
|
||||
`{"id": "playbooks", "name": "Playbooks", "version": "1.2.0", "server": {"executable": "backend.exe"}}`,
|
||||
`{"id": "focalboard", "name": "Mattermost Boards", "version": "1.2.0", "server": {"executable": "backend.exe"}}`,
|
||||
`{"id": "com.mattermost.calls", "name": "Calls", "version": "1.2.0", "server": {"executable": "backend.exe"}}`,
|
||||
`{"id": "com.mattermost.nps", "name": "User Satisfaction Surveys", "version": "1.2.0", "server": {"executable": "backend.exe"}}`,
|
||||
`{"id": "com.mattermost.apps", "server": {"executable": "backend.exe"}}`,
|
||||
}, []string{"otherplugin", "mattermost-autolink", "playbooks", "focalboard", "com.mattermost.calls", "com.mattermost.nps", "com.mattermost.apps"},
|
||||
true, th.App, th.Context)
|
||||
|
||||
integrations, appErr := th.App.ch.getInstalledIntegrations()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
expected := []*model.InstalledIntegration{
|
||||
{
|
||||
Type: "plugin",
|
||||
ID: "mattermost-autolink",
|
||||
Name: "Autolink",
|
||||
Version: "1.2.0",
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
Type: "plugin",
|
||||
ID: "otherplugin",
|
||||
Name: "Other Plugin",
|
||||
Version: "1.2.0",
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
require.Equal(t, expected, integrations)
|
||||
|
||||
usage, appErr := th.App.GetIntegrationsUsage()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// 2 enabled integrations
|
||||
expectedUsage := &model.IntegrationsUsage{
|
||||
Enabled: 2,
|
||||
}
|
||||
require.Equal(t, expectedUsage, usage)
|
||||
}
|
||||
@@ -1200,28 +1200,6 @@ func (a *OpenTracingAppLayer) CheckForClientSideCert(r *http.Request) (string, s
|
||||
return resultVar0, resultVar1, resultVar2
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CheckFreemiumLimitsForConfigSave(oldConfig *model.Config, newConfig *model.Config) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckFreemiumLimitsForConfigSave")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.CheckFreemiumLimitsForConfigSave(oldConfig, newConfig)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CheckIntegrity() <-chan model.IntegrityCheckResult {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckIntegrity")
|
||||
@@ -6630,28 +6608,6 @@ func (a *OpenTracingAppLayer) GetIncomingWebhooksPageByUser(userID string, page
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetIntegrationsUsage() (*model.IntegrationsUsage, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetIntegrationsUsage")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetIntegrationsUsage()
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetJob(id string) (*model.Job, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetJob")
|
||||
|
||||
@@ -200,10 +200,6 @@ func (ch *Channels) syncPluginsActiveState() {
|
||||
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
||||
mlog.Warn("failed to notify plugin status changed", mlog.Err(err))
|
||||
}
|
||||
|
||||
if err := ch.notifyIntegrationsUsageChanged(); err != nil {
|
||||
mlog.Warn("Failed to notify integrations usage changed", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) NewPluginAPI(c *request.Context, manifest *model.Manifest) plugin.API {
|
||||
@@ -422,11 +418,6 @@ func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
|
||||
// activation if inactive anywhere in the cluster.
|
||||
// Notifies cluster peers through config change.
|
||||
func (a *App) EnablePlugin(id string) *model.AppError {
|
||||
appErr := a.checkIfIntegrationsMeetFreemiumLimits([]string{id})
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
return a.ch.enablePlugin(id)
|
||||
}
|
||||
|
||||
@@ -537,20 +528,6 @@ func (ch *Channels) disablePlugin(id string) *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *Channels) notifyIntegrationsUsageChanged() *model.AppError {
|
||||
usage, appErr := ch.getIntegrationsUsage()
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventIntegrationsUsageChanged, "", "", "", nil, "")
|
||||
message.Add("usage", usage)
|
||||
message.GetBroadcast().ContainsSensitiveData = true
|
||||
ch.Publish(message)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
|
||||
pluginsEnvironment := a.GetPluginsEnvironment()
|
||||
if pluginsEnvironment == nil {
|
||||
|
||||
@@ -102,10 +102,6 @@ func (ch *Channels) installPluginFromData(data model.PluginEventData) {
|
||||
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
||||
mlog.Error("Failed to notify plugin status changed", mlog.Err(err))
|
||||
}
|
||||
|
||||
if err := ch.notifyIntegrationsUsageChanged(); err != nil {
|
||||
mlog.Warn("Failed to notify integrations usage changed", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *Channels) removePluginFromData(data model.PluginEventData) {
|
||||
@@ -118,10 +114,6 @@ func (ch *Channels) removePluginFromData(data model.PluginEventData) {
|
||||
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
||||
mlog.Warn("failed to notify plugin status changed", mlog.Err(err))
|
||||
}
|
||||
|
||||
if err := ch.notifyIntegrationsUsageChanged(); err != nil {
|
||||
mlog.Warn("Failed to notify integrations usage changed", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
// InstallPluginWithSignature verifies and installs plugin.
|
||||
@@ -177,10 +169,6 @@ func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installat
|
||||
mlog.Warn("Failed to notify plugin status changed", mlog.Err(err))
|
||||
}
|
||||
|
||||
if err := ch.notifyIntegrationsUsageChanged(); err != nil {
|
||||
mlog.Warn("Failed to notify integrations usage changed", mlog.Err(err))
|
||||
}
|
||||
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
@@ -455,10 +443,6 @@ func (ch *Channels) RemovePlugin(id string) *model.AppError {
|
||||
mlog.Warn("Failed to notify plugin status changed", mlog.Err(err))
|
||||
}
|
||||
|
||||
if err := ch.notifyIntegrationsUsageChanged(); err != nil {
|
||||
mlog.Warn("Failed to notify integrations usage changed", mlog.Err(err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1057,17 +1057,6 @@ func TestEnablePluginWithCloudLimits(t *testing.T) {
|
||||
appErr = th.App.EnablePlugin("testplugin")
|
||||
checkNoError(t, appErr)
|
||||
|
||||
appErr = th.App.EnablePlugin("testplugin2")
|
||||
checkError(t, appErr)
|
||||
require.Equal(t, "app.install_integration.reached_max_limit.error", appErr.Id)
|
||||
|
||||
th.App.Srv().RemoveLicense()
|
||||
appErr = th.App.EnablePlugin("testplugin2")
|
||||
checkNoError(t, appErr)
|
||||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||
appErr = th.App.EnablePlugin("testplugin2")
|
||||
checkError(t, appErr)
|
||||
|
||||
// Let enable succeed if a CWS error occurs
|
||||
cloud = &mocks.CloudInterface{}
|
||||
th.App.Srv().Cloud = cloud
|
||||
|
||||
31
app/usage.go
31
app/usage.go
@@ -10,37 +10,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
// CheckFreemiumLimitsForConfigSave returns an error if the configuration being saved violates a cloud plan's limits
|
||||
func (a *App) CheckFreemiumLimitsForConfigSave(oldConfig, newConfig *model.Config) *model.AppError {
|
||||
appErr := a.checkIntegrationLimitsForConfigSave(oldConfig, newConfig)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIntegrationsUsage returns usage information on enabled integrations
|
||||
func (a *App) GetIntegrationsUsage() (*model.IntegrationsUsage, *model.AppError) {
|
||||
return a.ch.getIntegrationsUsage()
|
||||
}
|
||||
|
||||
func (ch *Channels) getIntegrationsUsage() (*model.IntegrationsUsage, *model.AppError) {
|
||||
installed, appErr := ch.getInstalledIntegrations()
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
var count = 0
|
||||
for _, i := range installed {
|
||||
if i.Enabled {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return &model.IntegrationsUsage{Enabled: count}, nil
|
||||
}
|
||||
|
||||
// GetPostsUsage returns the total posts count rounded down to the most
|
||||
// significant digit
|
||||
func (a *App) GetPostsUsage() (int64, *model.AppError) {
|
||||
|
||||
Ссылка в новой задаче
Block a user