[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 удалений

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

@@ -11,8 +11,6 @@ import (
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"github.com/pkg/errors"
@@ -26,8 +24,6 @@ import (
"github.com/microcosm-cc/bluemonday"
)
const serverInactivityHours = 100
// Returns category if enabled is true (default false)
// If "" is returned when enabled is false, the category headers aren't attached to the email
func getSendGridCategory(category string, enabled bool) string {
@@ -948,48 +944,6 @@ func (es *Service) CreateVerifyEmailToken(userID string, newEmail string) (*mode
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 {
T := i18n.GetUserTranslations(locale)
subject := T("api.templates.license_up_for_renewal_subject")

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

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

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

@@ -344,20 +344,6 @@ func (_m *ServiceInterface) SendInviteEmailsToTeamAndChannels(team *model.Team,
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
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)

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

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

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

@@ -484,7 +484,6 @@ func NewServer(options ...Option) (*Server, error) {
s.Go(func() {
appInstance := New(ServerConnector(s.Channels()))
s.runLicenseExpirationCheckJob()
s.runInactivityCheckJob()
runDNDStatusExpireJob(appInstance)
runPostReminderJob(appInstance)
})
@@ -1198,12 +1197,6 @@ func runConfigCleanupJob(s *Server) {
}, time.Hour*24)
}
func (s *Server) runInactivityCheckJob() {
model.CreateRecurringTask("Server inactivity Check", func() {
s.doInactivityCheck()
}, time.Hour*24)
}
func (s *Server) runLicenseExpirationCheckJob() {
s.doLicenseExpirationCheck()
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
}
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 {
origCtx := s.Root.Store.Context()
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
}
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) {
origCtx := s.Root.Store.Context()
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 {
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) {
tries := 0

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

@@ -2300,17 +2300,6 @@ func (s *SqlPostStore) AnalyticsPostCount(options *model.PostCountOptions) (int6
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) {
query := `SELECT * FROM Posts WHERE CreateAt = ? AND ChannelId = ?`

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

@@ -221,17 +221,6 @@ func (me SqlSessionStore) UpdateExpiresAt(sessionId string, time int64) error {
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 {
_, err := me.GetMasterX().Exec("UPDATE Sessions SET LastActivityAt = ? WHERE Id = ?", time, sessionId)
if err != nil {

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

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

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

@@ -277,27 +277,6 @@ func (_m *PostStore) GetFlaggedPostsForTeam(userID string, teamID string, offset
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:
func (_m *PostStore) GetMaxPostSize() int {
ret := _m.Called()

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

@@ -74,27 +74,6 @@ func (_m *SessionStore) Get(ctx context.Context, sessionIDOrToken string) (*mode
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
func (_m *SessionStore) GetSessions(userID string) ([]*model.Session, error) {
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("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(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("OverwriteMultiple", func(t *testing.T) { testPostStoreOverwriteMultiple(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")
}
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) {
teamId := model.NewId()
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("UpdateExpiresAt", func(t *testing.T) { testSessionStoreUpdateExpiresAt(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("GetSessionsExpired", func(t *testing.T) { testGetSessionsExpired(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)
}
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) {
s1 := &model.Session{}
s1.UserId = model.NewId()

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

@@ -5483,22 +5483,6 @@ func (s *TimerLayerPostStore) GetFlaggedPostsForTeam(userID string, teamID strin
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 {
start := time.Now()
@@ -7370,22 +7354,6 @@ func (s *TimerLayerSessionStore) Get(ctx context.Context, sessionIDOrToken strin
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) {
start := time.Now()

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

@@ -7,14 +7,6 @@
"id": "August",
"translation": "August"
},
{
"id": "Boards",
"translation": "Boards"
},
{
"id": "Channels",
"translation": "Channels"
},
{
"id": "December",
"translation": "December"
@@ -51,10 +43,6 @@
"id": "October",
"translation": "October"
},
{
"id": "Playbooks",
"translation": "Playbooks"
},
{
"id": "September",
"translation": "September"
@@ -3763,42 +3751,6 @@
"id": "api.templates.reset_subject",
"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",
"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_text_color": isDefault(*cfg.EmailSettings.LoginButtonTextColor, ""),
"smtp_server_timeout": *cfg.EmailSettings.SMTPServerTimeout,
"enable_inactivity_email": *cfg.EmailSettings.EnableInactivityEmail,
})
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": "",
"LoginButtonBorderColor": "",
"LoginButtonTextColor": "",
"EnableInactivityEmail": true
},
"RateLimitSettings": {
"Enable": false,