diff --git a/e2e-tests/cypress/tests/integration/channels/enterprise/cloud/billing/cloud_pricing_modal_spec.ts b/e2e-tests/cypress/tests/integration/channels/enterprise/cloud/billing/cloud_pricing_modal_spec.ts
index 70b026d72d..0134ee1b68 100644
--- a/e2e-tests/cypress/tests/integration/channels/enterprise/cloud/billing/cloud_pricing_modal_spec.ts
+++ b/e2e-tests/cypress/tests/integration/channels/enterprise/cloud/billing/cloud_pricing_modal_spec.ts
@@ -334,7 +334,7 @@ describe('Pricing modal', () => {
// * Check that enterprise card action button shows Try free for 30 days
cy.get('#pricingModal').should('be.visible');
- cy.get('#start_cloud_trial_btn').contains('Try free for 30 days');
+ cy.get('#start_cloud_trial_btn').contains('Start trial');
});
it('should open pricing modal when Upgrade button clicked while in enterprise trial sku', () => {
@@ -365,7 +365,7 @@ describe('Pricing modal', () => {
// * Check that enterprise card action button is disabled
cy.get('#enterprise > .bottom > .bottom_container').should('be.visible');
- cy.get('#start_cloud_trial_btn').contains('Try free for 30 days');
+ cy.get('#start_cloud_trial_btn').contains('Start trial');
cy.get('#enterprise > .bottom > .bottom_container').should('be.visible');
cy.get('#start_cloud_trial_btn').should('be.disabled');
});
diff --git a/e2e-tests/cypress/tests/integration/channels/enterprise/self-hosted/self_hosted_pricing_modal_spec.ts b/e2e-tests/cypress/tests/integration/channels/enterprise/self-hosted/self_hosted_pricing_modal_spec.ts
index 37196627a9..16ab7c3d6e 100644
--- a/e2e-tests/cypress/tests/integration/channels/enterprise/self-hosted/self_hosted_pricing_modal_spec.ts
+++ b/e2e-tests/cypress/tests/integration/channels/enterprise/self-hosted/self_hosted_pricing_modal_spec.ts
@@ -102,7 +102,7 @@ describe('Self hosted Pricing modal', () => {
// * Check that enteprise trial button is available
cy.get('#pricingModal').should('be.visible');
cy.get('#enterprise').should('be.visible');
- cy.get('#start_trial_btn').should('not.be.disabled').contains('Try free for 30 days');
+ cy.get('#start_trial_btn').should('not.be.disabled').contains('Start trial');
});
it('Upgrade button should open pricing modal admin users when the server has requested a trial before on free plan', () => {
diff --git a/e2e-tests/cypress/tests/integration/channels/system_console/feature_discovery_cloud_spec.js b/e2e-tests/cypress/tests/integration/channels/system_console/feature_discovery_cloud_spec.js
index eba774b224..72aae691a0 100644
--- a/e2e-tests/cypress/tests/integration/channels/system_console/feature_discovery_cloud_spec.js
+++ b/e2e-tests/cypress/tests/integration/channels/system_console/feature_discovery_cloud_spec.js
@@ -53,7 +53,7 @@ describe('Feature discovery cloud', () => {
const testForTrialButton = () => {
cy.get('#start_cloud_trial_btn').should('exist');
- cy.get('#start_cloud_trial_btn').contains('Try free for 30 days');
+ cy.get('#start_cloud_trial_btn').contains('Start trial');
};
const testForUpgradeToProfessionalOption = () => {
diff --git a/model/client4.go b/model/client4.go
index 853cfdd080..d6cc62ba0f 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -7812,7 +7812,23 @@ func (c *Client4) GetChannelMemberCountsByGroup(channelID string, includeTimezon
return ch, BuildResponse(r), nil
}
+func (c *Client4) RequestTrialLicenseWithExtraFields(trialRequest *TrialLicenseRequest) (*Response, error) {
+ b, err := json.Marshal(trialRequest)
+ if err != nil {
+ return nil, NewAppError("RequestTrialLicense", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
+ }
+
+ r, err := c.DoAPIPost("/trial-license", string(b))
+ if err != nil {
+ return BuildResponse(r), err
+ }
+
+ defer closeBody(r)
+ return BuildResponse(r), nil
+}
+
// RequestTrialLicense will request a trial license and install it in the server
+// DEPRECATED - USE RequestTrialLicenseWithExtraFields (this method remains for backwards compatibility)
func (c *Client4) RequestTrialLicense(users int) (*Response, error) {
b, err := json.Marshal(map[string]any{"users": users, "terms_accepted": true})
if err != nil {
diff --git a/model/license.go b/model/license.go
index cf5421bf02..7fa211e4e8 100644
--- a/model/license.go
+++ b/model/license.go
@@ -84,6 +84,48 @@ type TrialLicenseRequest struct {
Users int `json:"users"`
TermsAccepted bool `json:"terms_accepted"`
ReceiveEmailsAccepted bool `json:"receive_emails_accepted"`
+ ContactName string `json:"contact_name"`
+ ContactEmail string `json:"contact_email"`
+ CompanyName string `json:"company_name"`
+ CompanyCountry string `json:"company_country"`
+ CompanySize string `json:"company_size"`
+}
+
+// If any of the below fields are set, this is not a legacy request, and all fields should be validated
+func (tlr *TrialLicenseRequest) IsLegacy() bool {
+ return tlr.CompanyCountry == "" && tlr.CompanyName == "" && tlr.CompanySize == "" && tlr.ContactName == ""
+}
+
+func (tlr *TrialLicenseRequest) IsValid() bool {
+ if !tlr.TermsAccepted {
+ return false
+ }
+
+ if tlr.Email == "" {
+ return false
+ }
+
+ if tlr.Users <= 0 {
+ return false
+ }
+
+ if tlr.CompanyCountry == "" {
+ return false
+ }
+
+ if tlr.CompanyName == "" {
+ return false
+ }
+
+ if tlr.CompanySize == "" {
+ return false
+ }
+
+ if tlr.ContactName == "" {
+ return false
+ }
+
+ return true
}
type Features struct {
diff --git a/model/license_test.go b/model/license_test.go
index 1d1a5f1acf..5b413e00a3 100644
--- a/model/license_test.go
+++ b/model/license_test.go
@@ -200,6 +200,100 @@ func TestLicenseRecordPreSave(t *testing.T) {
assert.NotZero(t, lr.CreateAt)
}
+func TestIsLegacyTrialRequest(t *testing.T) {
+ legacyTr := &TrialLicenseRequest{
+ Email: "test@mattermost.com",
+ TermsAccepted: true,
+ SiteURL: "https://mattermost.com",
+ SiteName: "Mattermost",
+ Users: 100,
+ }
+ t.Run("legacy trial request", func(t *testing.T) {
+ assert.True(t, legacyTr.IsLegacy())
+ })
+
+ t.Run("legacy trial request with any non-legacy field set is not a legacy request", func(t *testing.T) {
+ legacyTr.CompanyCountry = "US"
+ assert.False(t, legacyTr.IsLegacy())
+ legacyTr.CompanyCountry = ""
+ legacyTr.CompanyName = "test company"
+ assert.False(t, legacyTr.IsLegacy())
+ legacyTr.CompanyName = ""
+ legacyTr.CompanySize = "50-100"
+ assert.False(t, legacyTr.IsLegacy())
+ legacyTr.CompanySize = ""
+ legacyTr.ContactName = "test user"
+ assert.False(t, legacyTr.IsLegacy())
+ legacyTr.ContactName = ""
+ assert.True(t, legacyTr.IsLegacy())
+ })
+
+}
+
+func TestTrialLicenseRequestIsValid(t *testing.T) {
+ validTlr := &TrialLicenseRequest{
+ Email: "test@test.com",
+ Users: 100,
+ CompanyCountry: "US",
+ CompanyName: "Test Company",
+ CompanySize: "50-100",
+ ContactName: "Test User",
+ TermsAccepted: true,
+ }
+
+ resetBaseRequest := func() {
+ validTlr = &TrialLicenseRequest{
+ Email: "test@test.com",
+ Users: 100,
+ CompanyCountry: "US",
+ CompanyName: "Test Company",
+ CompanySize: "50-100",
+ ContactName: "Test User",
+ TermsAccepted: true,
+ }
+ }
+ t.Run("valid request", func(t *testing.T) {
+ resetBaseRequest()
+ assert.Equal(t, true, validTlr.IsValid())
+ })
+
+ t.Run("no terms", func(t *testing.T) {
+ resetBaseRequest()
+ validTlr.TermsAccepted = false
+ assert.Equal(t, false, validTlr.IsValid())
+ })
+
+ t.Run("no email", func(t *testing.T) {
+ resetBaseRequest()
+ validTlr.Email = ""
+ assert.Equal(t, false, validTlr.IsValid())
+ })
+
+ t.Run("no CompanyCountry", func(t *testing.T) {
+ resetBaseRequest()
+ validTlr.CompanyCountry = ""
+ assert.Equal(t, false, validTlr.IsValid())
+ })
+
+ t.Run("no CompanyName", func(t *testing.T) {
+ resetBaseRequest()
+ validTlr.CompanyName = ""
+ assert.Equal(t, false, validTlr.IsValid())
+ })
+
+ t.Run("no CompanySize", func(t *testing.T) {
+ resetBaseRequest()
+ validTlr.CompanySize = ""
+ assert.Equal(t, false, validTlr.IsValid())
+ })
+
+ t.Run("Bad User Count", func(t *testing.T) {
+ resetBaseRequest()
+ validTlr.Users = 0
+ assert.Equal(t, false, validTlr.IsValid())
+ })
+}
+
func TestLicense_IsTrialLicense(t *testing.T) {
t.Run("detect trial license directly from the flag", func(t *testing.T) {
license := &License{
diff --git a/server/channels/api4/cloud.go b/server/channels/api4/cloud.go
index 3769403950..48a8ddf83f 100644
--- a/server/channels/api4/cloud.go
+++ b/server/channels/api4/cloud.go
@@ -246,16 +246,6 @@ func validateBusinessEmail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !c.App.Channels().License().IsCloud() {
- c.Err = model.NewAppError("Api4.validateBusinessEmail", "api.cloud.license_error", nil, "", http.StatusForbidden)
- return
- }
-
- if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteBilling) {
- c.SetPermissionError(model.PermissionSysconsoleWriteBilling)
- return
- }
-
user, appErr := c.App.GetUser(c.AppContext.Session().UserId)
if appErr != nil {
c.Err = model.NewAppError("Api4.validateBusinessEmail", "api.cloud.request_error", nil, "", http.StatusForbidden).Wrap(appErr)
diff --git a/server/channels/api4/cloud_test.go b/server/channels/api4/cloud_test.go
index 1114ef4ba4..6b2067ec15 100644
--- a/server/channels/api4/cloud_test.go
+++ b/server/channels/api4/cloud_test.go
@@ -328,30 +328,6 @@ func Test_requestTrial(t *testing.T) {
}
func Test_validateBusinessEmail(t *testing.T) {
- t.Run("Returns forbidden for non admin executors", func(t *testing.T) {
- th := Setup(t).InitBasic()
- defer th.TearDown()
-
- th.Client.Login(th.BasicUser.Email, th.BasicUser.Password)
-
- invalidEmail := model.ValidateBusinessEmailRequest{Email: "invalid@gmail.com"}
-
- th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
-
- cloud := mocks.CloudInterface{}
-
- cloud.Mock.On("ValidateBusinessEmail", th.SystemAdminUser.Id, invalidEmail.Email).Return(errors.New("invalid email"))
-
- cloudImpl := th.App.Srv().Cloud
- defer func() {
- th.App.Srv().Cloud = cloudImpl
- }()
- th.App.Srv().Cloud = &cloud
-
- res, err := th.Client.ValidateBusinessEmail(&invalidEmail)
- require.Error(t, err)
- require.Equal(t, http.StatusForbidden, res.StatusCode, "403")
- })
t.Run("Returns forbidden for invalid business email", func(t *testing.T) {
th := Setup(t).InitBasic()
diff --git a/server/channels/api4/license.go b/server/channels/api4/license.go
index 456f5b9fcb..9911c241e1 100644
--- a/server/channels/api4/license.go
+++ b/server/channels/api4/license.go
@@ -210,11 +210,7 @@ func requestTrialLicense(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- var trialRequest struct {
- Users int `json:"users"`
- TermsAccepted bool `json:"terms_accepted"`
- ReceiveEmailsAccepted bool `json:"receive_emails_accepted"`
- }
+ var trialRequest *model.TrialLicenseRequest
b, readErr := io.ReadAll(r.Body)
if readErr != nil {
@@ -223,8 +219,16 @@ func requestTrialLicense(c *Context, w http.ResponseWriter, r *http.Request) {
}
json.Unmarshal(b, &trialRequest)
- if err := c.App.Channels().RequestTrialLicense(c.AppContext.Session().UserId, trialRequest.Users, trialRequest.TermsAccepted, trialRequest.ReceiveEmailsAccepted); err != nil {
- c.Err = err
+ var appErr *model.AppError
+ // If any of the newly supported trial request fields are set (ie, not a legacy request), process this as a new trial request (requiring the new fields) otherwise fall back on the old method.
+ if !trialRequest.IsLegacy() {
+ appErr = c.App.Channels().RequestTrialLicenseWithExtraFields(c.AppContext.Session().UserId, trialRequest)
+ } else {
+ appErr = c.App.Channels().RequestTrialLicense(c.AppContext.Session().UserId, trialRequest.Users, trialRequest.TermsAccepted, trialRequest.ReceiveEmailsAccepted)
+ }
+
+ if appErr != nil {
+ c.Err = appErr
return
}
diff --git a/server/channels/api4/license_test.go b/server/channels/api4/license_test.go
index 33f54eeecc..7762e3fc0f 100644
--- a/server/channels/api4/license_test.go
+++ b/server/channels/api4/license_test.go
@@ -223,6 +223,154 @@ func TestRemoveLicenseFile(t *testing.T) {
})
}
+func TestRequestTrialLicenseWithExtraFields(t *testing.T) {
+ th := Setup(t)
+ defer th.TearDown()
+
+ licenseManagerMock := &mocks.LicenseInterface{}
+ licenseManagerMock.On("CanStartTrial").Return(true, nil)
+ th.App.Srv().Platform().SetLicenseManager(licenseManagerMock)
+ cloud := mocks.CloudInterface{}
+
+ cloudImpl := th.App.Srv().Cloud
+ defer func() {
+ th.App.Srv().Cloud = cloudImpl
+ }()
+ th.App.Srv().Cloud = &cloud
+
+ th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "http://localhost:8065/" })
+ nUsers := 1
+ validTrialRequest := &model.TrialLicenseRequest{
+ Email: "test@mattermost.com",
+ Users: nUsers,
+ TermsAccepted: true,
+ CompanyCountry: "US",
+ CompanyName: "mattermost",
+ CompanySize: "1-10",
+ ContactName: "Matter Most",
+ }
+
+ t.Run("permission denied", func(t *testing.T) {
+ resp, err := th.Client.RequestTrialLicenseWithExtraFields(&model.TrialLicenseRequest{})
+ require.Error(t, err)
+ CheckForbiddenStatus(t, resp)
+ })
+
+ t.Run("trial license user count less than current users", func(t *testing.T) {
+ license := model.NewTestLicense()
+ license.Features.Users = model.NewInt(nUsers)
+ licenseJSON, jsonErr := json.Marshal(license)
+ require.NoError(t, jsonErr)
+ testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+ res.WriteHeader(http.StatusOK)
+ response := map[string]string{
+ "license": string(licenseJSON),
+ }
+ err := json.NewEncoder(res).Encode(response)
+ require.NoError(t, err)
+ }))
+ defer testServer.Close()
+
+ mockLicenseValidator := mocks2.LicenseValidatorIface{}
+ defer testutils.ResetLicenseValidator()
+
+ mockLicenseValidator.On("ValidateLicense", mock.Anything).Return(true, string(licenseJSON))
+ utils.LicenseValidator = &mockLicenseValidator
+ licenseManagerMock := &mocks.LicenseInterface{}
+ licenseManagerMock.On("CanStartTrial").Return(true, nil).Once()
+ th.App.Srv().Platform().SetLicenseManager(licenseManagerMock)
+ originalCwsUrl := *th.App.Srv().Config().CloudSettings.CWSURL
+ th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = testServer.URL })
+ defer func(requestTrialURL string) {
+ th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = requestTrialURL })
+ }(originalCwsUrl)
+
+ cloud.On("ValidateBusinessEmail", mock.Anything, mock.Anything).Return(nil)
+
+ resp, err := th.SystemAdminClient.RequestTrialLicenseWithExtraFields(validTrialRequest)
+ CheckErrorID(t, err, "api.license.add_license.unique_users.app_error")
+ CheckBadRequestStatus(t, resp)
+ })
+
+ t.Run("returns status 451 when it receives status 451", func(t *testing.T) {
+
+ license := model.NewTestLicense()
+ license.Features.Users = model.NewInt(nUsers)
+ licenseJSON, jsonErr := json.Marshal(license)
+ require.NoError(t, jsonErr)
+ testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+ res.WriteHeader(http.StatusUnavailableForLegalReasons)
+ }))
+ defer testServer.Close()
+
+ mockLicenseValidator := mocks2.LicenseValidatorIface{}
+ defer testutils.ResetLicenseValidator()
+
+ mockLicenseValidator.On("ValidateLicense", mock.Anything).Return(true, string(licenseJSON))
+ utils.LicenseValidator = &mockLicenseValidator
+ licenseManagerMock := &mocks.LicenseInterface{}
+ licenseManagerMock.On("CanStartTrial").Return(true, nil).Once()
+ th.App.Srv().Platform().SetLicenseManager(licenseManagerMock)
+
+ originalCwsUrl := *th.App.Srv().Config().CloudSettings.CWSURL
+ th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = testServer.URL })
+ defer func(requestTrialURL string) {
+ th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = requestTrialURL })
+ }(originalCwsUrl)
+
+ resp, err := th.SystemAdminClient.RequestTrialLicenseWithExtraFields(validTrialRequest)
+ require.Error(t, err)
+ require.Equal(t, resp.StatusCode, 451)
+ })
+
+ t.Run("returns status 400 if request is a mix of legacy and new fields", func(t *testing.T) {
+ validTrialRequest.CompanyCountry = ""
+ validTrialRequest.Users = 100
+ defer func() { validTrialRequest.CompanyCountry = "US" }()
+ license := model.NewTestLicense()
+ license.Features.Users = model.NewInt(nUsers)
+ licenseJSON, jsonErr := json.Marshal(license)
+ require.NoError(t, jsonErr)
+ testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+ res.WriteHeader(http.StatusOK)
+ response := map[string]string{
+ "license": string(licenseJSON),
+ }
+ err := json.NewEncoder(res).Encode(response)
+ require.NoError(t, err)
+ }))
+ defer testServer.Close()
+
+ mockLicenseValidator := mocks2.LicenseValidatorIface{}
+ defer testutils.ResetLicenseValidator()
+
+ mockLicenseValidator.On("ValidateLicense", mock.Anything).Return(true, string(licenseJSON))
+ utils.LicenseValidator = &mockLicenseValidator
+ licenseManagerMock := &mocks.LicenseInterface{}
+ licenseManagerMock.On("CanStartTrial").Return(true, nil).Once()
+ th.App.Srv().Platform().SetLicenseManager(licenseManagerMock)
+ originalCwsUrl := *th.App.Srv().Config().CloudSettings.CWSURL
+ th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = testServer.URL })
+ defer func(requestTrialURL string) {
+ th.App.UpdateConfig(func(cfg *model.Config) { *cfg.CloudSettings.CWSURL = requestTrialURL })
+ }(originalCwsUrl)
+
+ cloud.On("ValidateBusinessEmail", mock.Anything, mock.Anything).Return(nil)
+
+ resp, err := th.SystemAdminClient.RequestTrialLicenseWithExtraFields(validTrialRequest)
+ CheckErrorID(t, err, "api.license.request-trial.bad-request")
+ CheckBadRequestStatus(t, resp)
+ })
+
+ th.App.Srv().Platform().SetLicenseManager(nil)
+ t.Run("trial license should fail if LicenseManager is nil", func(t *testing.T) {
+ resp, err := th.SystemAdminClient.RequestTrialLicenseWithExtraFields(validTrialRequest)
+ CheckErrorID(t, err, "api.license.upgrade_needed.app_error")
+ CheckForbiddenStatus(t, resp)
+ })
+
+}
+
func TestRequestTrialLicense(t *testing.T) {
th := Setup(t)
defer th.TearDown()
diff --git a/server/channels/app/channels.go b/server/channels/app/channels.go
index c0241c9689..f2ae0e6d11 100644
--- a/server/channels/app/channels.go
+++ b/server/channels/app/channels.go
@@ -30,6 +30,7 @@ const ServerKey product.ServiceKey = "server"
type licenseSvc interface {
GetLicense() *model.License
RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError
+ RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError
}
// Channels contains all channels related state.
@@ -323,6 +324,10 @@ func (ch *Channels) License() *model.License {
return ch.licenseSvc.GetLicense()
}
+func (ch *Channels) RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError {
+ return ch.licenseSvc.RequestTrialLicenseWithExtraFields(requesterID, trialRequest)
+}
+
func (ch *Channels) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
return ch.licenseSvc.RequestTrialLicense(requesterID, users, termsAccepted,
receiveEmailsAccepted)
diff --git a/server/channels/app/license.go b/server/channels/app/license.go
index 252c232ad3..4b05cf1cf1 100644
--- a/server/channels/app/license.go
+++ b/server/channels/app/license.go
@@ -36,6 +36,51 @@ func (w *licenseWrapper) GetLicense() *model.License {
return w.srv.License()
}
+func (w *licenseWrapper) RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError {
+ if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
+ return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
+ }
+
+ requester, err := w.srv.userService.GetUser(requesterID)
+ if err != nil {
+ var nfErr *store.ErrNotFound
+ switch {
+ case errors.As(err, &nfErr):
+ return model.NewAppError("RequestTrialLicense", MissingAccountError, nil, "", http.StatusNotFound).Wrap(err)
+ default:
+ return model.NewAppError("RequestTrialLicense", "app.user.get_by_username.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
+ }
+ }
+
+ if w.srv.Cloud.ValidateBusinessEmail(requesterID, trialRequest.ContactEmail) != nil {
+ return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request.business-email", nil, "", http.StatusBadRequest)
+ }
+
+ // Create a new struct only using the fields from the request that are allowed to be set by the client
+ sanitizedRequest := &model.TrialLicenseRequest{
+ ServerID: w.srv.TelemetryId(),
+ Name: requester.GetDisplayName(model.ShowFullName),
+ Email: requester.Email,
+ SiteName: *w.srv.platform.Config().TeamSettings.SiteName,
+ SiteURL: *w.srv.platform.Config().ServiceSettings.SiteURL,
+ Users: trialRequest.Users,
+ TermsAccepted: trialRequest.TermsAccepted,
+ ReceiveEmailsAccepted: trialRequest.ReceiveEmailsAccepted,
+ ContactName: trialRequest.ContactName,
+ ContactEmail: trialRequest.ContactEmail,
+ CompanyName: trialRequest.CompanyName,
+ CompanySize: trialRequest.CompanySize,
+ CompanyCountry: trialRequest.CompanyCountry,
+ }
+
+ if !sanitizedRequest.IsValid() {
+ return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request", nil, "", http.StatusBadRequest)
+ }
+
+ return w.srv.platform.RequestTrialLicense(sanitizedRequest)
+}
+
+// DEPRECATED - use RequestTrialLicenseWithExtraFields instead. This function remains to support the Plugin API.
func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
diff --git a/server/i18n/en.json b/server/i18n/en.json
index 492fee9d58..0994b13552 100644
--- a/server/i18n/en.json
+++ b/server/i18n/en.json
@@ -2049,6 +2049,10 @@
"id": "api.license.request-trial.bad-request",
"translation": "The number of users requested is not correct."
},
+ {
+ "id": "api.license.request-trial.bad-request.business-email",
+ "translation": "Invalid business email for trial"
+ },
{
"id": "api.license.request-trial.bad-request.terms-not-accepted",
"translation": "You must accept the Mattermost Software Evaluation Agreement and Privacy Policy to request a license."
diff --git a/webapp/channels/src/actions/admin_actions.jsx b/webapp/channels/src/actions/admin_actions.jsx
index 2d97882800..fc60696af9 100644
--- a/webapp/channels/src/actions/admin_actions.jsx
+++ b/webapp/channels/src/actions/admin_actions.jsx
@@ -451,12 +451,12 @@ export function ping() {
};
}
-export function requestTrialLicense(users, termsAccepted, receiveEmailsAccepted, page) {
+export function requestTrialLicense(requestLicenseBody, page) {
return async () => {
try {
trackEvent('api', 'api_request_trial_license', {from_page: page});
- const response = await Client4.requestTrialLicense({users, terms_accepted: termsAccepted, receive_emails_accepted: receiveEmailsAccepted});
+ const response = await Client4.requestTrialLicense(requestLicenseBody);
return {data: response};
} catch (e) {
// In the event that the status code returned is 451, this request has been blocked because it originated from an embargoed country_dropdown
diff --git a/webapp/channels/src/components/admin_console/billing/delete_workspace/result_modal.scss b/webapp/channels/src/components/admin_console/billing/delete_workspace/result_modal.scss
index 2e5b6800f3..413d71dcdd 100644
--- a/webapp/channels/src/components/admin_console/billing/delete_workspace/result_modal.scss
+++ b/webapp/channels/src/components/admin_console/billing/delete_workspace/result_modal.scss
@@ -3,3 +3,77 @@
transform: translateY(50%);
}
}
+
+.ResultModal__small {
+ .modal-header {
+ .close {
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:focus {
+ background-color: rgba(var(--center-channel-color-rgb), 0.08);
+ color: rgba(var(--center-channel-color-rgb), 0.72);
+ opacity: 1;
+ }
+
+ top: 26px;
+ right: 26px;
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ color: rgba(var(--center-channel-color-rgb), 0.56) !important;
+ font-family:
+ 'Open Sans',
+ sans-serif;
+ font-size: 32px;
+ font-weight: 400;
+ }
+ }
+
+ .modal-dialog {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 600px;
+ height: 360px;
+ border: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
+ margin: auto;
+ border-radius: 8px;
+ transform: translate(-50%, -50%) !important;
+
+ .modal-header {
+ border: none;
+ background: var(--center-channel-bg) !important;
+ }
+
+ .modal-content {
+ height: 100%;
+ border-radius: 8px;
+
+ .IconMessage__svg-wrapper {
+ margin-left: 0;
+ }
+
+ .IconMessage-h3 {
+ margin-top: 0;
+ }
+
+ .IconMessage-sub {
+ max-width: 504px;
+ margin-top: 0;
+ }
+
+ .IconMessage-buttons {
+ justify-content: right;
+ padding-right: 24px;
+ margin-top: 60px;
+
+ .btn {
+ font-family: 'Open sans', sans-serif;
+ font-size: 12px;
+ font-weight: 600;
+ }
+ }
+ }
+ }
+}
diff --git a/webapp/channels/src/components/admin_console/billing/delete_workspace/result_modal.tsx b/webapp/channels/src/components/admin_console/billing/delete_workspace/result_modal.tsx
index bc452d3c57..6e9948756a 100644
--- a/webapp/channels/src/components/admin_console/billing/delete_workspace/result_modal.tsx
+++ b/webapp/channels/src/components/admin_console/billing/delete_workspace/result_modal.tsx
@@ -12,10 +12,12 @@ import {useOpenCloudZendeskSupportForm} from 'components/common/hooks/useOpenZen
import {closeModal} from 'actions/views/modals';
import {isModalOpen} from 'selectors/views/modals';
import {GlobalState} from 'types/store';
+import {Modal} from 'react-bootstrap';
import './result_modal.scss';
type Props = {
+ type?: string;
onHide?: () => void;
icon: JSX.Element;
title: JSX.Element;
@@ -28,47 +30,77 @@ type Props = {
ignoreExit: boolean;
};
-export default function ResultModal(props: Props) {
+export default function ResultModal({type, icon, title, subtitle, primaryButtonText, primaryButtonHandler, identifier, contactSupportButtonVisible, resultType, ignoreExit, onHide}: Props) {
const dispatch = useDispatch();
const [openContactSupport] = useOpenCloudZendeskSupportForm('Delete workspace', '');
const isResultModalOpen = useSelector((state: GlobalState) =>
- isModalOpen(state, props.identifier),
+ isModalOpen(state, identifier),
);
- const onHide = () => {
- dispatch(closeModal(props.identifier));
- if (typeof props.onHide === 'function') {
- props.onHide();
- }
+ const handleHide = () => {
+ dispatch(closeModal(identifier));
+ onHide?.();
};
- const modalType = `delete-workspace-result_modal__${props.resultType}`;
+ const modalType = `delete-workspace-result_modal__${resultType}`;
+ if (type === 'small') {
+ return (
+