Mm 43084 add cloud workspace limits endpoint (#20041)
* add cloud workspace limits endpoint
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
f460401cf2
Коммит
d4db43c95e
@@ -19,6 +19,8 @@ import (
|
|||||||
func (api *API) InitCloud() {
|
func (api *API) InitCloud() {
|
||||||
// GET /api/v4/cloud/products
|
// GET /api/v4/cloud/products
|
||||||
api.BaseRoutes.Cloud.Handle("/products", api.APISessionRequired(getCloudProducts)).Methods("GET")
|
api.BaseRoutes.Cloud.Handle("/products", api.APISessionRequired(getCloudProducts)).Methods("GET")
|
||||||
|
// GET /api/v4/cloud/limits
|
||||||
|
api.BaseRoutes.Cloud.Handle("/limits", api.APISessionRequired(getCloudLimits)).Methods("GET")
|
||||||
|
|
||||||
// POST /api/v4/cloud/payment
|
// POST /api/v4/cloud/payment
|
||||||
// POST /api/v4/cloud/payment/confirm
|
// POST /api/v4/cloud/payment/confirm
|
||||||
@@ -141,6 +143,38 @@ func getCloudProducts(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(json)
|
w.Write(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCloudLimits(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.Cloud {
|
||||||
|
c.Err = model.NewAppError("Api4.getCloudLimits", "api.cloud.license_error", nil, "", http.StatusNotImplemented)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.App.Config().FeatureFlags.CloudFree {
|
||||||
|
emptyLimits := &model.ProductLimits{}
|
||||||
|
json, err := json.Marshal(emptyLimits)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = model.NewAppError("Api4.getCloudLimits", "api.cloud.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(json)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
limits, err := c.App.Cloud().GetCloudLimits(c.AppContext.Session().UserId)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = model.NewAppError("Api4.getCloudLimits", "api.cloud.request_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(limits)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = model.NewAppError("Api4.getCloudLimits", "api.cloud.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(json)
|
||||||
|
}
|
||||||
|
|
||||||
func getCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.Cloud {
|
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.Cloud {
|
||||||
c.Err = model.NewAppError("Api4.getCloudCustomer", "api.cloud.license_error", nil, "", http.StatusNotImplemented)
|
c.Err = model.NewAppError("Api4.getCloudCustomer", "api.cloud.license_error", nil, "", http.StatusNotImplemented)
|
||||||
|
|||||||
143
api4/cloud_test.go
Обычный файл
143
api4/cloud_test.go
Обычный файл
@@ -0,0 +1,143 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package api4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"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 Test_getCloudLimits(t *testing.T) {
|
||||||
|
t.Run("feature flag off returns empty limits", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_CLOUDFREE", "false")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDFREE")
|
||||||
|
th.App.ReloadConfig()
|
||||||
|
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||||
|
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||||
|
|
||||||
|
limits, r, err := th.Client.GetProductLimits()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, limits, &model.ProductLimits{})
|
||||||
|
require.Equal(t, http.StatusOK, r.StatusCode, "Expected 200 OK")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no license returns not implemented", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_CLOUDFREE", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDFREE")
|
||||||
|
th.App.ReloadConfig()
|
||||||
|
|
||||||
|
th.App.Srv().RemoveLicense()
|
||||||
|
|
||||||
|
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||||
|
|
||||||
|
limits, r, err := th.Client.GetProductLimits()
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, limits)
|
||||||
|
require.Equal(t, http.StatusNotImplemented, r.StatusCode, "Expected 501 Not Implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non cloud license returns not implemented", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_CLOUDFREE", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDFREE")
|
||||||
|
th.App.ReloadConfig()
|
||||||
|
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicense())
|
||||||
|
|
||||||
|
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||||
|
|
||||||
|
limits, r, err := th.Client.GetProductLimits()
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, limits)
|
||||||
|
require.Equal(t, http.StatusNotImplemented, r.StatusCode, "Expected 501 Not Implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error fetching limits returns internal server error", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_CLOUDFREE", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDFREE")
|
||||||
|
th.App.ReloadConfig()
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||||
|
|
||||||
|
cloud := &mocks.CloudInterface{}
|
||||||
|
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, errors.New("Unable to get limits"))
|
||||||
|
|
||||||
|
cloudImpl := th.App.Srv().Cloud
|
||||||
|
defer func() {
|
||||||
|
th.App.Srv().Cloud = cloudImpl
|
||||||
|
}()
|
||||||
|
th.App.Srv().Cloud = cloud
|
||||||
|
|
||||||
|
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||||
|
|
||||||
|
limits, r, err := th.Client.GetProductLimits()
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, limits)
|
||||||
|
require.Equal(t, http.StatusInternalServerError, r.StatusCode, "Expected 500 Internal Server Error")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unauthenticated users can not access", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
th.Client.Logout()
|
||||||
|
|
||||||
|
limits, r, err := th.Client.GetProductLimits()
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, limits)
|
||||||
|
require.Equal(t, http.StatusUnauthorized, r.StatusCode, "Expected 401 Unauthorized")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good request with cloud server and feature flag returns response", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
os.Setenv("MM_FEATUREFLAGS_CLOUDFREE", "true")
|
||||||
|
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDFREE")
|
||||||
|
th.App.ReloadConfig()
|
||||||
|
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||||||
|
|
||||||
|
cloud := &mocks.CloudInterface{}
|
||||||
|
ten := 10
|
||||||
|
mockLimits := &model.ProductLimits{
|
||||||
|
Messages: &model.MessagesLimits{
|
||||||
|
History: &ten,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(mockLimits, nil)
|
||||||
|
|
||||||
|
cloudImpl := th.App.Srv().Cloud
|
||||||
|
defer func() {
|
||||||
|
th.App.Srv().Cloud = cloudImpl
|
||||||
|
}()
|
||||||
|
th.App.Srv().Cloud = cloud
|
||||||
|
|
||||||
|
th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||||
|
|
||||||
|
limits, r, err := th.Client.GetProductLimits()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, r.StatusCode, "Expected 200 OK")
|
||||||
|
require.Equal(t, mockLimits, limits)
|
||||||
|
require.Equal(t, *mockLimits.Messages.History, *limits.Messages.History)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
type CloudInterface interface {
|
type CloudInterface interface {
|
||||||
GetCloudProducts(userID string, includeLegacyProducts bool) ([]*model.Product, error)
|
GetCloudProducts(userID string, includeLegacyProducts bool) ([]*model.Product, error)
|
||||||
|
GetCloudLimits(userID string) (*model.ProductLimits, error)
|
||||||
|
|
||||||
CreateCustomerPayment(userID string) (*model.StripeSetupIntent, error)
|
CreateCustomerPayment(userID string) (*model.StripeSetupIntent, error)
|
||||||
ConfirmCustomerPayment(userID string, confirmRequest *model.ConfirmPaymentMethodRequest) error
|
ConfirmCustomerPayment(userID string, confirmRequest *model.ConfirmPaymentMethodRequest) error
|
||||||
|
|||||||
@@ -97,6 +97,29 @@ func (_m *CloudInterface) GetCloudCustomer(userID string) (*model.CloudCustomer,
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCloudLimits provides a mock function with given fields: userID
|
||||||
|
func (_m *CloudInterface) GetCloudLimits(userID string) (*model.ProductLimits, error) {
|
||||||
|
ret := _m.Called(userID)
|
||||||
|
|
||||||
|
var r0 *model.ProductLimits
|
||||||
|
if rf, ok := ret.Get(0).(func(string) *model.ProductLimits); ok {
|
||||||
|
r0 = rf(userID)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.ProductLimits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||||
|
r1 = rf(userID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// GetCloudProducts provides a mock function with given fields: userID, includeLegacyProducts
|
// GetCloudProducts provides a mock function with given fields: userID, includeLegacyProducts
|
||||||
func (_m *CloudInterface) GetCloudProducts(userID string, includeLegacyProducts bool) ([]*model.Product, error) {
|
func (_m *CloudInterface) GetCloudProducts(userID string, includeLegacyProducts bool) ([]*model.Product, error) {
|
||||||
ret := _m.Called(userID, includeLegacyProducts)
|
ret := _m.Called(userID, includeLegacyProducts)
|
||||||
|
|||||||
@@ -7707,6 +7707,19 @@ func (c *Client4) GetCloudProducts() ([]*Product, *Response, error) {
|
|||||||
return cloudProducts, BuildResponse(r), nil
|
return cloudProducts, BuildResponse(r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client4) GetProductLimits() (*ProductLimits, *Response, error) {
|
||||||
|
r, err := c.DoAPIGet(c.cloudRoute()+"/limits", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildResponse(r), err
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
var productLimits *ProductLimits
|
||||||
|
json.NewDecoder(r.Body).Decode(&productLimits)
|
||||||
|
|
||||||
|
return productLimits, BuildResponse(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client4) CreateCustomerPayment() (*StripeSetupIntent, *Response, error) {
|
func (c *Client4) CreateCustomerPayment() (*StripeSetupIntent, *Response, error) {
|
||||||
r, err := c.DoAPIPost(c.cloudRoute()+"/payment", "")
|
r, err := c.DoAPIPost(c.cloudRoute()+"/payment", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -181,3 +181,32 @@ type CloudWorkspaceOwner struct {
|
|||||||
type SubscriptionChange struct {
|
type SubscriptionChange struct {
|
||||||
ProductID string `json:"product_id"`
|
ProductID string `json:"product_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BoardsLimits struct {
|
||||||
|
Cards *int `json:"cards"`
|
||||||
|
Views *int `json:"views"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilesLimits struct {
|
||||||
|
TotalStorage *int64 `json:"total_storage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntegrationsLimits struct {
|
||||||
|
Enabled *int `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessagesLimits struct {
|
||||||
|
History *int `json:"history"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TeamsLimits struct {
|
||||||
|
Active *int `json:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductLimits struct {
|
||||||
|
Boards *BoardsLimits `json:"boards,omitempty"`
|
||||||
|
Files *FilesLimits `json:"files,omitempty"`
|
||||||
|
Integrations *IntegrationsLimits `json:"integrations,omitempty"`
|
||||||
|
Messages *MessagesLimits `json:"messages,omitempty"`
|
||||||
|
Teams *TeamsLimits `json:"teams,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ type FeatureFlags struct {
|
|||||||
|
|
||||||
InsightsEnabled bool
|
InsightsEnabled bool
|
||||||
|
|
||||||
|
CloudFree bool
|
||||||
|
|
||||||
CommandPalette bool
|
CommandPalette bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +90,7 @@ func (f *FeatureFlags) SetDefaults() {
|
|||||||
f.UseCaseOnboarding = true
|
f.UseCaseOnboarding = true
|
||||||
f.GraphQL = false
|
f.GraphQL = false
|
||||||
f.InsightsEnabled = false
|
f.InsightsEnabled = false
|
||||||
|
f.CloudFree = false
|
||||||
f.CommandPalette = false
|
f.CommandPalette = false
|
||||||
}
|
}
|
||||||
func (f *FeatureFlags) Plugins() map[string]string {
|
func (f *FeatureFlags) Plugins() map[string]string {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user