[MM-40917] - Inactive Server Email Notification (#19374)
* [MM-40917] - Inactive Server Email Notification * add email template * make store layers * add some store tests * fix translations * fix logic * improve * fix lint * feedback-impl * fix wrong text * optimize queries * move feature flag check * feedback impl-1 * add line * feedback impl Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
1b1ba687bb
Коммит
8d6d1c51c2
@@ -762,6 +762,39 @@ func (es *Service) SendAtUserLimitWarningEmail(email string, locale string, site
|
||||
return true, 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]interface{}{"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")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("inactivity_body", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := es.sendMail(email, subject, body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *Service) SendLicenseUpForRenewalEmail(email, name, locale, siteURL, renewalLink string, daysToExpiration int) error {
|
||||
T := i18n.GetUserTranslations(locale)
|
||||
subject := T("api.templates.license_up_for_renewal_subject")
|
||||
@@ -775,6 +808,7 @@ func (es *Service) SendLicenseUpForRenewalEmail(email, name, locale, siteURL, re
|
||||
data.Props["Button"] = T("api.templates.license_up_for_renewal_renew_now")
|
||||
data.Props["ButtonURL"] = renewalLink
|
||||
data.Props["QuestionTitle"] = T("api.templates.questions_footer.title")
|
||||
data.Props["SupportEmail"] = "feedback@mattermost.com"
|
||||
data.Props["QuestionInfo"] = T("api.templates.questions_footer.info")
|
||||
|
||||
body, err := es.templatesContainer.RenderToString("license_up_for_renewal", data)
|
||||
|
||||
@@ -633,6 +633,7 @@ func NewServer(options ...Option) (*Server, error) {
|
||||
s.Go(func() {
|
||||
appInstance := New(ServerConnector(s.Channels()))
|
||||
s.runLicenseExpirationCheckJob()
|
||||
s.runInactivityCheckJob()
|
||||
runDNDStatusExpireJob(appInstance)
|
||||
})
|
||||
s.runJobs()
|
||||
@@ -1478,6 +1479,12 @@ func runJobsCleanupJob(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() {
|
||||
|
||||
141
app/server_inactivity.go
Обычный файл
141
app/server_inactivity.go
Обычный файл
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
)
|
||||
|
||||
const serverInactivityHours = 100
|
||||
|
||||
func (s *Server) doInactivityCheck() {
|
||||
if !s.Config().FeatureFlags.EnableInactivityCheckJob {
|
||||
mlog.Info("No activity check because EnableInactivityCheckJob feature flag is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
inactivityDurationHourseEnv := os.Getenv("MM_INACTIVITY_DURATION")
|
||||
inactivityDurationHours, parseError := strconv.ParseFloat(inactivityDurationHourseEnv, 64)
|
||||
if parseError != nil {
|
||||
// default to 100 hours
|
||||
inactivityDurationHours = serverInactivityHours
|
||||
}
|
||||
|
||||
systemValue, sysValErr := s.Store.System().GetByName("INACTIVITY")
|
||||
if sysValErr != nil {
|
||||
// any other error apart from ErrNotFound we stop execution
|
||||
if _, ok := sysValErr.(*store.ErrNotFound); !ok {
|
||||
mlog.Warn("An error occurred while getting INACTIVITY from system store", mlog.Err(sysValErr))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a system value, it means this job already ran atleast once.
|
||||
// we then check the last time the job ran plus the last time a post was made to determine if we
|
||||
// can remind the user to use workspace again. If no post was made, we check the last time they logged in (session)
|
||||
// and determine whether to send them a reminder.
|
||||
if systemValue != nil {
|
||||
sysT, _ := strconv.ParseInt(systemValue.Value, 10, 64)
|
||||
tt := time.Unix(sysT/1000, 0)
|
||||
timeLastSentInativityEmail := time.Since(tt).Hours()
|
||||
|
||||
lastPostAt, _ := s.Store.Post().GetLastPostRowCreateAt()
|
||||
if lastPostAt != 0 {
|
||||
posT := time.Unix(lastPostAt/1000, 0)
|
||||
timeForLastPost := time.Since(posT).Hours()
|
||||
|
||||
if timeLastSentInativityEmail > inactivityDurationHours && 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 timeLastSentInativityEmail > inactivityDurationHours && timeForLastSession > inactivityDurationHours {
|
||||
s.takeInactivityAction()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The first time this job runs. We check if the user has not made any posts
|
||||
// and remind them to use the workspace. If no posts have been made. We check the last time
|
||||
// they logged in (session) 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.Config().ServiceSettings.SiteURL
|
||||
if siteURL == "" {
|
||||
mlog.Warn("No SiteURL configured")
|
||||
}
|
||||
|
||||
properties := map[string]interface{}{
|
||||
"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 {
|
||||
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 time that we sent emails. The next time we calculate
|
||||
sysVar := &model.System{Name: "INACTIVITY", Value: fmt.Sprint(model.GetMillis())}
|
||||
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)
|
||||
}
|
||||
Ссылка в новой задаче
Block a user