[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>
Этот коммит содержится в:
Javier Aguirre
2022-11-23 10:42:23 +01:00
коммит произвёл GitHub
родитель 0509e78744
Коммит 7f419ea091
28 изменённых файлов: 1 добавлений и 559 удалений

Просмотреть файл

@@ -157,12 +157,7 @@ func updateConfig(c *Context, w http.ResponseWriter, r *http.Request) {
}
if cfg.PluginSettings.PluginStates[model.PluginIdFocalboard].Enable && cfg.FeatureFlags.BoardsProduct {
c.Err = model.NewAppError("EnablePlugin", "app.plugin.product_mode.app_error", map[string]any{"Name": model.PluginIdFocalboard}, "", http.StatusBadRequest)
return
}
if appErr := c.App.CheckFreemiumLimitsForConfigSave(appCfg, cfg); appErr != nil {
c.Err = appErr
c.Err = model.NewAppError("EnablePlugin", "app.plugin.product_mode.app_error", map[string]any{"Name": model.PluginIdFocalboard}, "", http.StatusInternalServerError)
return
}
@@ -304,11 +299,6 @@ func patchConfig(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
if appErr := c.App.CheckFreemiumLimitsForConfigSave(appCfg, cfg); appErr != nil {
c.Err = appErr
return
}
// There are some settings that cannot be changed in a cloud env
if c.App.Channels().License().IsCloud() {
if cfg.ComplianceSettings.Directory != nil && *appCfg.ComplianceSettings.Directory != *cfg.ComplianceSettings.Directory {

Просмотреть файл

@@ -17,9 +17,7 @@ import (
"github.com/mattermost/mattermost-server/v6/app"
"github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/einterfaces/mocks"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock"
)
func TestGetConfig(t *testing.T) {
@@ -249,59 +247,6 @@ func TestUpdateConfig(t *testing.T) {
assert.Equal(t, newURL, *cfg2.PluginSettings.MarketplaceURL)
})
t.Run("Should not be able to save config if the new config exceeds Freemium limits", func(t *testing.T) {
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
defer th.App.Srv().RemoveLicense()
cloud := &mocks.CloudInterface{}
cloudImpl := th.App.Srv().Cloud
defer func() {
th.App.Srv().Cloud = cloudImpl
}()
th.App.Srv().Cloud = cloud
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(&model.ProductLimits{
Integrations: &model.IntegrationsLimits{
Enabled: model.NewInt(0),
},
}, nil).Once()
// Exceed freemium limit. Should throw error.
cfg1 := th.App.Config().Clone()
cfg1.PluginSettings.PluginStates["new-plugin"] = &model.PluginState{Enable: true}
_, _, err1 := th.SystemAdminClient.UpdateConfig(cfg1)
require.Error(t, err1)
// No attempt to enable a plugin. Should not throw error.
cfg1 = th.App.Config().Clone()
cfg1.PluginSettings.PluginStates["new-plugin"] = &model.PluginState{Enable: false}
_, _, err1 = th.SystemAdminClient.UpdateConfig(cfg1)
require.NoError(t, err1)
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(&model.ProductLimits{
Integrations: &model.IntegrationsLimits{
Enabled: model.NewInt(1),
},
}, nil).Twice()
// Exceed freemium limit while enabling more than one plugin. Should throw error.
cfg1 = th.App.Config().Clone()
cfg1.PluginSettings.PluginStates["new-plugin"] = &model.PluginState{Enable: true}
cfg1.PluginSettings.PluginStates["new-plugin2"] = &model.PluginState{Enable: true}
_, _, err1 = th.SystemAdminClient.PatchConfig(cfg1)
require.Error(t, err1)
// Match freemium limit. Should not throw error.
cfg1 = th.App.Config().Clone()
cfg1.PluginSettings.PluginStates["new-plugin"] = &model.PluginState{Enable: true}
_, _, err1 = th.SystemAdminClient.UpdateConfig(cfg1)
require.NoError(t, err1)
// Save same config with same plugin enabled. Should not throw error.
_, _, err1 = th.SystemAdminClient.UpdateConfig(cfg1)
require.NoError(t, err1)
})
t.Run("Should not be able to modify ComplianceSettings.Directory in cloud", func(t *testing.T) {
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
defer th.App.Srv().RemoveLicense()
@@ -847,59 +792,6 @@ func TestPatchConfig(t *testing.T) {
assert.Equal(t, newURL, *cfg.PluginSettings.MarketplaceURL)
})
t.Run("Should not be able to save config if the new config exceeds Freemium limits", func(t *testing.T) {
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
defer th.App.Srv().RemoveLicense()
cloud := &mocks.CloudInterface{}
cloudImpl := th.App.Srv().Cloud
defer func() {
th.App.Srv().Cloud = cloudImpl
}()
th.App.Srv().Cloud = cloud
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(&model.ProductLimits{
Integrations: &model.IntegrationsLimits{
Enabled: model.NewInt(0),
},
}, nil).Once()
// Exceed freemium limit. Should throw error.
cfg1 := th.App.Config().Clone()
cfg1.PluginSettings.PluginStates["new-plugin"] = &model.PluginState{Enable: true}
_, _, err1 := th.SystemAdminClient.PatchConfig(cfg1)
require.Error(t, err1)
// No attempt to enable a plugin. Should not throw error.
cfg1 = th.App.Config().Clone()
cfg1.PluginSettings.PluginStates["new-plugin"] = &model.PluginState{Enable: false}
_, _, err1 = th.SystemAdminClient.PatchConfig(cfg1)
require.NoError(t, err1)
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(&model.ProductLimits{
Integrations: &model.IntegrationsLimits{
Enabled: model.NewInt(1),
},
}, nil).Twice()
// Exceed freemium limit while enabling more than one plugin. Should throw error.
cfg1 = th.App.Config().Clone()
cfg1.PluginSettings.PluginStates["new-plugin"] = &model.PluginState{Enable: true}
cfg1.PluginSettings.PluginStates["new-plugin2"] = &model.PluginState{Enable: true}
_, _, err1 = th.SystemAdminClient.PatchConfig(cfg1)
require.Error(t, err1)
// Match freemium limit. Should not throw error.
cfg1 = th.App.Config().Clone()
cfg1.PluginSettings.PluginStates["new-plugin"] = &model.PluginState{Enable: true}
_, _, err1 = th.SystemAdminClient.PatchConfig(cfg1)
require.NoError(t, err1)
// Save same config with same plugin enabled. Should not throw error.
_, _, err1 = th.SystemAdminClient.PatchConfig(cfg1)
require.NoError(t, err1)
})
t.Run("System Admin should not be able to clear Site URL", func(t *testing.T) {
cfg, _, err := th.SystemAdminClient.GetConfig()
require.NoError(t, err)

Просмотреть файл

@@ -18,8 +18,6 @@ func (api *API) InitUsage() {
api.BaseRoutes.Usage.Handle("/storage", api.APISessionRequired(getStorageUsage)).Methods("GET")
// GET /api/v4/usage/teams
api.BaseRoutes.Usage.Handle("/teams", api.APISessionRequired(getTeamsUsage)).Methods("GET")
// GET /api/v4/usage/integrations
api.BaseRoutes.Usage.Handle("/integrations", api.APISessionRequired(getIntegrationsUsage)).Methods("GET")
}
func getPostsUsage(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -74,30 +72,3 @@ func getTeamsUsage(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write(json)
}
func getIntegrationsUsage(c *Context, w http.ResponseWriter, r *http.Request) {
if !*c.App.Config().PluginSettings.Enable {
json, err := json.Marshal(&model.IntegrationsUsage{})
if err != nil {
c.Err = model.NewAppError("Api4.getIntegrationsUsage", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
return
}
w.Write(json)
return
}
usage, appErr := c.App.GetIntegrationsUsage()
if appErr != nil {
c.Err = appErr
return
}
json, err := json.Marshal(usage)
if err != nil {
c.Err = model.NewAppError("Api4.getIntegrationsUsage", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
return
}
w.Write(json)
}

Просмотреть файл

@@ -91,28 +91,3 @@ func TestGetTeamsUsage(t *testing.T) {
assert.Equal(t, int64(3), usage.Active)
})
}
func TestGetIntegrationsUsage(t *testing.T) {
t.Run("unauthenticated users can not access", func(t *testing.T) {
th := Setup(t)
defer th.TearDown()
th.Client.Logout()
usage, r, err := th.Client.GetIntegrationsUsage()
assert.Error(t, err)
assert.Nil(t, usage)
assert.Equal(t, http.StatusUnauthorized, r.StatusCode)
})
t.Run("good request returns response", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
usage, r, err := th.Client.GetIntegrationsUsage()
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, r.StatusCode)
assert.NotNil(t, usage)
assert.Equal(t, 0, usage.Enabled)
})
}

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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) {

Просмотреть файл

@@ -9202,10 +9202,6 @@
"id": "app.recent_searches.app_error",
"translation": "Fehler beim Holen der letzten Suchen"
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "Du hast das Maximum von {{.NumIntegrations}} aktivierten Integrationen erreicht. Um Integrationen ohne Beschränkungen zu installieren, upgrade auf ein bezahltes Abonnements."
},
{
"id": "app.teams.analytics_teams_count.app_error",
"translation": "Kann Team-Zähler nicht abfragen"

Просмотреть файл

@@ -5619,10 +5619,6 @@
"id": "app.insights.feature_disabled",
"translation": "Insights feature is disabled."
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "You've reached the max limit of {{.NumIntegrations}} enabled integrations. To install unlimited integrations, upgrade to one of our paid plans."
},
{
"id": "app.job.download_export_results_not_enabled",
"translation": "DownloadExportResults in config.json is false. Please set this to true to download the results of this job."

Просмотреть файл

@@ -9198,10 +9198,6 @@
"id": "api.file.cloud_upload.app_error",
"translation": "Uploading via mmctl to a Cloud instance is not supported. Please check the documentation here: https://docs.mattermost.com/manage/cloud-data-export.html."
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "You've reached the limit of {{.NumIntegrations}} enabled integrations. To install unlimited integrations, upgrade to one of the paid plans."
},
{
"id": "app.usage.get_storage_usage.app_error",
"translation": "Failed to get storage usage."

Просмотреть файл

@@ -9151,10 +9151,6 @@
"id": "api.templates.invite_team_and_channels_body.title",
"translation": " "
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "Has alcanzado el límite máximo de {{.NumIntegrations}} integraciones activas. Para instalar integraciones ilimitadas, actualiza a uno de unos planes de pago."
},
{
"id": "model.channel.is_valid.1_or_more.app_error",
"translation": "El Nombre debe tener 1 o más caracteres alfanuméricos en minúsculas."

Просмотреть файл

@@ -8747,10 +8747,6 @@
"id": "app.job.get_all_jobs_by_type_and_status.app_error",
"translation": "Impossible d'obtenir tous les travaux par type et statuts."
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "Vous avez atteint la limite maximale de {{.NumIntegrations}} d'intégrations activées. Pour installer un nombre illimité d'intégrations, effectuez une mise à niveau vers l'un de nos plans payants."
},
{
"id": "app.insights.feature_disabled",
"translation": "La fonctionnalité des aperçus est désactivée."

Просмотреть файл

@@ -9203,10 +9203,6 @@
"id": "app.teams.analytics_teams_count.app_error",
"translation": "Nem kérdezhető le a csapatok száma"
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "Elérte az engedélyezett integrációk maximális számát {{.NumIntegrations}}. Korlátlan számú integráció telepítéséhez frissítsen valamelyik fizetős csomagunkra."
},
{
"id": "app.post.analytics_teams_count.app_error",
"translation": "Nem kérdezhető le a csapat használtság"

Просмотреть файл

@@ -8383,10 +8383,6 @@
"id": "app.notification.body.group.title",
"translation": " "
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": " "
},
{
"id": "app.user.missing_account.const",
"translation": " "

Просмотреть файл

@@ -9195,10 +9195,6 @@
"id": "api.cloud.subscription.update_error",
"translation": "ウェブフックからサブスクリプションを更新する際にエラーが発生しました。"
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "有効な統合機能数の上限 {{.NumIntegrations}} に達しました。無制限に統合機能をインストールするには、いずれかの有料プランにアップグレードしてください。"
},
{
"id": "model.config.is_valid.image_decoder_concurrency.app_error",
"translation": "デコーダーの並列数 {{.Value}} は不正です。正の数または-1であるべきです。"

Просмотреть файл

@@ -9218,10 +9218,6 @@
"id": "app.teams.analytics_teams_count.app_error",
"translation": "Niet gelukt om het aan aantal teams op te halen"
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "Je bereikte de maximumlimiet van {{.NumIntegrations}} ingeschakelde integraties. Om onbeperkte integraties te installeren, upgrade naar een van onze betaalde plannen."
},
{
"id": "api.cloud.teams_limit_reached.restore",
"translation": "Kan het team niet herstellen omdat de teamlimiet bereikt is"

Просмотреть файл

@@ -9203,10 +9203,6 @@
"id": "app.recent_searches.app_error",
"translation": "Błąd pobierania ostatnich wyszukiwań"
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "Osiągnąłeś maksymalny limit {{.NumIntegrations}} włączonych integracji. Aby zainstalować nieograniczoną liczbę integracji, uaktualnij do jednego z naszych płatnych planów."
},
{
"id": "app.teams.analytics_teams_count.app_error",
"translation": "Nie można uzyskać liczby zespołów"

Просмотреть файл

@@ -9202,10 +9202,6 @@
"id": "api.file.cloud_upload.app_error",
"translation": "Uppladdning via mmctl till en molninstans stöds inte. Se dokumentationen här: https://docs.mattermost.com/manage/cloud-data-export.html."
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "Du har nått maxgränsen {{.NumIntegrations}} aktiva integrationer. Om du vill installera obegränsat antal integrationer kan du uppgradera till en av våra betal-abonnemang."
},
{
"id": "app.usage.get_storage_usage.app_error",
"translation": "Det gick inte att få fram lagringsvolym."

Просмотреть файл

@@ -9202,10 +9202,6 @@
"id": "api.file.cloud_upload.app_error",
"translation": "Bir Bulut kopyasına mmctl ile yükleme desteklenmiyor. Lütfen şu makaleye bakın: https://docs.mattermost.com/manage/cloud-data-export.html."
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "Kullanabileceğiniz en fazla {{.NumIntegrations}} bütünleştirme sınırına ulaştınız. Sınırsız bütünleştirme için ücretli tarifelerimizden birine geçin."
},
{
"id": "app.teams.analytics_teams_count.app_error",
"translation": "Takım sayısı alınamadı"

Просмотреть файл

@@ -9231,10 +9231,6 @@
"id": "app.post_reminder_dm",
"translation": "您好,这是您关于此消息的提醒 @{{.Username}}: {{.SiteURL}}/{{.TeamName}}/pl/{{.PostId}}"
},
{
"id": "app.install_integration.reached_max_limit.error",
"translation": "您已达到启用 {{.NumIntegrations}} 集成的最大限制。 要安装无限集成,请升级到我们的付费计划之一。"
},
{
"id": "app.usage.get_storage_usage.app_error",
"translation": "无法获取存储使用情况。"

Просмотреть файл

@@ -8434,19 +8434,6 @@ func (c *Client4) GetTeamsUsage() (*TeamsUsage, *Response, error) {
return usage, BuildResponse(r), err
}
// GetIntegrationsUsage returns usage information on integrations, including the count of enabled integrations
func (c *Client4) GetIntegrationsUsage() (*IntegrationsUsage, *Response, error) {
r, err := c.DoAPIGet(c.usageRoute()+"/integrations", "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var usage *IntegrationsUsage
err = json.NewDecoder(r.Body).Decode(&usage)
return usage, BuildResponse(r), err
}
func (c *Client4) GetNewTeamMembersSince(teamID string, timeRange string, page int, perPage int) (*NewTeamMembersList, *Response, error) {
query := fmt.Sprintf("?time_range=%v&page=%v&per_page=%v", timeRange, page, perPage)
r, err := c.DoAPIGet(c.teamRoute(teamID)+"/top/team_members"+query, "")

Просмотреть файл

@@ -16,10 +16,6 @@ type TeamsUsage struct {
CloudArchived int64 `json:"cloud_archived"`
}
type IntegrationsUsage struct {
Enabled int `json:"enabled"`
}
var InstalledIntegrationsIgnoredPlugins = map[string]struct{}{
PluginIdPlaybooks: {},
PluginIdFocalboard: {},

Просмотреть файл

@@ -76,7 +76,6 @@ const (
WebsocketEventThreadFollowChanged = "thread_follow_changed"
WebsocketEventThreadReadChanged = "thread_read_changed"
WebsocketFirstAdminVisitMarketplaceStatusReceived = "first_admin_visit_marketplace_status_received"
WebsocketEventIntegrationsUsageChanged = "integrations_usage_changed"
)
type WebSocketMessage interface {