[MM-53086] Remove SendWelcomePost A/B test and feature (#23733)
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -895,7 +895,6 @@ type AppIface interface {
|
|||||||
InviteNewUsersToTeamGracefully(memberInvite *model.MemberInvite, teamID, senderId string, reminderInterval string) ([]*model.EmailInviteWithError, *model.AppError)
|
InviteNewUsersToTeamGracefully(memberInvite *model.MemberInvite, teamID, senderId string, reminderInterval string) ([]*model.EmailInviteWithError, *model.AppError)
|
||||||
IsCRTEnabledForUser(c request.CTX, userID string) bool
|
IsCRTEnabledForUser(c request.CTX, userID string) bool
|
||||||
IsConfigReadOnly() bool
|
IsConfigReadOnly() bool
|
||||||
IsFirstAdmin(user *model.User) bool
|
|
||||||
IsFirstUserAccount() bool
|
IsFirstUserAccount() bool
|
||||||
IsLeader() bool
|
IsLeader() bool
|
||||||
IsPasswordValid(password string) *model.AppError
|
IsPasswordValid(password string) *model.AppError
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattermost/logr/v2"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
||||||
@@ -189,35 +187,6 @@ func (a *App) JoinDefaultChannels(c request.CTX, teamID string, user *model.User
|
|||||||
message.Add("user_id", user.Id)
|
message.Add("user_id", user.Id)
|
||||||
message.Add("team_id", channel.TeamId)
|
message.Add("team_id", channel.TeamId)
|
||||||
a.Publish(message)
|
a.Publish(message)
|
||||||
|
|
||||||
// A/B Test on the welcome post
|
|
||||||
if a.Config().FeatureFlags.SendWelcomePost && channelName == model.DefaultChannelName {
|
|
||||||
nbTeams, err := a.Srv().Store().Team().AnalyticsTeamCount(&model.TeamSearch{
|
|
||||||
IncludeDeleted: model.NewBool(true),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Warn("unable to get number of teams", logr.Err(err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if nbTeams == 1 && a.IsFirstAdmin(user) {
|
|
||||||
// Post the welcome message
|
|
||||||
if _, err := a.CreatePost(c, &model.Post{
|
|
||||||
ChannelId: channel.Id,
|
|
||||||
Type: model.PostTypeWelcomePost,
|
|
||||||
UserId: user.Id,
|
|
||||||
}, channel, false, false); err != nil {
|
|
||||||
c.Logger().Warn("unable to post welcome message", logr.Err(err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ts := a.Srv().GetTelemetryService()
|
|
||||||
if ts != nil {
|
|
||||||
ts.SendTelemetry("welcome-message-sent", map[string]any{
|
|
||||||
"category": "growth",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if nErr != nil {
|
if nErr != nil {
|
||||||
|
|||||||
@@ -12003,23 +12003,6 @@ func (a *OpenTracingAppLayer) IsConfigReadOnly() bool {
|
|||||||
return resultVar0
|
return resultVar0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) IsFirstAdmin(user *model.User) bool {
|
|
||||||
origCtx := a.ctx
|
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.IsFirstAdmin")
|
|
||||||
|
|
||||||
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.IsFirstAdmin(user)
|
|
||||||
|
|
||||||
return resultVar0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) IsFirstUserAccount() bool {
|
func (a *OpenTracingAppLayer) IsFirstUserAccount() bool {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.IsFirstUserAccount")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.IsFirstUserAccount")
|
||||||
|
|||||||
@@ -217,19 +217,6 @@ func (a *App) IsFirstUserAccount() bool {
|
|||||||
return a.ch.srv.platform.IsFirstUserAccount()
|
return a.ch.srv.platform.IsFirstUserAccount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) IsFirstAdmin(user *model.User) bool {
|
|
||||||
if !user.IsSystemAdmin() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
adminID, err := a.Srv().Store().User().GetFirstSystemAdminID()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return adminID == user.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUser creates a user and sets several fields of the returned User struct to
|
// CreateUser creates a user and sets several fields of the returned User struct to
|
||||||
// their zero values.
|
// their zero values.
|
||||||
func (a *App) CreateUser(c request.CTX, user *model.User) (*model.User, *model.AppError) {
|
func (a *App) CreateUser(c request.CTX, user *model.User) (*model.User, *model.AppError) {
|
||||||
|
|||||||
@@ -1875,57 +1875,6 @@ func TestCreateUserWithInitialPreferences(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsFirstAdmin(t *testing.T) {
|
|
||||||
t.Run("should return false if user is not sysadmin", func(t *testing.T) {
|
|
||||||
th := SetupWithStoreMock(t)
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
Id := model.NewId()
|
|
||||||
isFirstAdmin := th.App.IsFirstAdmin(&model.User{
|
|
||||||
Id: Id,
|
|
||||||
Roles: model.SystemUserRoleId,
|
|
||||||
})
|
|
||||||
require.False(t, isFirstAdmin)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should return false if user is sysadmin but not the first one", func(t *testing.T) {
|
|
||||||
th := SetupWithStoreMock(t)
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
Id := model.NewId()
|
|
||||||
|
|
||||||
mockUserStore := storemocks.UserStore{}
|
|
||||||
mockUserStore.On("GetFirstSystemAdminID").Return(model.NewId(), nil)
|
|
||||||
|
|
||||||
mockStore := th.App.Srv().Store().(*storemocks.Store)
|
|
||||||
mockStore.On("User").Return(&mockUserStore)
|
|
||||||
|
|
||||||
isFirstAdmin := th.App.IsFirstAdmin(&model.User{
|
|
||||||
Id: Id,
|
|
||||||
Roles: model.SystemAdminRoleId,
|
|
||||||
})
|
|
||||||
require.False(t, isFirstAdmin)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("should return true if user is sysadmin and the first one", func(t *testing.T) {
|
|
||||||
th := SetupWithStoreMock(t)
|
|
||||||
defer th.TearDown()
|
|
||||||
|
|
||||||
Id := model.NewId()
|
|
||||||
|
|
||||||
mockStore := th.App.Srv().Store().(*storemocks.Store)
|
|
||||||
mockUserStore := storemocks.UserStore{}
|
|
||||||
mockUserStore.On("GetFirstSystemAdminID").Return(Id, nil)
|
|
||||||
mockStore.On("User").Return(&mockUserStore)
|
|
||||||
|
|
||||||
isFirstAdmin := th.App.IsFirstAdmin(&model.User{
|
|
||||||
Id: Id,
|
|
||||||
Roles: model.SystemAdminRoleId,
|
|
||||||
})
|
|
||||||
require.True(t, isFirstAdmin)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSendSubscriptionHistoryEvent(t *testing.T) {
|
func TestSendSubscriptionHistoryEvent(t *testing.T) {
|
||||||
cloudProduct := &model.Product{
|
cloudProduct := &model.Product{
|
||||||
ID: "prod_test1",
|
ID: "prod_test1",
|
||||||
|
|||||||
@@ -11404,24 +11404,6 @@ func (s *OpenTracingLayerUserStore) GetEtagForProfilesNotInTeam(teamID string) s
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerUserStore) GetFirstSystemAdminID() (string, error) {
|
|
||||||
origCtx := s.Root.Store.Context()
|
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.GetFirstSystemAdminID")
|
|
||||||
s.Root.Store.SetContext(newCtx)
|
|
||||||
defer func() {
|
|
||||||
s.Root.Store.SetContext(origCtx)
|
|
||||||
}()
|
|
||||||
|
|
||||||
defer span.Finish()
|
|
||||||
result, err := s.UserStore.GetFirstSystemAdminID()
|
|
||||||
if err != nil {
|
|
||||||
span.LogFields(spanlog.Error(err))
|
|
||||||
ext.Error.Set(span, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OpenTracingLayerUserStore) GetForLogin(loginID string, allowSignInWithUsername bool, allowSignInWithEmail bool) (*model.User, error) {
|
func (s *OpenTracingLayerUserStore) GetForLogin(loginID string, allowSignInWithUsername bool, allowSignInWithEmail bool) (*model.User, error) {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.GetForLogin")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.GetForLogin")
|
||||||
|
|||||||
@@ -13010,27 +13010,6 @@ func (s *RetryLayerUserStore) GetEtagForProfilesNotInTeam(teamID string) string
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RetryLayerUserStore) GetFirstSystemAdminID() (string, error) {
|
|
||||||
|
|
||||||
tries := 0
|
|
||||||
for {
|
|
||||||
result, err := s.UserStore.GetFirstSystemAdminID()
|
|
||||||
if err == nil {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
if !isRepeatableError(err) {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
tries++
|
|
||||||
if tries >= 3 {
|
|
||||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RetryLayerUserStore) GetForLogin(loginID string, allowSignInWithUsername bool, allowSignInWithEmail bool) (*model.User, error) {
|
func (s *RetryLayerUserStore) GetForLogin(loginID string, allowSignInWithUsername bool, allowSignInWithEmail bool) (*model.User, error) {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
|
|||||||
@@ -1746,16 +1746,6 @@ func (us SqlUserStore) InferSystemInstallDate() (int64, error) {
|
|||||||
return createAt, nil
|
return createAt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) GetFirstSystemAdminID() (string, error) {
|
|
||||||
var id string
|
|
||||||
err := us.GetReplicaX().Get(&id, "SELECT Id FROM Users WHERE Roles LIKE ? ORDER BY CreateAt ASC LIMIT 1", "%system_admin%")
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "failed to get first system admin")
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us SqlUserStore) GetUsersBatchForIndexing(startTime int64, startFileID string, limit int) ([]*model.UserForIndexing, error) {
|
func (us SqlUserStore) GetUsersBatchForIndexing(startTime int64, startFileID string, limit int) ([]*model.UserForIndexing, error) {
|
||||||
users := []*model.User{}
|
users := []*model.User{}
|
||||||
usersQuery, args, err := us.usersQuery.
|
usersQuery, args, err := us.usersQuery.
|
||||||
|
|||||||
@@ -487,7 +487,6 @@ type UserStore interface {
|
|||||||
IsEmpty(excludeBots bool) (bool, error)
|
IsEmpty(excludeBots bool) (bool, error)
|
||||||
GetUsersWithInvalidEmails(page int, perPage int, restrictedDomains string) ([]*model.User, error)
|
GetUsersWithInvalidEmails(page int, perPage int, restrictedDomains string) ([]*model.User, error)
|
||||||
InsertUsers(users []*model.User) error
|
InsertUsers(users []*model.User) error
|
||||||
GetFirstSystemAdminID() (string, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BotStore interface {
|
type BotStore interface {
|
||||||
|
|||||||
@@ -635,30 +635,6 @@ func (_m *UserStore) GetEtagForProfilesNotInTeam(teamID string) string {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFirstSystemAdminID provides a mock function with given fields:
|
|
||||||
func (_m *UserStore) GetFirstSystemAdminID() (string, error) {
|
|
||||||
ret := _m.Called()
|
|
||||||
|
|
||||||
var r0 string
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(0).(func() (string, error)); ok {
|
|
||||||
return rf()
|
|
||||||
}
|
|
||||||
if rf, ok := ret.Get(0).(func() string); ok {
|
|
||||||
r0 = rf()
|
|
||||||
} else {
|
|
||||||
r0 = ret.Get(0).(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func() error); ok {
|
|
||||||
r1 = rf()
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetForLogin provides a mock function with given fields: loginID, allowSignInWithUsername, allowSignInWithEmail
|
// GetForLogin provides a mock function with given fields: loginID, allowSignInWithUsername, allowSignInWithEmail
|
||||||
func (_m *UserStore) GetForLogin(loginID string, allowSignInWithUsername bool, allowSignInWithEmail bool) (*model.User, error) {
|
func (_m *UserStore) GetForLogin(loginID string, allowSignInWithUsername bool, allowSignInWithEmail bool) (*model.User, error) {
|
||||||
ret := _m.Called(loginID, allowSignInWithUsername, allowSignInWithEmail)
|
ret := _m.Called(loginID, allowSignInWithUsername, allowSignInWithEmail)
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlStore) {
|
|||||||
t.Run("ResetLastPictureUpdate", func(t *testing.T) { testUserStoreResetLastPictureUpdate(t, ss) })
|
t.Run("ResetLastPictureUpdate", func(t *testing.T) { testUserStoreResetLastPictureUpdate(t, ss) })
|
||||||
t.Run("GetKnownUsers", func(t *testing.T) { testGetKnownUsers(t, ss) })
|
t.Run("GetKnownUsers", func(t *testing.T) { testGetKnownUsers(t, ss) })
|
||||||
t.Run("GetUsersWithInvalidEmails", func(t *testing.T) { testGetUsersWithInvalidEmails(t, ss) })
|
t.Run("GetUsersWithInvalidEmails", func(t *testing.T) { testGetUsersWithInvalidEmails(t, ss) })
|
||||||
t.Run("GetFirstSystemAdminID", func(t *testing.T) { testUserStoreGetFirstSystemAdminID(t, ss) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUserStoreSave(t *testing.T, ss store.Store) {
|
func testUserStoreSave(t *testing.T, ss store.Store) {
|
||||||
@@ -4194,30 +4193,6 @@ func testCount(t *testing.T, ss store.Store) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUserStoreGetFirstSystemAdminID(t *testing.T, ss store.Store) {
|
|
||||||
sysAdmin := &model.User{}
|
|
||||||
sysAdmin.Email = MakeEmail()
|
|
||||||
sysAdmin.Roles = model.SystemAdminRoleId + " " + model.SystemUserRoleId
|
|
||||||
sysAdmin, err := ss.User().Save(sysAdmin)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() { require.NoError(t, ss.User().PermanentDelete(sysAdmin.Id)) }()
|
|
||||||
|
|
||||||
// We need the second system admin to be created after the first one
|
|
||||||
// our granulirity is ms
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
|
|
||||||
sysAdmin2 := &model.User{}
|
|
||||||
sysAdmin2.Email = MakeEmail()
|
|
||||||
sysAdmin2.Roles = model.SystemAdminRoleId + " " + model.SystemUserRoleId
|
|
||||||
sysAdmin2, err = ss.User().Save(sysAdmin2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() { require.NoError(t, ss.User().PermanentDelete(sysAdmin2.Id)) }()
|
|
||||||
|
|
||||||
returnedId, err := ss.User().GetFirstSystemAdminID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, sysAdmin.Id, returnedId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUserStoreAnalyticsActiveCount(t *testing.T, ss store.Store, s SqlStore) {
|
func testUserStoreAnalyticsActiveCount(t *testing.T, ss store.Store, s SqlStore) {
|
||||||
|
|
||||||
cleanupStatusStore(t, s)
|
cleanupStatusStore(t, s)
|
||||||
|
|||||||
@@ -10272,22 +10272,6 @@ func (s *TimerLayerUserStore) GetEtagForProfilesNotInTeam(teamID string) string
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TimerLayerUserStore) GetFirstSystemAdminID() (string, error) {
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
result, err := s.UserStore.GetFirstSystemAdminID()
|
|
||||||
|
|
||||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
|
||||||
if s.Root.Metrics != nil {
|
|
||||||
success := "false"
|
|
||||||
if err == nil {
|
|
||||||
success = "true"
|
|
||||||
}
|
|
||||||
s.Root.Metrics.ObserveStoreMethodDuration("UserStore.GetFirstSystemAdminID", success, elapsed)
|
|
||||||
}
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TimerLayerUserStore) GetForLogin(loginID string, allowSignInWithUsername bool, allowSignInWithEmail bool) (*model.User, error) {
|
func (s *TimerLayerUserStore) GetForLogin(loginID string, allowSignInWithUsername bool, allowSignInWithEmail bool) (*model.User, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ type FeatureFlags struct {
|
|||||||
|
|
||||||
CommandPalette bool
|
CommandPalette bool
|
||||||
|
|
||||||
// A/B Test on posting a welcome message
|
|
||||||
SendWelcomePost bool
|
|
||||||
|
|
||||||
PostPriority bool
|
PostPriority bool
|
||||||
|
|
||||||
// Enable WYSIWYG text editor
|
// Enable WYSIWYG text editor
|
||||||
@@ -90,7 +87,6 @@ func (f *FeatureFlags) SetDefaults() {
|
|||||||
f.InsightsEnabled = false
|
f.InsightsEnabled = false
|
||||||
f.CommandPalette = false
|
f.CommandPalette = false
|
||||||
f.CallsEnabled = true
|
f.CallsEnabled = true
|
||||||
f.SendWelcomePost = true
|
|
||||||
f.PostPriority = true
|
f.PostPriority = true
|
||||||
f.PeopleProduct = false
|
f.PeopleProduct = false
|
||||||
f.ReduceOnBoardingTaskList = false
|
f.ReduceOnBoardingTaskList = false
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ const (
|
|||||||
PostTypeChannelRestored = "system_channel_restored"
|
PostTypeChannelRestored = "system_channel_restored"
|
||||||
PostTypeEphemeral = "system_ephemeral"
|
PostTypeEphemeral = "system_ephemeral"
|
||||||
PostTypeChangeChannelPrivacy = "system_change_chan_privacy"
|
PostTypeChangeChannelPrivacy = "system_change_chan_privacy"
|
||||||
PostTypeWelcomePost = "system_welcome_post"
|
|
||||||
PostTypeAddBotTeamsChannels = "add_bot_teams_channels"
|
PostTypeAddBotTeamsChannels = "add_bot_teams_channels"
|
||||||
PostTypeSystemWarnMetricStatus = "warn_metric_status"
|
PostTypeSystemWarnMetricStatus = "warn_metric_status"
|
||||||
PostTypeMe = "me"
|
PostTypeMe = "me"
|
||||||
@@ -434,7 +433,6 @@ func (o *Post) IsValid(maxPostSize int) *AppError {
|
|||||||
PostTypeChangeChannelPrivacy,
|
PostTypeChangeChannelPrivacy,
|
||||||
PostTypeAddBotTeamsChannels,
|
PostTypeAddBotTeamsChannels,
|
||||||
PostTypeSystemWarnMetricStatus,
|
PostTypeSystemWarnMetricStatus,
|
||||||
PostTypeWelcomePost,
|
|
||||||
PostTypeReminder,
|
PostTypeReminder,
|
||||||
PostTypeMe:
|
PostTypeMe:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -925,7 +925,7 @@ func TestPostForPlugin(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("non post type custom_up_notification for plugin should have requested features prop", func(t *testing.T) {
|
t.Run("non post type custom_up_notification for plugin should have requested features prop", func(t *testing.T) {
|
||||||
p := &Post{
|
p := &Post{
|
||||||
Type: PostTypeWelcomePost,
|
Type: PostTypeReminder,
|
||||||
}
|
}
|
||||||
props := make(StringInterface)
|
props := make(StringInterface)
|
||||||
props["requested_features"] = "test_requested_features_map"
|
props["requested_features"] = "test_requested_features_map"
|
||||||
|
|||||||
@@ -87,8 +87,6 @@ import {UserProfile} from '@mattermost/types/users';
|
|||||||
|
|
||||||
import {ActionResult} from 'mattermost-redux/types/actions';
|
import {ActionResult} from 'mattermost-redux/types/actions';
|
||||||
|
|
||||||
import WelcomePostRenderer from 'components/welcome_post_renderer';
|
|
||||||
|
|
||||||
import {applyLuxonDefaults} from './effects';
|
import {applyLuxonDefaults} from './effects';
|
||||||
|
|
||||||
import RootProvider from './root_provider';
|
import RootProvider from './root_provider';
|
||||||
@@ -442,7 +440,6 @@ export default class Root extends React.PureComponent<Props, State> {
|
|||||||
// See figma design on issue https://mattermost.atlassian.net/browse/MM-43649
|
// See figma design on issue https://mattermost.atlassian.net/browse/MM-43649
|
||||||
this.props.actions.registerCustomPostRenderer('custom_up_notification', OpenPricingModalPost, 'upgrade_post_message_renderer');
|
this.props.actions.registerCustomPostRenderer('custom_up_notification', OpenPricingModalPost, 'upgrade_post_message_renderer');
|
||||||
this.props.actions.registerCustomPostRenderer('custom_pl_notification', OpenPluginInstallPost, 'plugin_install_post_message_renderer');
|
this.props.actions.registerCustomPostRenderer('custom_pl_notification', OpenPluginInstallPost, 'plugin_install_post_message_renderer');
|
||||||
this.props.actions.registerCustomPostRenderer('system_welcome_post', WelcomePostRenderer, 'welcome_post_renderer');
|
|
||||||
|
|
||||||
if (this.desktopMediaQuery.addEventListener) {
|
if (this.desktopMediaQuery.addEventListener) {
|
||||||
this.desktopMediaQuery.addEventListener('change', this.handleMediaQueryChangeEvent);
|
this.desktopMediaQuery.addEventListener('change', this.handleMediaQueryChangeEvent);
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
@import 'utils/mixins';
|
|
||||||
|
|
||||||
.WelcomePostRenderer {
|
|
||||||
color: var(--center-channel-color);
|
|
||||||
|
|
||||||
&__ActionsContainer {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 1em;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
@include tertiary-button;
|
|
||||||
@include button-small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {mount} from 'enzyme';
|
|
||||||
|
|
||||||
import {Provider} from 'react-redux';
|
|
||||||
|
|
||||||
import WelcomePostRenderer from 'components/welcome_post_renderer';
|
|
||||||
import {GlobalState} from 'types/store';
|
|
||||||
import {mockStore} from 'tests/test_store';
|
|
||||||
import {Post} from '@mattermost/types/posts';
|
|
||||||
|
|
||||||
describe('components/WelcomePostRenderer', () => {
|
|
||||||
const initialStore = {
|
|
||||||
entities: {
|
|
||||||
channels: {
|
|
||||||
currentChannelId: 'current_channel_id',
|
|
||||||
channels: {
|
|
||||||
current_channel_id: {id: 'current_channel_id'},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
currentUserId: 'user',
|
|
||||||
profiles: {
|
|
||||||
user: {id: 'user', roles: 'system_user'},
|
|
||||||
admin: {id: 'admin', roles: 'system_admin'},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
general: {
|
|
||||||
config: {},
|
|
||||||
},
|
|
||||||
teams: {teams: {current_team_id: {}}, currentTeamId: 'current_team_id'},
|
|
||||||
posts: {posts: {}},
|
|
||||||
groups: {
|
|
||||||
groups: {},
|
|
||||||
myGroups: [],
|
|
||||||
},
|
|
||||||
preferences: {myPreferences: {}},
|
|
||||||
emojis: {},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
components: {},
|
|
||||||
},
|
|
||||||
|
|
||||||
} as unknown as GlobalState;
|
|
||||||
|
|
||||||
test('should display a help and settings button for users', () => {
|
|
||||||
const store = mockStore({...initialStore});
|
|
||||||
const wrapper = mount(
|
|
||||||
<Provider store={store.store}>
|
|
||||||
<WelcomePostRenderer
|
|
||||||
post={{} as Post}
|
|
||||||
/>
|
|
||||||
</Provider>,
|
|
||||||
store.mountOptions,
|
|
||||||
);
|
|
||||||
|
|
||||||
let found = 0;
|
|
||||||
wrapper.find('button').forEach((button) => {
|
|
||||||
if (button.text() === '/help') {
|
|
||||||
found++;
|
|
||||||
}
|
|
||||||
if (button.text() === '/settings') {
|
|
||||||
found++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(found).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display a help and marketplace button for admin', () => {
|
|
||||||
const store = mockStore({
|
|
||||||
...initialStore,
|
|
||||||
entities: {
|
|
||||||
...initialStore.entities,
|
|
||||||
users: {
|
|
||||||
...initialStore.entities.users,
|
|
||||||
currentUserId: 'admin',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const wrapper = mount(
|
|
||||||
<Provider store={store.store}>
|
|
||||||
<WelcomePostRenderer
|
|
||||||
post={{} as Post}
|
|
||||||
/>
|
|
||||||
</Provider>,
|
|
||||||
store.mountOptions,
|
|
||||||
);
|
|
||||||
|
|
||||||
let found = 0;
|
|
||||||
wrapper.find('button').forEach((button) => {
|
|
||||||
if (button.text() === '/help') {
|
|
||||||
found++;
|
|
||||||
}
|
|
||||||
if (button.text() === '/marketplace') {
|
|
||||||
found++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(found).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import {useIntl} from 'react-intl';
|
|
||||||
|
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
|
||||||
|
|
||||||
import {Post} from '@mattermost/types/posts';
|
|
||||||
import {submitCommand} from 'actions/views/create_comment';
|
|
||||||
import PostMarkdown from 'components/post_markdown';
|
|
||||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/common';
|
|
||||||
import {isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
|
|
||||||
|
|
||||||
import {PostDraft} from 'types/store/draft';
|
|
||||||
import {localizeMessage} from 'utils/utils';
|
|
||||||
|
|
||||||
import './index.scss';
|
|
||||||
|
|
||||||
export default function WelcomePostRenderer(props: {post: Post}) {
|
|
||||||
const isAdmin = useSelector(isCurrentUserSystemAdmin);
|
|
||||||
const currentChannelId = useSelector(getCurrentChannelId);
|
|
||||||
const {formatMessage} = useIntl();
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
let message = '';
|
|
||||||
const actions: React.ReactNode[] = [];
|
|
||||||
|
|
||||||
const makeOnClickForCommand = (command: string) => {
|
|
||||||
return () => {
|
|
||||||
const draft: PostDraft = {
|
|
||||||
message: command,
|
|
||||||
uploadsInProgress: [],
|
|
||||||
fileInfos: [],
|
|
||||||
} as unknown as PostDraft;
|
|
||||||
dispatch(submitCommand(currentChannelId, '', draft));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeButton = (text: React.ReactNode, command: string) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
onClick={makeOnClickForCommand(command)}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const helpButton = makeButton(formatMessage({
|
|
||||||
id: 'welcome_post_renderer.button_label.slash_help',
|
|
||||||
defaultMessage: '/help',
|
|
||||||
}), '/help');
|
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
message = [
|
|
||||||
'### ' + localizeMessage('welcome_post_renderer.admin_message.title', 'Welcome to Mattermost! :rocket:'),
|
|
||||||
'',
|
|
||||||
localizeMessage('welcome_post_renderer.admin_message.first_paragraph', 'Mattermost is an open source platform for secure communication, collaboration, and orchestration of work across tools and teams.'),
|
|
||||||
localizeMessage('welcome_post_renderer.admin_message.second_paragraph', 'Here is a list of commands to use to try and get familiar with the platform.'),
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
actions.push(makeButton(formatMessage({
|
|
||||||
id: 'welcome_post_renderer.button_label.slash_marketplace',
|
|
||||||
defaultMessage: '/marketplace',
|
|
||||||
}), '/marketplace'));
|
|
||||||
} else {
|
|
||||||
message = [
|
|
||||||
'### ' + localizeMessage('welcome_post_renderer.user_message.title', 'Welcome to Mattermost! :rocket:'),
|
|
||||||
'',
|
|
||||||
localizeMessage('welcome_post_renderer.user_message.first_paragraph', 'Mattermost is an open source platform for secure communication, collaboration, and orchestration of work across tools and teams.'),
|
|
||||||
localizeMessage('welcome_post_renderer.user_message.second_paragraph', 'Here is a list of commands to use to try and get familiar with the platform.'),
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
actions.push(makeButton(formatMessage({
|
|
||||||
id: 'welcome_post_renderer.button_label.slash_settings',
|
|
||||||
defaultMessage: '/settings',
|
|
||||||
}), '/settings'));
|
|
||||||
}
|
|
||||||
actions.push(helpButton);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='WelcomePostRenderer'>
|
|
||||||
<PostMarkdown
|
|
||||||
message={message}
|
|
||||||
isRHS={false}
|
|
||||||
post={props.post}
|
|
||||||
channelId={props.post.channel_id}
|
|
||||||
mentionKeys={[]}
|
|
||||||
/>
|
|
||||||
{actions.length > 0 && (
|
|
||||||
<div className='WelcomePostRenderer__ActionsContainer'>
|
|
||||||
{actions.map((action, idx) => (
|
|
||||||
<div
|
|
||||||
key={'action-' + idx}
|
|
||||||
>
|
|
||||||
{action}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -5681,15 +5681,6 @@
|
|||||||
"webapp.mattermost.feature.unlimited_file_storage": "Unlimited File Storage",
|
"webapp.mattermost.feature.unlimited_file_storage": "Unlimited File Storage",
|
||||||
"webapp.mattermost.feature.unlimited_messages": "Unlimited Messages",
|
"webapp.mattermost.feature.unlimited_messages": "Unlimited Messages",
|
||||||
"webapp.mattermost.feature.upgrade_downgraded_workspace": "Revert the workspace to a paid plan",
|
"webapp.mattermost.feature.upgrade_downgraded_workspace": "Revert the workspace to a paid plan",
|
||||||
"welcome_post_renderer.admin_message.first_paragraph": "Mattermost is an open source platform for secure communication, collaboration, and orchestration of work across tools and teams.",
|
|
||||||
"welcome_post_renderer.admin_message.second_paragraph": "Here is a list of commands to use to try and get familiar with the platform.",
|
|
||||||
"welcome_post_renderer.admin_message.title": "Welcome to Mattermost! :rocket:",
|
|
||||||
"welcome_post_renderer.button_label.slash_help": "/help",
|
|
||||||
"welcome_post_renderer.button_label.slash_marketplace": "/marketplace",
|
|
||||||
"welcome_post_renderer.button_label.slash_settings": "/settings",
|
|
||||||
"welcome_post_renderer.user_message.first_paragraph": "Mattermost is an open source platform for secure communication, collaboration, and orchestration of work across tools and teams.",
|
|
||||||
"welcome_post_renderer.user_message.second_paragraph": "Here is a list of commands to use to try and get familiar with the platform.",
|
|
||||||
"welcome_post_renderer.user_message.title": "Welcome to Mattermost! :rocket:",
|
|
||||||
"widget.input.clear": "Clear",
|
"widget.input.clear": "Clear",
|
||||||
"widget.input.required": "This field is required",
|
"widget.input.required": "This field is required",
|
||||||
"widget.passwordInput.createPassword": "Choose a Password",
|
"widget.passwordInput.createPassword": "Choose a Password",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user