[MM-49751] - Turn off Inactive Server Email (#22648)

* [MM-49751] - Turn off Inactive Server Email

* remove unused var

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Allan Guwatudde
2023-03-27 22:38:19 +03:00
коммит произвёл GitHub
родитель 50a9b9bdfa
Коммит 0140e94d77
23 изменённых файлов: 0 добавлений и 1088 удалений

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

@@ -1624,7 +1624,6 @@ type EmailSettings struct {
LoginButtonColor *string `access:"experimental_features"` LoginButtonColor *string `access:"experimental_features"`
LoginButtonBorderColor *string `access:"experimental_features"` LoginButtonBorderColor *string `access:"experimental_features"`
LoginButtonTextColor *string `access:"experimental_features"` LoginButtonTextColor *string `access:"experimental_features"`
EnableInactivityEmail *bool
} }
func (s *EmailSettings) SetDefaults(isUpdate bool) { func (s *EmailSettings) SetDefaults(isUpdate bool) {
@@ -1767,10 +1766,6 @@ func (s *EmailSettings) SetDefaults(isUpdate bool) {
if s.LoginButtonTextColor == nil { if s.LoginButtonTextColor == nil {
s.LoginButtonTextColor = NewString("#2389D7") s.LoginButtonTextColor = NewString("#2389D7")
} }
if s.EnableInactivityEmail == nil {
s.EnableInactivityEmail = NewBool(true)
}
} }
type RateLimitSettings struct { type RateLimitSettings struct {

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

@@ -41,8 +41,6 @@ type FeatureFlags struct {
NormalizeLdapDNs bool NormalizeLdapDNs bool
EnableInactivityCheckJob bool
// Enable special onboarding flow for first admin // Enable special onboarding flow for first admin
UseCaseOnboarding bool UseCaseOnboarding bool
@@ -92,7 +90,6 @@ func (f *FeatureFlags) SetDefaults() {
f.BoardsFeatureFlags = "" f.BoardsFeatureFlags = ""
f.BoardsDataRetention = false f.BoardsDataRetention = false
f.NormalizeLdapDNs = false f.NormalizeLdapDNs = false
f.EnableInactivityCheckJob = true
f.UseCaseOnboarding = true f.UseCaseOnboarding = true
f.GraphQL = false f.GraphQL = false
f.InsightsEnabled = true f.InsightsEnabled = true

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

@@ -11,8 +11,6 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strconv"
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -26,8 +24,6 @@ import (
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
) )
const serverInactivityHours = 100
// Returns category if enabled is true (default false) // Returns category if enabled is true (default false)
// If "" is returned when enabled is false, the category headers aren't attached to the email // If "" is returned when enabled is false, the category headers aren't attached to the email
func getSendGridCategory(category string, enabled bool) string { func getSendGridCategory(category string, enabled bool) string {
@@ -948,48 +944,6 @@ func (es *Service) CreateVerifyEmailToken(userID string, newEmail string) (*mode
return token, nil return token, nil
} }
func (es *Service) SendLicenseInactivityEmail(email, name, locale, siteURL string) error {
T := i18n.GetUserTranslations(locale)
subject := T("api.templates.server_inactivity_subject")
data := es.NewEmailTemplateData(locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = T("api.templates.server_inactivity_title")
data.Props["SubTitle"] = T("api.templates.server_inactivity_subtitle", map[string]any{"Name": name})
data.Props["InfoBullet"] = T("api.templates.server_inactivity_info_bullet")
data.Props["InfoBullet1"] = T("api.templates.server_inactivity_info_bullet1")
data.Props["InfoBullet2"] = T("api.templates.server_inactivity_info_bullet2")
data.Props["Info"] = T("api.templates.server_inactivity_info")
data.Props["EmailUs"] = T("api.templates.email_us_anytime_at")
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
data.Props["Button"] = T("api.templates.server_inactivity_button")
data.Props["SupportEmail"] = "feedback@mattermost.com"
data.Props["ButtonURL"] = siteURL
data.Props["Channels"] = T("Channels")
data.Props["Playbooks"] = T("Playbooks")
data.Props["Boards"] = T("Boards")
inactivityDurationHoursEnv := os.Getenv("MM_INACTIVITY_DURATION")
inactivityDurationHours, parseError := strconv.ParseFloat(inactivityDurationHoursEnv, 64)
if parseError != nil {
// default to 100 hours
inactivityDurationHours = serverInactivityHours
}
data.Props["FooterDisclaimer"] = T("api.templates.server_inactivity_footer_disclaimer", map[string]any{"Hours": inactivityDurationHours})
body, err := es.templatesContainer.RenderToString("inactivity_body", data)
if err != nil {
return err
}
if err := es.sendMail(email, subject, body, "LicenseInactivityEmail"); err != nil {
return err
}
return nil
}
func (es *Service) SendLicenseUpForRenewalEmail(email, name, locale, siteURL, ctaTitle, ctaLink, ctaText string, daysToExpiration int) error { func (es *Service) SendLicenseUpForRenewalEmail(email, name, locale, siteURL, ctaTitle, ctaLink, ctaText string, daysToExpiration int) error {
T := i18n.GetUserTranslations(locale) T := i18n.GetUserTranslations(locale)
subject := T("api.templates.license_up_for_renewal_subject") subject := T("api.templates.license_up_for_renewal_subject")

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

@@ -408,7 +408,6 @@ func TestMailServiceConfig(t *testing.T) {
LoginButtonColor: new(string), LoginButtonColor: new(string),
LoginButtonBorderColor: new(string), LoginButtonBorderColor: new(string),
LoginButtonTextColor: new(string), LoginButtonTextColor: new(string),
EnableInactivityEmail: new(bool),
}, },
} }
}, },

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

@@ -344,20 +344,6 @@ func (_m *ServiceInterface) SendInviteEmailsToTeamAndChannels(team *model.Team,
return r0, r1 return r0, r1
} }
// SendLicenseInactivityEmail provides a mock function with given fields: _a0, name, locale, siteURL
func (_m *ServiceInterface) SendLicenseInactivityEmail(_a0 string, name string, locale string, siteURL string) error {
ret := _m.Called(_a0, name, locale, siteURL)
var r0 error
if rf, ok := ret.Get(0).(func(string, string, string, string) error); ok {
r0 = rf(_a0, name, locale, siteURL)
} else {
r0 = ret.Error(0)
}
return r0
}
// SendLicenseUpForRenewalEmail provides a mock function with given fields: _a0, name, locale, siteURL, ctaTitle, ctaLink, ctaText, daysToExpiration // SendLicenseUpForRenewalEmail provides a mock function with given fields: _a0, name, locale, siteURL, ctaTitle, ctaLink, ctaText, daysToExpiration
func (_m *ServiceInterface) SendLicenseUpForRenewalEmail(_a0 string, name string, locale string, siteURL string, ctaTitle string, ctaLink string, ctaText string, daysToExpiration int) error { func (_m *ServiceInterface) SendLicenseUpForRenewalEmail(_a0 string, name string, locale string, siteURL string, ctaTitle string, ctaLink string, ctaText string, daysToExpiration int) error {
ret := _m.Called(_a0, name, locale, siteURL, ctaTitle, ctaLink, ctaText, daysToExpiration) ret := _m.Called(_a0, name, locale, siteURL, ctaTitle, ctaLink, ctaText, daysToExpiration)

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

@@ -163,7 +163,6 @@ type ServiceInterface interface {
InitEmailBatching() InitEmailBatching()
SendChangeUsernameEmail(newUsername, email, locale, siteURL string) error SendChangeUsernameEmail(newUsername, email, locale, siteURL string) error
CreateVerifyEmailToken(userID string, newEmail string) (*model.Token, error) CreateVerifyEmailToken(userID string, newEmail string) (*model.Token, error)
SendLicenseInactivityEmail(email, name, locale, siteURL string) error
Stop() Stop()
} }

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

@@ -484,7 +484,6 @@ func NewServer(options ...Option) (*Server, error) {
s.Go(func() { s.Go(func() {
appInstance := New(ServerConnector(s.Channels())) appInstance := New(ServerConnector(s.Channels()))
s.runLicenseExpirationCheckJob() s.runLicenseExpirationCheckJob()
s.runInactivityCheckJob()
runDNDStatusExpireJob(appInstance) runDNDStatusExpireJob(appInstance)
runPostReminderJob(appInstance) runPostReminderJob(appInstance)
}) })
@@ -1198,12 +1197,6 @@ func runConfigCleanupJob(s *Server) {
}, time.Hour*24) }, time.Hour*24)
} }
func (s *Server) runInactivityCheckJob() {
model.CreateRecurringTask("Server inactivity Check", func() {
s.doInactivityCheck()
}, time.Hour*24)
}
func (s *Server) runLicenseExpirationCheckJob() { func (s *Server) runLicenseExpirationCheckJob() {
s.doLicenseExpirationCheck() s.doLicenseExpirationCheck()
model.CreateRecurringTask("License Expiration Check", func() { model.CreateRecurringTask("License Expiration Check", func() {

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

@@ -1,119 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"os"
"strconv"
"time"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
)
const serverInactivityHours = 100
const inactivityEmailSent = "INACTIVITY"
func (s *Server) doInactivityCheck() {
if *s.platform.Config().ServiceSettings.EnableDeveloper {
mlog.Info("No activity check because developer mode is enabled")
return
}
if !*s.platform.Config().EmailSettings.EnableInactivityEmail {
mlog.Info("No activity check because EnableInactivityEmail is false")
return
}
if !s.platform.Config().FeatureFlags.EnableInactivityCheckJob {
mlog.Info("No activity check because EnableInactivityCheckJob feature flag is disabled")
return
}
_, sysValErr := s.Store().System().GetByName(inactivityEmailSent)
// if there is no error which may include *store.ErrNotFound, it means this check was already flagged as done
if sysValErr == nil {
return
}
inactivityDurationHoursEnv := os.Getenv("MM_INACTIVITY_DURATION")
inactivityDurationHours, parseError := strconv.ParseFloat(inactivityDurationHoursEnv, 64)
if parseError != nil {
// default to 100 hours
inactivityDurationHours = serverInactivityHours
}
// The first time this job runs. We check if the user has not made any posts in last inactivityDurationHours
// and remind them to use the workspace. If no posts have been made. We check the last time
// they logged in (session) for the last inactivityDurationHours and send a reminder.
lastPostAt, _ := s.Store().Post().GetLastPostRowCreateAt()
if lastPostAt != 0 {
posT := time.Unix(lastPostAt/1000, 0)
timeForLastPost := time.Since(posT).Hours()
if timeForLastPost > inactivityDurationHours {
s.takeInactivityAction()
}
return
}
lastSessionAt, _ := s.Store().Session().GetLastSessionRowCreateAt()
if lastSessionAt != 0 {
sesT := time.Unix(lastSessionAt/1000, 0)
timeForLastSession := time.Since(sesT).Hours()
if timeForLastSession > inactivityDurationHours {
s.takeInactivityAction()
}
return
}
}
func (s *Server) takeInactivityAction() {
siteURL := *s.platform.Config().ServiceSettings.SiteURL
if siteURL == "" {
mlog.Warn("No SiteURL configured")
}
properties := map[string]any{
"SiteURL": siteURL,
}
s.GetTelemetryService().SendTelemetry("inactive_server", properties)
users, err := s.Store().User().GetSystemAdminProfiles()
if err != nil {
mlog.Error("Failed to get system admins for inactivity check from Mattermost.")
return
}
for _, user := range users {
// See https://go.dev/doc/faq#closures_and_goroutines for why we make this assignment
user := user
if user.Email == "" {
mlog.Error("Invalid system admin email.", mlog.String("user_email", user.Email))
continue
}
name := user.FirstName
if name == "" {
name = user.Username
}
mlog.Debug("Sending inactivity reminder email.", mlog.String("user_email", user.Email))
s.Go(func() {
if err := s.EmailService.SendLicenseInactivityEmail(user.Email, name, user.Locale, siteURL); err != nil {
mlog.Error("Error while sending inactivity reminder email.", mlog.String("user_email", user.Email), mlog.Err(err))
}
})
}
// Mark that we sent emails.
sysVar := &model.System{Name: inactivityEmailSent, Value: "true"}
if err := s.Store().System().SaveOrUpdate(sysVar); err != nil {
mlog.Error("Unable to save INACTIVITY", mlog.Err(err))
}
// do some telemetry about sending the email
s.GetTelemetryService().SendTelemetry("inactive_server_emails_sent", properties)
}

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

@@ -6057,24 +6057,6 @@ func (s *OpenTracingLayerPostStore) GetFlaggedPostsForTeam(userID string, teamID
return result, err return result, err
} }
func (s *OpenTracingLayerPostStore) GetLastPostRowCreateAt() (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetLastPostRowCreateAt")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.PostStore.GetLastPostRowCreateAt()
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerPostStore) GetMaxPostSize() int { func (s *OpenTracingLayerPostStore) GetMaxPostSize() int {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetMaxPostSize") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostStore.GetMaxPostSize")
@@ -8171,24 +8153,6 @@ func (s *OpenTracingLayerSessionStore) Get(ctx context.Context, sessionIDOrToken
return result, err return result, err
} }
func (s *OpenTracingLayerSessionStore) GetLastSessionRowCreateAt() (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.GetLastSessionRowCreateAt")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.SessionStore.GetLastSessionRowCreateAt()
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerSessionStore) GetSessions(userID string) ([]*model.Session, error) { func (s *OpenTracingLayerSessionStore) GetSessions(userID string) ([]*model.Session, error) {
origCtx := s.Root.Store.Context() origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.GetSessions") span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SessionStore.GetSessions")

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

@@ -6856,27 +6856,6 @@ func (s *RetryLayerPostStore) GetFlaggedPostsForTeam(userID string, teamID strin
} }
func (s *RetryLayerPostStore) GetLastPostRowCreateAt() (int64, error) {
tries := 0
for {
result, err := s.PostStore.GetLastPostRowCreateAt()
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 *RetryLayerPostStore) GetMaxPostSize() int { func (s *RetryLayerPostStore) GetMaxPostSize() int {
return s.PostStore.GetMaxPostSize() return s.PostStore.GetMaxPostSize()
@@ -9304,27 +9283,6 @@ func (s *RetryLayerSessionStore) Get(ctx context.Context, sessionIDOrToken strin
} }
func (s *RetryLayerSessionStore) GetLastSessionRowCreateAt() (int64, error) {
tries := 0
for {
result, err := s.SessionStore.GetLastSessionRowCreateAt()
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 *RetryLayerSessionStore) GetSessions(userID string) ([]*model.Session, error) { func (s *RetryLayerSessionStore) GetSessions(userID string) ([]*model.Session, error) {
tries := 0 tries := 0

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

@@ -2300,17 +2300,6 @@ func (s *SqlPostStore) AnalyticsPostCount(options *model.PostCountOptions) (int6
return v, nil return v, nil
} }
func (s *SqlPostStore) GetLastPostRowCreateAt() (int64, error) {
query := `SELECT CREATEAT FROM Posts ORDER BY CREATEAT DESC LIMIT 1`
var createAt int64
err := s.GetReplicaX().Get(&createAt, query)
if err != nil {
return 0, errors.Wrapf(err, "failed to get last post createat")
}
return createAt, nil
}
func (s *SqlPostStore) GetPostsCreatedAt(channelId string, time int64) ([]*model.Post, error) { func (s *SqlPostStore) GetPostsCreatedAt(channelId string, time int64) ([]*model.Post, error) {
query := `SELECT * FROM Posts WHERE CreateAt = ? AND ChannelId = ?` query := `SELECT * FROM Posts WHERE CreateAt = ? AND ChannelId = ?`

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

@@ -221,17 +221,6 @@ func (me SqlSessionStore) UpdateExpiresAt(sessionId string, time int64) error {
return nil return nil
} }
func (me *SqlSessionStore) GetLastSessionRowCreateAt() (int64, error) {
query := `SELECT CREATEAT FROM Sessions ORDER BY CREATEAT DESC LIMIT 1`
var createAt int64
err := me.GetReplicaX().Get(&createAt, query)
if err != nil {
return 0, errors.Wrapf(err, "failed to get last session createat")
}
return createAt, nil
}
func (me SqlSessionStore) UpdateLastActivityAt(sessionId string, time int64) error { func (me SqlSessionStore) UpdateLastActivityAt(sessionId string, time int64) error {
_, err := me.GetMasterX().Exec("UPDATE Sessions SET LastActivityAt = ? WHERE Id = ?", time, sessionId) _, err := me.GetMasterX().Exec("UPDATE Sessions SET LastActivityAt = ? WHERE Id = ?", time, sessionId)
if err != nil { if err != nil {

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

@@ -381,7 +381,6 @@ type PostStore interface {
AnalyticsPostCount(options *model.PostCountOptions) (int64, error) AnalyticsPostCount(options *model.PostCountOptions) (int64, error)
ClearCaches() ClearCaches()
InvalidateLastPostTimeCache(channelID string) InvalidateLastPostTimeCache(channelID string)
GetLastPostRowCreateAt() (int64, error)
GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error) GetPostsCreatedAt(channelID string, timestamp int64) ([]*model.Post, error)
Overwrite(post *model.Post) (*model.Post, error) Overwrite(post *model.Post) (*model.Post, error)
OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error) OverwriteMultiple(posts []*model.Post) ([]*model.Post, int, error)
@@ -510,7 +509,6 @@ type SessionStore interface {
Remove(sessionIDOrToken string) error Remove(sessionIDOrToken string) error
RemoveAllSessions() error RemoveAllSessions() error
PermanentDeleteSessionsByUser(teamID string) error PermanentDeleteSessionsByUser(teamID string) error
GetLastSessionRowCreateAt() (int64, error)
UpdateExpiresAt(sessionID string, timestamp int64) error UpdateExpiresAt(sessionID string, timestamp int64) error
UpdateLastActivityAt(sessionID string, timestamp int64) error UpdateLastActivityAt(sessionID string, timestamp int64) error
UpdateRoles(userID string, roles string) (string, error) UpdateRoles(userID string, roles string) (string, error)

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

@@ -277,27 +277,6 @@ func (_m *PostStore) GetFlaggedPostsForTeam(userID string, teamID string, offset
return r0, r1 return r0, r1
} }
// GetLastPostRowCreateAt provides a mock function with given fields:
func (_m *PostStore) GetLastPostRowCreateAt() (int64, error) {
ret := _m.Called()
var r0 int64
if rf, ok := ret.Get(0).(func() int64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMaxPostSize provides a mock function with given fields: // GetMaxPostSize provides a mock function with given fields:
func (_m *PostStore) GetMaxPostSize() int { func (_m *PostStore) GetMaxPostSize() int {
ret := _m.Called() ret := _m.Called()

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

@@ -74,27 +74,6 @@ func (_m *SessionStore) Get(ctx context.Context, sessionIDOrToken string) (*mode
return r0, r1 return r0, r1
} }
// GetLastSessionRowCreateAt provides a mock function with given fields:
func (_m *SessionStore) GetLastSessionRowCreateAt() (int64, error) {
ret := _m.Called()
var r0 int64
if rf, ok := ret.Get(0).(func() int64); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetSessions provides a mock function with given fields: userID // GetSessions provides a mock function with given fields: userID
func (_m *SessionStore) GetSessions(userID string) ([]*model.Session, error) { func (_m *SessionStore) GetSessions(userID string) ([]*model.Session, error) {
ret := _m.Called(userID) ret := _m.Called(userID)

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

@@ -43,7 +43,6 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, ss) }) t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, ss) })
t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, ss) }) t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, ss) })
t.Run("GetPostsCreatedAt", func(t *testing.T) { testPostStoreGetPostsCreatedAt(t, ss) }) t.Run("GetPostsCreatedAt", func(t *testing.T) { testPostStoreGetPostsCreatedAt(t, ss) })
t.Run("GetLastPostRowCreateAt", func(t *testing.T) { testPostStoreGetLastPostRowCreateAt(t, ss) })
t.Run("Overwrite", func(t *testing.T) { testPostStoreOverwrite(t, ss) }) t.Run("Overwrite", func(t *testing.T) { testPostStoreOverwrite(t, ss) })
t.Run("OverwriteMultiple", func(t *testing.T) { testPostStoreOverwriteMultiple(t, ss) }) t.Run("OverwriteMultiple", func(t *testing.T) { testPostStoreOverwriteMultiple(t, ss) })
t.Run("GetPostsByIds", func(t *testing.T) { testPostStoreGetPostsByIds(t, ss) }) t.Run("GetPostsByIds", func(t *testing.T) { testPostStoreGetPostsByIds(t, ss) })
@@ -3361,40 +3360,6 @@ func testPostStoreGetFlaggedPostsForChannel(t *testing.T, ss store.Store) {
require.Len(t, r.Order, 0, "should have 0 posts") require.Len(t, r.Order, 0, "should have 0 posts")
} }
func testPostStoreGetLastPostRowCreateAt(t *testing.T, ss store.Store) {
teamId := model.NewId()
channel1, err := ss.Channel().Save(&model.Channel{
TeamId: teamId,
DisplayName: "DisplayName1",
Name: "channel" + model.NewId(),
Type: model.ChannelTypeOpen,
}, -1)
require.NoError(t, err)
createTime1 := model.GetMillis() + 1
o0 := &model.Post{}
o0.ChannelId = channel1.Id
o0.UserId = model.NewId()
o0.Message = NewTestId()
o0.CreateAt = createTime1
o0, err = ss.Post().Save(o0)
require.NoError(t, err)
createTime2 := model.GetMillis() + 2
o1 := &model.Post{}
o1.ChannelId = o0.ChannelId
o1.UserId = model.NewId()
o1.Message = "Latest message"
o1.CreateAt = createTime2
_, err = ss.Post().Save(o1)
require.NoError(t, err)
createAt, err := ss.Post().GetLastPostRowCreateAt()
require.NoError(t, err)
assert.Equal(t, createAt, createTime2)
}
func testPostStoreGetPostsCreatedAt(t *testing.T, ss store.Store) { func testPostStoreGetPostsCreatedAt(t *testing.T, ss store.Store) {
teamId := model.NewId() teamId := model.NewId()
channel1, err := ss.Channel().Save(&model.Channel{ channel1, err := ss.Channel().Save(&model.Channel{

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

@@ -33,7 +33,6 @@ func TestSessionStore(t *testing.T, ss store.Store) {
t.Run("SessionUpdateDeviceId2", func(t *testing.T) { testSessionUpdateDeviceId2(t, ss) }) t.Run("SessionUpdateDeviceId2", func(t *testing.T) { testSessionUpdateDeviceId2(t, ss) })
t.Run("UpdateExpiresAt", func(t *testing.T) { testSessionStoreUpdateExpiresAt(t, ss) }) t.Run("UpdateExpiresAt", func(t *testing.T) { testSessionStoreUpdateExpiresAt(t, ss) })
t.Run("UpdateLastActivityAt", func(t *testing.T) { testSessionStoreUpdateLastActivityAt(t, ss) }) t.Run("UpdateLastActivityAt", func(t *testing.T) { testSessionStoreUpdateLastActivityAt(t, ss) })
t.Run("GetLastSessionRowCreateAt", func(t *testing.T) { testSessionStoreGetLastSessionRowCreateAt(t, ss) })
t.Run("SessionCount", func(t *testing.T) { testSessionCount(t, ss) }) t.Run("SessionCount", func(t *testing.T) { testSessionCount(t, ss) })
t.Run("GetSessionsExpired", func(t *testing.T) { testGetSessionsExpired(t, ss) }) t.Run("GetSessionsExpired", func(t *testing.T) { testGetSessionsExpired(t, ss) })
t.Run("UpdateExpiredNotify", func(t *testing.T) { testUpdateExpiredNotify(t, ss) }) t.Run("UpdateExpiredNotify", func(t *testing.T) { testUpdateExpiredNotify(t, ss) })
@@ -47,23 +46,6 @@ func testSessionStoreSave(t *testing.T, ss store.Store) {
require.NoError(t, err) require.NoError(t, err)
} }
func testSessionStoreGetLastSessionRowCreateAt(t *testing.T, ss store.Store) {
s1 := &model.Session{}
s1.UserId = model.NewId()
_, err := ss.Session().Save(s1)
require.NoError(t, err)
latestSessionUserid := model.NewId()
s2 := &model.Session{}
s2.UserId = latestSessionUserid
latestSession, err := ss.Session().Save(s2)
require.NoError(t, err)
createAt, err := ss.Session().GetLastSessionRowCreateAt()
require.NoError(t, err)
assert.Equal(t, latestSession.CreateAt, createAt)
}
func testSessionGet(t *testing.T, ss store.Store) { func testSessionGet(t *testing.T, ss store.Store) {
s1 := &model.Session{} s1 := &model.Session{}
s1.UserId = model.NewId() s1.UserId = model.NewId()

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

@@ -5483,22 +5483,6 @@ func (s *TimerLayerPostStore) GetFlaggedPostsForTeam(userID string, teamID strin
return result, err return result, err
} }
func (s *TimerLayerPostStore) GetLastPostRowCreateAt() (int64, error) {
start := time.Now()
result, err := s.PostStore.GetLastPostRowCreateAt()
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostStore.GetLastPostRowCreateAt", success, elapsed)
}
return result, err
}
func (s *TimerLayerPostStore) GetMaxPostSize() int { func (s *TimerLayerPostStore) GetMaxPostSize() int {
start := time.Now() start := time.Now()
@@ -7370,22 +7354,6 @@ func (s *TimerLayerSessionStore) Get(ctx context.Context, sessionIDOrToken strin
return result, err return result, err
} }
func (s *TimerLayerSessionStore) GetLastSessionRowCreateAt() (int64, error) {
start := time.Now()
result, err := s.SessionStore.GetLastSessionRowCreateAt()
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("SessionStore.GetLastSessionRowCreateAt", success, elapsed)
}
return result, err
}
func (s *TimerLayerSessionStore) GetSessions(userID string) ([]*model.Session, error) { func (s *TimerLayerSessionStore) GetSessions(userID string) ([]*model.Session, error) {
start := time.Now() start := time.Now()

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

@@ -7,14 +7,6 @@
"id": "August", "id": "August",
"translation": "August" "translation": "August"
}, },
{
"id": "Boards",
"translation": "Boards"
},
{
"id": "Channels",
"translation": "Channels"
},
{ {
"id": "December", "id": "December",
"translation": "December" "translation": "December"
@@ -51,10 +43,6 @@
"id": "October", "id": "October",
"translation": "October" "translation": "October"
}, },
{
"id": "Playbooks",
"translation": "Playbooks"
},
{ {
"id": "September", "id": "September",
"translation": "September" "translation": "September"
@@ -3763,42 +3751,6 @@
"id": "api.templates.reset_subject", "id": "api.templates.reset_subject",
"translation": "[{{ .SiteName }}] Reset your password" "translation": "[{{ .SiteName }}] Reset your password"
}, },
{
"id": "api.templates.server_inactivity_button",
"translation": "Open Mattermost"
},
{
"id": "api.templates.server_inactivity_footer_disclaimer",
"translation": "You received this one-time email because your Mattermost server was inactive for more than {{.Hours}} hours. This email was automatically generated by your Mattermost server."
},
{
"id": "api.templates.server_inactivity_info",
"translation": "Come and check it out!"
},
{
"id": "api.templates.server_inactivity_info_bullet",
"translation": "Guest Access to specified "
},
{
"id": "api.templates.server_inactivity_info_bullet1",
"translation": "Workflow management with "
},
{
"id": "api.templates.server_inactivity_info_bullet2",
"translation": "Manage tasks using "
},
{
"id": "api.templates.server_inactivity_subject",
"translation": "Come open Mattermost to increase your teams productivity!"
},
{
"id": "api.templates.server_inactivity_subtitle",
"translation": "Hey {{.Name}}, weve noticed that your Mattermost server is collecting a bit of dust. Take a look at some features that can help lighten your team's workload."
},
{
"id": "api.templates.server_inactivity_title",
"translation": "Unlock increased productivity with these awesome features"
},
{ {
"id": "api.templates.signin_change_email.body.info", "id": "api.templates.signin_change_email.body.info",
"translation": "You updated your sign-in method on {{ .SiteName }} to {{.Method}}." "translation": "You updated your sign-in method on {{ .SiteName }} to {{.Method}}."

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

@@ -609,7 +609,6 @@ func (ts *TelemetryService) trackConfig() {
"isdefault_login_button_border_color": isDefault(*cfg.EmailSettings.LoginButtonBorderColor, ""), "isdefault_login_button_border_color": isDefault(*cfg.EmailSettings.LoginButtonBorderColor, ""),
"isdefault_login_button_text_color": isDefault(*cfg.EmailSettings.LoginButtonTextColor, ""), "isdefault_login_button_text_color": isDefault(*cfg.EmailSettings.LoginButtonTextColor, ""),
"smtp_server_timeout": *cfg.EmailSettings.SMTPServerTimeout, "smtp_server_timeout": *cfg.EmailSettings.SMTPServerTimeout,
"enable_inactivity_email": *cfg.EmailSettings.EnableInactivityEmail,
}) })
ts.SendTelemetry(TrackConfigRate, map[string]any{ ts.SendTelemetry(TrackConfigRate, map[string]any{

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

@@ -1,548 +0,0 @@
{{define "inactivity_body"}}
<!-- FILE: inactivity_body.mjml -->
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title>
</title>
<!--[if !mso]><!-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
</style>
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700" rel="stylesheet" type="text/css">
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700);
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
}
</style>
<style media="screen and (min-width:480px)">
.moz-text-html .mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
</style>
<style type="text/css">
@media only screen and (max-width:480px) {
table.mj-full-width-mobile {
width: 100% !important;
}
td.mj-full-width-mobile {
width: auto !important;
}
}
</style>
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,600,700);
.emailBody {
background-color: #F3F3F3
}
.emailBody a {
text-decoration: none !important;
color: #1C58D9;
}
.title div {
font-weight: 600 !important;
font-size: 28px !important;
line-height: 36px !important;
letter-spacing: -0.01em !important;
color: #3F4350 !important;
font-family: Open Sans, sans-serif !important;
}
.subTitle div {
font-size: 16px !important;
line-height: 24px !important;
color: rgba(63, 67, 80, 0.64) !important;
}
.subTitle a {
color: rgb(28, 88, 217) !important;
}
.button a {
background-color: #1C58D9 !important;
font-weight: 600 !important;
font-size: 16px !important;
line-height: 18px !important;
color: #FFFFFF !important;
padding: 15px 24px !important;
}
.button-cloud a {
background-color: #1C58D9 !important;
font-weight: 400 !important;
font-size: 16px !important;
line-height: 18px !important;
color: #FFFFFF !important;
padding: 15px 24px !important;
}
.messageButton a {
background-color: #FFFFFF !important;
border: 1px solid #FFFFFF !important;
box-sizing: border-box !important;
color: #1C58D9 !important;
padding: 12px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.info div {
font-size: 14px !important;
line-height: 20px !important;
color: #3F4350 !important;
padding: 40px 0px !important;
}
.footerTitle div {
font-weight: 600 !important;
font-size: 16px !important;
line-height: 24px !important;
color: #3F4350 !important;
padding: 0px 0px 4px 0px !important;
}
.footerInfo div {
font-size: 14px !important;
line-height: 20px !important;
color: #3F4350 !important;
padding: 0px 48px 0px 48px !important;
}
.footerInfo a {
color: #1C58D9 !important;
}
.appDownloadButton a {
background-color: #FFFFFF !important;
border: 1px solid #1C58D9 !important;
box-sizing: border-box !important;
color: #1C58D9 !important;
padding: 13px 20px !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 14px !important;
}
.emailFooter div {
font-size: 12px !important;
line-height: 16px !important;
color: rgba(63, 67, 80, 0.56) !important;
padding: 8px 24px 8px 24px !important;
}
.postCard {
padding: 0px 24px 40px 24px !important;
}
.messageCard {
background: #FFFFFF !important;
border: 1px solid rgba(61, 60, 64, 0.08) !important;
box-sizing: border-box !important;
box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.12) !important;
border-radius: 4px !important;
padding: 32px !important;
}
.messageAvatar img {
width: 32px !important;
height: 32px !important;
padding: 0px !important;
border-radius: 32px !important;
}
.messageAvatarCol {
width: 32px !important;
}
.postNameAndTime {
padding: 0px 0px 4px 0px !important;
display: flex;
}
.senderName {
font-family: Open Sans, sans-serif;
text-align: left !important;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3F4350 !important;
}
.time {
font-family: Open Sans, sans-serif;
font-size: 12px;
line-height: 16px;
color: rgba(63, 67, 80, 0.56);
padding: 2px 6px;
align-items: center;
float: left;
}
.channelBg {
background: rgba(63, 67, 80, 0.08);
border-radius: 4px;
display: flex;
padding-left: 4px;
}
.channelLogo {
width: 10px;
height: 10px;
padding: 5px 4px 5px 6px;
float: left;
}
.channelName {
font-family: Open Sans, sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 16px;
letter-spacing: 0.01em;
text-transform: uppercase;
color: rgba(63, 67, 80, 0.64);
padding: 2px 6px 2px 0px;
}
.gmChannelCount {
background-color: rgba(63, 67, 80, 0.2);
padding: 0 5px;
border-radius: 2px;
margin-right: 2px;
}
.senderMessage div {
text-align: left !important;
font-size: 14px !important;
line-height: 20px !important;
color: #3F4350 !important;
padding: 0px !important;
}
.senderInfoCol {
width: 394px !important;
padding: 0px 0px 0px 12px !important;
}
@media all and (min-width: 541px) {
.emailBody {
padding: 32px !important;
}
}
@media all and (max-width: 540px) and (min-width: 401px) {
.emailBody {
padding: 16px !important;
}
.messageCard {
padding: 16px !important;
}
.senderInfoCol {
width: 80% !important;
padding: 0px 0px 0px 12px !important;
}
}
@media all and (max-width: 400px) {
.emailBody {
padding: 0px !important;
}
.footerInfo div {
padding: 0px !important;
}
.messageCard {
padding: 16px !important;
}
.postCard {
padding: 0px 0px 40px 0px !important;
}
.senderInfoCol {
width: 80% !important;
padding: 0px 0px 0px 12px !important;
}
}
@media only screen and (min-width:480px) {
.mj-column-per-50 {
width: 100% !important;
max-width: 100% !important;
}
}
</style>
</head>
<body style="word-spacing:normal;">
<div class="emailBody" style="background-color: #F3F3F3;">
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;border-radius:8px;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;border-radius:8px;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:24px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px 0px 40px 0px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:552px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="center" style="font-size:0px;padding:0px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:132px;">
<img alt height="21" src="{{.Props.SiteURL}}/static/images/logo_email_dark.png" style="border:0;display:block;outline:none;text-decoration:none;height:21.76px;width:100%;font-size:13px;" width="132">
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px 24px 40px 24px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:504px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" class="title" style="font-size:0px;padding:10px 25px;padding-bottom:16px;word-break:break-word;">
<div style="text-align: left; font-weight: 600; font-size: 28px; line-height: 36px; letter-spacing: -0.01em; color: #3F4350; font-family: Open Sans, sans-serif;">{{.Props.Title}}</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;padding-bottom:16px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:16px;font-weight:normal;line-height:24px;text-align:left;color:#3F4350;">{{.Props.SubTitle}}</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;padding-bottom:16px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:16px;font-weight:normal;line-height:24px;text-align:left;color:#3F4350;">
<ul>
<li>{{.Props.InfoBullet}}<b>{{.Props.Channels}}</b></li>
<li>{{.Props.InfoBullet1}}<b>{{.Props.Playbooks}}</b></li>
<li>{{.Props.InfoBullet2}}<b>{{.Props.Boards}}</b></li>
</ul>
</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;padding-bottom:16px;word-break:break-word;">
<div style="font-family:Open Sans, sans-serif;font-size:16px;font-weight:normal;line-height:24px;text-align:left;color:#3F4350;">{{.Props.Info}}</div>
</td>
</tr>
<tr>
<td align="center" vertical-align="middle" class="button" style="font-size:0px;padding:0px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="#FFFFFF" role="presentation" style="border:none;border-radius:4px;cursor:auto;mso-padding-alt:10px 25px;background:#FFFFFF;" valign="middle">
<a href="{{.Props.ButtonURL}}" style="display: inline-block; background: #FFFFFF; font-family: Open Sans, sans-serif; margin: 0; text-transform: none; mso-padding-alt: 0px; border-radius: 4px; text-decoration: none; background-color: #1C58D9; font-weight: 600; font-size: 16px; line-height: 18px; color: #FFFFFF; padding: 15px 24px;" target="_blank">
{{.Props.Button}}
</a>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px 24px 40px 24px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:504px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="center" style="font-size:0px;padding:0px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:246px;">
<img alt height="auto" src="{{.Props.SiteURL}}/static/images/invite_illustration.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="246">
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px 24px 40px 40px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:488px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="left" class="footerTitle" style="font-size:0px;padding:24px 0px 0px 0px;word-break:break-word;">
<div style="font-family: Arial; text-align: left; font-weight: 600; font-size: 16px; line-height: 24px; color: #3F4350; padding: 0px 0px 4px 0px;">{{.Props.QuestionTitle}}</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:0px 0px;word-break:break-word;">
<div style="font-family:Arial;font-size:14px;font-weight:normal;line-height:20px;text-align:left;color:#3F4350;">{{.Props.QuestionInfo}}
<a href="mailto:{{.Props.SupportEmail}}" style="color: #1C58D9; text-decoration: none;">
{{.Props.SupportEmail}}
</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr><tr><td class="" width="600px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:552px;" width="552" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
<div style="margin:0px auto;max-width:552px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px 24px 40px 24px;text-align:center;">
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:504px;" ><![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-top:1px solid #E5E5E5;vertical-align:top;" width="100%">
<tbody>
<tr>
<td align="center" class="emailFooter" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family: Arial; text-align: center; font-size: 12px; line-height: 16px; color: rgba(63, 67, 80, 0.56); padding: 8px 24px 8px 24px;">{{.Props.FooterDisclaimer}}</div>
</td>
</tr>
<tr>
<td align="center" class="emailFooter" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family: Arial; text-align: center; font-size: 12px; line-height: 16px; color: rgba(63, 67, 80, 0.56); padding: 8px 24px 8px 24px;">{{.Props.Organization}}
{{.Props.FooterV2}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table></td></tr></table><![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
</div>
</body>
</html>
{{end}}

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

@@ -1,65 +0,0 @@
<mjml>
<mj-head>
<mj-include path="./partials/style.mjml" />
</mj-head>
<mj-body css-class="emailBody">
<mj-wrapper mj-class="email">
<mj-include path="./partials/logo.mjml" />
<mj-section padding="0px 24px 40px 24px">
<mj-column>
<mj-text css-class="title" align="left" color="#3F4350" font-size="16px" font-weight="600" line-height="24px" padding-bottom="16px">
{{.Props.Title}}
</mj-text>
<mj-text align="left" color="#3F4350" font-size="16px" font-weight="normal" line-height="24px" padding-bottom="16px">
{{.Props.SubTitle}}
</mj-text>
<mj-text align="left" color="#3F4350" font-size="16px" font-weight="normal" line-height="24px" padding-bottom="16px">
<ul>
<li>{{.Props.InfoBullet}}<b>{{.Props.Channels}}</b></li>
<li>{{.Props.InfoBullet1}}<b>{{.Props.Playbooks}}</b></li>
<li>{{.Props.InfoBullet2}}<b>{{.Props.Boards}}</b></li>
</ul>
</mj-text>
<mj-text align="left" color="#3F4350" font-size="16px" font-weight="normal" line-height="24px" padding-bottom="16px">
{{.Props.Info}}
</mj-text>
<mj-button href="{{.Props.ButtonURL}}" padding="0px" css-class="button">{{.Props.Button}}</mj-button>
</mj-column>
</mj-section>
<mj-section padding="0px 24px 40px 24px">
<mj-column>
<mj-image src="{{.Props.SiteURL}}/static/images/invite_illustration.png" width="246px" padding="0px" />
</mj-column>
</mj-section>
<mj-section padding="0px 24px 40px 40px">
<mj-column>
<mj-text css-class="footerTitle" color="#3F4350" font-size="16px" font-weight="normal" line-height="24px" padding="24px 0px 0px 0px" align="left" font-family="Arial">
{{.Props.QuestionTitle}}
</mj-text>
<mj-text font-size="14px" line-height="20px" font-weight="normal" color="#3F4350" padding="0px 0px" align="left" font-family="Arial">
{{.Props.QuestionInfo}}
<a href='mailto:{{.Props.SupportEmail}}'>
{{.Props.SupportEmail}}
</a>
</mj-text>
</mj-column>
</mj-section>
<mj-section padding="0px 24px 40px 24px">
<mj-column border-top="1px solid #E5E5E5">
<mj-text css-class="emailFooter" font-family="Arial" font-size="12px" line-height="16px" color="#3F4350">
{{.Props.FooterDisclaimer}}
</mj-text>
<mj-text css-class="emailFooter" font-family="Arial" font-size="12px" line-height="16px" color="#3F4350">
{{.Props.Organization}}
{{.Props.FooterV2}}
</mj-text>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
</mjml>

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

@@ -167,7 +167,6 @@
"LoginButtonColor": "", "LoginButtonColor": "",
"LoginButtonBorderColor": "", "LoginButtonBorderColor": "",
"LoginButtonTextColor": "", "LoginButtonTextColor": "",
"EnableInactivityEmail": true
}, },
"RateLimitSettings": { "RateLimitSettings": {
"Enable": false, "Enable": false,