* Deprecate admin advisor

* Webapp portion

* More webapp deprecation

* More cleanup

* Linting

* emoved metric ack dialog from annoucenemet bar

* Cleanued up uninsed i18n strings

* Updated test

* fixed types

* Updating server test

* Updated i18n

* Updated cypress test:

* Updated cypress test:

---------

Co-authored-by: harshil Sharma <harshilsharma63@gmail.com>
Этот коммит содержится в:
Maria A Nunez
2024-02-25 22:35:00 -05:00
коммит произвёл GitHub
родитель dc8fc773dc
Коммит e9b9d4ff60
42 изменённых файлов: 45 добавлений и 2285 удалений

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

@@ -66,9 +66,6 @@ func (api *API) InitSystem() {
api.BaseRoutes.APIRoot.Handle("/upgrade_to_enterprise", api.APISessionRequired(upgradeToEnterprise)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/upgrade_to_enterprise/status", api.APISessionRequired(upgradeToEnterpriseStatus)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/restart", api.APISessionRequired(restart)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/warn_metrics/status", api.APISessionRequired(getWarnMetricsStatus)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/warn_metrics/ack/{warn_metric_id:[A-Za-z0-9-_]+}", api.APIHandler(sendWarnMetricAckEmail)).Methods("POST")
api.BaseRoutes.APIRoot.Handle("/warn_metrics/trial-license-ack/{warn_metric_id:[A-Za-z0-9-_]+}", api.APIHandler(requestTrialLicenseAndAckWarnMetric)).Methods("POST")
api.BaseRoutes.System.Handle("/notices/{team_id:[A-Za-z0-9]+}", api.APISessionRequired(getProductNotices)).Methods("GET")
api.BaseRoutes.System.Handle("/notices/view", api.APISessionRequired(updateViewedProductNotices)).Methods("PUT")
api.BaseRoutes.System.Handle("/support_packet", api.APISessionRequired(generateSupportPacket)).Methods("GET")
@@ -846,100 +843,6 @@ func restart(c *Context, w http.ResponseWriter, r *http.Request) {
}()
}
func getWarnMetricsStatus(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionToAny(*c.AppContext.Session(), model.SysconsoleReadPermissions) {
c.SetPermissionError(model.SysconsoleReadPermissions...)
return
}
license := c.App.Channels().License()
if license != nil {
c.Logger.Debug("License is present, skip.")
return
}
status, appErr := c.App.GetWarnMetricsStatus(c.AppContext)
if appErr != nil {
c.Err = appErr
return
}
js, err := json.Marshal(status)
if err != nil {
c.Err = model.NewAppError("getWarnMetricsStatus", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
return
}
w.Write(js)
}
func sendWarnMetricAckEmail(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("sendWarnMetricAckEmail", audit.Fail)
defer c.LogAuditRec(auditRec)
c.LogAudit("attempt")
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
license := c.App.Channels().License()
if license != nil {
c.Logger.Debug("License is present, skip.")
return
}
user, appErr := c.App.GetUser(c.AppContext.Session().UserId)
if appErr != nil {
c.Err = appErr
return
}
var ack model.SendWarnMetricAck
if jsonErr := json.NewDecoder(r.Body).Decode(&ack); jsonErr != nil {
c.SetInvalidParamWithErr("ack", jsonErr)
return
}
appErr = c.App.NotifyAndSetWarnMetricAck(c.AppContext, c.Params.WarnMetricId, user, ack.ForceAck, false)
if appErr != nil {
c.Err = appErr
}
auditRec.Success()
ReturnStatusOK(w)
}
func requestTrialLicenseAndAckWarnMetric(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec := c.MakeAuditRecord("requestTrialLicenseAndAckWarnMetric", audit.Fail)
defer c.LogAuditRec(auditRec)
c.LogAudit("attempt")
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
if model.BuildEnterpriseReady != "true" {
c.Logger.Debug("Not Enterprise Edition, skip.")
return
}
license := c.App.Channels().License()
if license != nil {
c.Logger.Debug("License is present, skip.")
return
}
if err := c.App.RequestLicenseAndAckWarnMetric(c.AppContext, c.Params.WarnMetricId, false); err != nil {
c.Err = err
return
}
auditRec.Success()
ReturnStatusOK(w)
}
func getProductNotices(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequireTeamId()
if c.Err != nil {

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

@@ -1,273 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"net/http"
"strings"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/i18n"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/platform/shared/mail"
)
func (a *App) GetWarnMetricsStatus(rctx request.CTX) (map[string]*model.WarnMetricStatus, *model.AppError) {
systemDataList, nErr := a.Srv().Store().System().Get()
if nErr != nil {
return nil, model.NewAppError("GetWarnMetricsStatus", "app.system.get.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
isE0Edition := model.BuildEnterpriseReady == "true" // license == nil was already validated upstream
result := map[string]*model.WarnMetricStatus{}
for key, value := range systemDataList {
if strings.HasPrefix(key, model.WarnMetricStatusStorePrefix) {
if warnMetric, ok := model.WarnMetricsTable[key]; ok {
if !warnMetric.IsBotOnly && (value == model.WarnMetricStatusRunonce || value == model.WarnMetricStatusLimitReached) {
result[key], _ = a.getWarnMetricStatusAndDisplayTextsForId(rctx, key, nil, isE0Edition)
}
}
}
}
return result, nil
}
func (a *App) getWarnMetricStatusAndDisplayTextsForId(rctx request.CTX, warnMetricId string, T i18n.TranslateFunc, isE0Edition bool) (*model.WarnMetricStatus, *model.WarnMetricDisplayTexts) {
var warnMetricStatus *model.WarnMetricStatus
var warnMetricDisplayTexts = &model.WarnMetricDisplayTexts{}
if warnMetric, ok := model.WarnMetricsTable[warnMetricId]; ok {
warnMetricStatus = &model.WarnMetricStatus{
Id: warnMetric.Id,
Limit: warnMetric.Limit,
Acked: false,
}
if T == nil {
rctx.Logger().Debug("No translation function")
return warnMetricStatus, nil
}
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.bot_response.notification_success.message")
switch warnMetricId {
case model.SystemWarnMetricNumberOfTeams5:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_teams_5.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_teams_5.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.number_of_teams_5.start_trial_notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.number_of_teams_5.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_teams_5.notification_body")
}
case model.SystemWarnMetricMfa:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.mfa.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.mfa.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.mfa.start_trial_notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.mfa.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.mfa.notification_body")
}
case model.SystemWarnMetricEmailDomain:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.email_domain.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.email_domain.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.email_domain.start_trial_notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.email_domain.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.email_domain.notification_body")
}
case model.SystemWarnMetricNumberOfChannels50:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_channels_50.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_channels_50.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.number_of_channels_50.start_trial.notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.number_of_channels_50.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_channels_50.notification_body")
}
case model.SystemWarnMetricNumberOfActiveUsers100:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_active_users_100.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_100.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.number_of_active_users_100.start_trial.notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.number_of_active_users_100.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_100.notification_body")
}
case model.SystemWarnMetricNumberOfActiveUsers200:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_active_users_200.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_200.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.number_of_active_users_200.start_trial.notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.number_of_active_users_200.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_200.notification_body")
}
case model.SystemWarnMetricNumberOfActiveUsers300:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_active_users_300.start_trial.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_300.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.number_of_active_users_300.start_trial.notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.number_of_active_users_300.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_300.notification_body")
}
case model.SystemWarnMetricNumberOfActiveUsers500:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_active_users_500.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_500.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.number_of_active_users_500.start_trial.notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.number_of_active_users_500.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_active_users_500.notification_body")
}
case model.SystemWarnMetricNumberOfPosts2m:
warnMetricDisplayTexts.BotTitle = T("api.server.warn_metric.number_of_posts_2M.notification_title")
if isE0Edition {
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_posts_2M.start_trial.notification_body")
warnMetricDisplayTexts.BotSuccessMessage = T("api.server.warn_metric.number_of_posts_2M.start_trial.notification_success.message")
} else {
warnMetricDisplayTexts.EmailBody = T("api.server.warn_metric.number_of_posts_2M.contact_us.email_body")
warnMetricDisplayTexts.BotMessageBody = T("api.server.warn_metric.number_of_posts_2M.notification_body")
}
default:
rctx.Logger().Debug("Invalid metric id", mlog.String("id", warnMetricId))
return nil, nil
}
return warnMetricStatus, warnMetricDisplayTexts
}
return nil, nil
}
func (a *App) NotifyAndSetWarnMetricAck(rctx request.CTX, warnMetricId string, sender *model.User, forceAck bool, isBot bool) *model.AppError {
if warnMetric, ok := model.WarnMetricsTable[warnMetricId]; ok {
data, nErr := a.Srv().Store().System().GetByName(warnMetric.Id)
if nErr == nil && data != nil && data.Value == model.WarnMetricStatusAck {
rctx.Logger().Debug("This metric warning has already been acknowledged", mlog.String("id", warnMetric.Id))
return nil
}
if !forceAck {
if *a.Config().EmailSettings.SMTPServer == "" {
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.missing_server.app_error", nil, i18n.T("api.context.invalid_param.app_error", map[string]any{"Name": "SMTPServer"}), http.StatusInternalServerError)
}
T := i18n.GetUserTranslations(sender.Locale)
data := a.Srv().EmailService.NewEmailTemplateData(sender.Locale)
data.Props["ContactNameHeader"] = T("api.templates.warn_metric_ack.body.contact_name_header")
data.Props["ContactNameValue"] = sender.GetFullName()
data.Props["ContactEmailHeader"] = T("api.templates.warn_metric_ack.body.contact_email_header")
data.Props["ContactEmailValue"] = sender.Email
//same definition as the active users count metric displayed in the SystemConsole Analytics section
registeredUsersCount, cerr := a.Srv().Store().User().Count(model.UserCountOptions{})
if cerr != nil {
rctx.Logger().Warn("Error retrieving the number of registered users", mlog.Err(cerr))
} else {
data.Props["RegisteredUsersHeader"] = T("api.templates.warn_metric_ack.body.registered_users_header")
data.Props["RegisteredUsersValue"] = registeredUsersCount
}
data.Props["SiteURLHeader"] = T("api.templates.warn_metric_ack.body.site_url_header")
data.Props["SiteURL"] = a.GetSiteURL()
data.Props["TelemetryIdHeader"] = T("api.templates.warn_metric_ack.body.diagnostic_id_header")
data.Props["TelemetryIdValue"] = a.TelemetryId()
data.Props["Footer"] = T("api.templates.warn_metric_ack.footer")
warnMetricStatus, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(rctx, warnMetricId, T, false)
if warnMetricStatus == nil {
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.invalid_warn_metric.app_error", nil, "", http.StatusInternalServerError)
}
subject := T("api.templates.warn_metric_ack.subject")
data.Props["Title"] = warnMetricDisplayTexts.EmailBody
mailConfig := a.Srv().MailServiceConfig()
body, err := a.Srv().TemplatesContainer().RenderToString("warn_metric_ack", data)
if err != nil {
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError)
}
if err := mail.SendMailUsingConfig(model.MmSupportAdvisorAddress, subject, body, mailConfig, false, "", "", "", sender.Email, "NotifyAndSetWarnMetricAck"); err != nil {
return model.NewAppError("NotifyAndSetWarnMetricAck", "api.email.send_warn_metric_ack.failure.app_error", map[string]any{"Error": err.Error()}, "", http.StatusInternalServerError)
}
}
if err := a.setWarnMetricsStatusAndNotify(rctx, warnMetric.Id); err != nil {
return err
}
}
return nil
}
func (a *App) setWarnMetricsStatusAndNotify(rctx request.CTX, warnMetricId string) *model.AppError {
// Ack all metric warnings on the server
if err := a.setWarnMetricsStatus(rctx, model.WarnMetricStatusAck); err != nil {
return err
}
// Inform client that this metric warning has been acked
message := model.NewWebSocketEvent(model.WebsocketWarnMetricStatusRemoved, "", "", "", nil, "")
message.Add("warnMetricId", warnMetricId)
a.Publish(message)
return nil
}
func (a *App) setWarnMetricsStatus(rctx request.CTX, status string) *model.AppError {
rctx.Logger().Debug("Set monitoring status for all warn metrics", mlog.String("status", status))
for _, warnMetric := range model.WarnMetricsTable {
if err := a.setWarnMetricsStatusForId(rctx, warnMetric.Id, status); err != nil {
return err
}
}
return nil
}
func (a *App) setWarnMetricsStatusForId(rctx request.CTX, warnMetricId string, status string) *model.AppError {
rctx.Logger().Debug("Store status for warn metric", mlog.String("warnMetricId", warnMetricId), mlog.String("status", status))
if err := a.Srv().Store().System().SaveOrUpdateWithWarnMetricHandling(&model.System{
Name: warnMetricId,
Value: status,
}); err != nil {
return model.NewAppError("setWarnMetricsStatusForId", "app.system.warn_metric.store.app_error", map[string]any{"WarnMetricName": warnMetricId}, "", http.StatusInternalServerError).Wrap(err)
}
return nil
}
func (a *App) RequestLicenseAndAckWarnMetric(c request.CTX, warnMetricId string, isBot bool) *model.AppError {
if *a.Config().ExperimentalSettings.RestrictSystemAdmin {
return model.NewAppError("RequestLicenseAndAckWarnMetric", "api.restricted_system_admin", nil, "", http.StatusForbidden)
}
currentUser, appErr := a.GetUser(c.Session().UserId)
if appErr != nil {
return appErr
}
registeredUsersCount, err := a.Srv().Store().User().Count(model.UserCountOptions{})
if err != nil {
return model.NewAppError("RequestLicenseAndAckWarnMetric", "api.license.request_trial_license.fail_get_user_count.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
if err := a.Channels().RequestTrialLicense(c.Session().UserId, int(registeredUsersCount), true, true); err != nil {
// turn off warn metric warning even in case of StartTrial failure
if nerr := a.setWarnMetricsStatusAndNotify(c, warnMetricId); nerr != nil {
return nerr
}
return err
}
if appErr = a.NotifyAndSetWarnMetricAck(c, warnMetricId, currentUser, true, isBot); appErr != nil {
return appErr
}
return nil
}

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

@@ -883,8 +883,6 @@ type AppIface interface {
GetUsersWithoutTeamPage(options *model.UserGetOptions, asAdmin bool) ([]*model.User, *model.AppError)
GetVerifyEmailToken(token string) (*model.Token, *model.AppError)
GetViewUsersRestrictions(c request.CTX, userID string) (*model.ViewUsersRestrictions, *model.AppError)
GetWarnMetricsBot(rctx request.CTX) (*model.Bot, *model.AppError)
GetWarnMetricsStatus(rctx request.CTX) (map[string]*model.WarnMetricStatus, *model.AppError)
HTTPService() httpservice.HTTPService
HandleCommandResponse(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError)
HandleCommandResponsePost(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.Post, *model.AppError)
@@ -953,7 +951,6 @@ type AppIface interface {
NewPluginAPI(c request.CTX, manifest *model.Manifest) plugin.API
Notification() einterfaces.NotificationInterface
NotificationsLog() *mlog.Logger
NotifyAndSetWarnMetricAck(rctx request.CTX, warnMetricId string, sender *model.User, forceAck bool, isBot bool) *model.AppError
NotifySelfHostedSignupProgress(progress string, userId string)
NotifySharedChannelUserUpdate(user *model.User)
OpenInteractiveDialog(request model.OpenDialogRequest) *model.AppError
@@ -1016,7 +1013,6 @@ type AppIface interface {
RemoveUserFromChannel(c request.CTX, userIDToRemove string, removerUserId string, channel *model.Channel) *model.AppError
RemoveUserFromTeam(c request.CTX, teamID string, userID string, requestorId string) *model.AppError
RemoveUsersFromChannelNotMemberOfTeam(c request.CTX, remover *model.User, channel *model.Channel, team *model.Team) *model.AppError
RequestLicenseAndAckWarnMetric(c request.CTX, warnMetricId string, isBot bool) *model.AppError
ResetPasswordFromToken(c request.CTX, userSuppliedTokenString, newPassword string) *model.AppError
ResetPermissionsSystem() *model.AppError
ResetSamlAuthDataToEmail(includeDeleted bool, dryRun bool, userIDs []string) (numAffected int, appErr *model.AppError)

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

@@ -160,35 +160,6 @@ func (a *App) CreateBot(c request.CTX, bot *model.Bot) (*model.Bot, *model.AppEr
return savedBot, nil
}
func (a *App) GetWarnMetricsBot(rctx request.CTX) (*model.Bot, *model.AppError) {
perPage := 1
userOptions := &model.UserGetOptions{
Page: 0,
PerPage: perPage,
Role: model.SystemAdminRoleId,
Inactive: false,
}
sysAdminList, err := a.GetUsersFromProfiles(userOptions)
if err != nil {
return nil, err
}
if len(sysAdminList) == 0 {
return nil, model.NewAppError("GetWarnMetricsBot", "app.bot.get_warn_metrics_bot.empty_admin_list.app_error", nil, "", http.StatusInternalServerError)
}
T := i18n.GetUserTranslations(sysAdminList[0].Locale)
warnMetricsBot := &model.Bot{
Username: model.BotWarnMetricBotUsername,
DisplayName: T("app.system.warn_metric.bot_displayname"),
Description: "",
OwnerId: sysAdminList[0].Id,
}
return a.getOrCreateBot(rctx, warnMetricsBot)
}
func (a *App) GetSystemBot(rctx request.CTX) (*model.Bot, *model.AppError) {
perPage := 1
userOptions := &model.UserGetOptions{

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

@@ -27,14 +27,12 @@ import (
"net/http"
"net/url"
"path"
"path/filepath"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/i18n"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
@@ -230,14 +228,6 @@ func (a *App) DoPostActionWithCookie(c request.CTX, postID, actionId, userID, se
return "", appErr
}
if strings.HasPrefix(upstreamURL, "/warn_metrics/") {
appErr = a.doLocalWarnMetricsRequest(c, upstreamURL, upstreamRequest)
if appErr != nil {
return "", appErr
}
return "", nil
}
requestJSON, err := json.Marshal(upstreamRequest)
if err != nil {
return "", model.NewAppError("DoPostActionWithCookie", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
@@ -446,92 +436,6 @@ func (ch *Channels) doPluginRequest(c request.CTX, method, rawURL string, values
return resp, nil
}
func (a *App) doLocalWarnMetricsRequest(c request.CTX, rawURL string, upstreamRequest *model.PostActionIntegrationRequest) *model.AppError {
_, err := url.Parse(rawURL)
if err != nil {
return model.NewAppError("doLocalWarnMetricsRequest", "api.post.do_action.action_integration.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
warnMetricId := filepath.Base(rawURL)
if warnMetricId == "" {
return model.NewAppError("doLocalWarnMetricsRequest", "api.post.do_action.action_integration.app_error", nil, "", http.StatusBadRequest)
}
license := a.Srv().License()
if license != nil {
c.Logger().Debug("License is present, skip this call")
return nil
}
user, appErr := a.GetUser(c.Session().UserId)
if appErr != nil {
return appErr
}
botPost := &model.Post{
UserId: upstreamRequest.Context["bot_user_id"].(string),
ChannelId: upstreamRequest.ChannelId,
HasReactions: true,
}
isE0Edition := (model.BuildEnterpriseReady == "true") // license == nil was already validated upstream
_, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(c, warnMetricId, i18n.T, isE0Edition)
botPost.Message = ":white_check_mark: " + warnMetricDisplayTexts.BotSuccessMessage
if isE0Edition {
if appErr = a.RequestLicenseAndAckWarnMetric(c, warnMetricId, true); appErr != nil {
botPost.Message = ":warning: " + i18n.T("api.server.warn_metric.bot_response.start_trial_failure.message")
}
} else {
forceAck := upstreamRequest.Context["force_ack"].(bool)
if appErr = a.NotifyAndSetWarnMetricAck(c, warnMetricId, user, forceAck, true); appErr != nil {
if forceAck {
return appErr
}
mailtoLinkText := a.buildWarnMetricMailtoLink(c, warnMetricId, user)
botPost.Message = ":warning: " + i18n.T("api.server.warn_metric.bot_response.notification_failure.message")
actions := []*model.PostAction{}
actions = append(actions,
&model.PostAction{
Id: "emailUs",
Name: i18n.T("api.server.warn_metric.email_us"),
Type: model.PostActionTypeButton,
Options: []*model.PostActionOptions{
{
Text: "WarnMetricMailtoUrl",
Value: mailtoLinkText,
},
{
Text: "TrackEventId",
Value: warnMetricId,
},
},
Integration: &model.PostActionIntegration{
Context: model.StringInterface{
"bot_user_id": botPost.UserId,
"force_ack": true,
},
URL: fmt.Sprintf("/warn_metrics/ack/%s", model.SystemWarnMetricNumberOfActiveUsers500),
},
},
)
attachments := []*model.SlackAttachment{{
AuthorName: "",
Title: "",
Actions: actions,
Text: i18n.T("api.server.warn_metric.bot_response.notification_failure.body"),
}}
model.ParseSlackAttachment(botPost, attachments)
}
}
if _, err := a.CreatePostAsUser(c, botPost, c.Session().Id, true); err != nil {
return err
}
return nil
}
type MailToLinkContent struct {
MetricId string `json:"metric_id"`
MailRecipient string `json:"mail_recipient"`
@@ -545,43 +449,6 @@ func (mlc *MailToLinkContent) ToJSON() string {
return string(b)
}
func (a *App) buildWarnMetricMailtoLink(rctx request.CTX, warnMetricId string, user *model.User) string {
T := i18n.GetUserTranslations(user.Locale)
_, warnMetricDisplayTexts := a.getWarnMetricStatusAndDisplayTextsForId(rctx, warnMetricId, T, false)
mailBody := warnMetricDisplayTexts.EmailBody
mailBody += T("api.server.warn_metric.bot_response.mailto_contact_header", map[string]any{"Contact": user.GetFullName()})
mailBody += "\r\n"
mailBody += T("api.server.warn_metric.bot_response.mailto_email_header", map[string]any{"Email": user.Email})
mailBody += "\r\n"
registeredUsersCount, err := a.Srv().Store().User().Count(model.UserCountOptions{})
if err != nil {
rctx.Logger().Warn("Error retrieving the number of registered users", mlog.Err(err))
} else {
mailBody += i18n.T("api.server.warn_metric.bot_response.mailto_registered_users_header", map[string]any{"NoRegisteredUsers": registeredUsersCount})
mailBody += "\r\n"
}
mailBody += T("api.server.warn_metric.bot_response.mailto_site_url_header", map[string]any{"SiteUrl": a.GetSiteURL()})
mailBody += "\r\n"
mailBody += T("api.server.warn_metric.bot_response.mailto_diagnostic_id_header", map[string]any{"DiagnosticId": a.TelemetryId()})
mailBody += "\r\n"
mailBody += T("api.server.warn_metric.bot_response.mailto_footer")
mailToLinkContent := &MailToLinkContent{
MetricId: warnMetricId,
MailRecipient: model.MmSupportAdvisorAddress,
MailCC: user.Email,
MailSubject: T("api.server.warn_metric.bot_response.mailto_subject"),
MailBody: mailBody,
}
return mailToLinkContent.ToJSON()
}
func (a *App) DoLocalRequest(c request.CTX, rawURL string, body []byte) (*http.Response, *model.AppError) {
return a.doPluginRequest(c, "POST", rawURL, nil, body)
}

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

@@ -11391,50 +11391,6 @@ func (a *OpenTracingAppLayer) GetViewUsersRestrictions(c request.CTX, userID str
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetWarnMetricsBot(rctx request.CTX) (*model.Bot, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetWarnMetricsBot")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetWarnMetricsBot(rctx)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetWarnMetricsStatus(rctx request.CTX) (map[string]*model.WarnMetricStatus, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetWarnMetricsStatus")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetWarnMetricsStatus(rctx)
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) HandleCommandResponse(c request.CTX, command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.HandleCommandResponse")
@@ -12872,28 +12828,6 @@ func (a *OpenTracingAppLayer) NewPluginAPI(c request.CTX, manifest *model.Manife
return resultVar0
}
func (a *OpenTracingAppLayer) NotifyAndSetWarnMetricAck(rctx request.CTX, warnMetricId string, sender *model.User, forceAck bool, isBot bool) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.NotifyAndSetWarnMetricAck")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0 := a.app.NotifyAndSetWarnMetricAck(rctx, warnMetricId, sender, forceAck, isBot)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
ext.Error.Set(span, true)
}
return resultVar0
}
func (a *OpenTracingAppLayer) NotifySelfHostedSignupProgress(progress string, userId string) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.NotifySelfHostedSignupProgress")
@@ -14486,28 +14420,6 @@ func (a *OpenTracingAppLayer) RenameTeam(team *model.Team, newTeamName string, n
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) RequestLicenseAndAckWarnMetric(c request.CTX, warnMetricId string, isBot bool) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RequestLicenseAndAckWarnMetric")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0 := a.app.RequestLicenseAndAckWarnMetric(c, warnMetricId, isBot)
if resultVar0 != nil {
span.LogFields(spanlog.Error(resultVar0))
ext.Error.Set(span, true)
}
return resultVar0
}
func (a *OpenTracingAppLayer) ResetPasswordFromToken(c request.CTX, userSuppliedTokenString string, newPassword string) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ResetPasswordFromToken")

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

@@ -9448,24 +9448,6 @@ func (s *OpenTracingLayerSystemStore) SaveOrUpdate(system *model.System) error {
return err
}
func (s *OpenTracingLayerSystemStore) SaveOrUpdateWithWarnMetricHandling(system *model.System) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SystemStore.SaveOrUpdateWithWarnMetricHandling")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.SystemStore.SaveOrUpdateWithWarnMetricHandling(system)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerSystemStore) Update(system *model.System) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SystemStore.Update")

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

@@ -10799,27 +10799,6 @@ func (s *RetryLayerSystemStore) SaveOrUpdate(system *model.System) error {
}
func (s *RetryLayerSystemStore) SaveOrUpdateWithWarnMetricHandling(system *model.System) error {
tries := 0
for {
err := s.SystemStore.SaveOrUpdateWithWarnMetricHandling(system)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerSystemStore) Update(system *model.System) error {
tries := 0

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

@@ -6,16 +6,12 @@ package sqlstore
import (
"database/sql"
"fmt"
"strconv"
"strings"
"time"
sq "github.com/mattermost/squirrel"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/channels/utils"
)
type SqlSystemStore struct {
@@ -59,24 +55,6 @@ func (s SqlSystemStore) SaveOrUpdate(system *model.System) error {
return nil
}
func (s SqlSystemStore) SaveOrUpdateWithWarnMetricHandling(system *model.System) error {
if err := s.SaveOrUpdate(system); err != nil {
return err
}
if strings.HasPrefix(system.Name, model.WarnMetricStatusStorePrefix) &&
(system.Value == model.WarnMetricStatusRunonce || system.Value == model.WarnMetricStatusLimitReached) {
if err := s.SaveOrUpdate(&model.System{
Name: model.SystemWarnMetricLastRunTimestampKey,
Value: strconv.FormatInt(utils.MillisFromTime(time.Now()), 10),
}); err != nil {
return errors.Wrapf(err, "failed to save system property with name=%s", model.SystemWarnMetricLastRunTimestampKey)
}
}
return nil
}
func (s SqlSystemStore) Update(system *model.System) error {
query := "UPDATE Systems SET Value=:Value WHERE Name=:Name"
if _, err := s.GetMasterX().NamedExec(query, system); err != nil {

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

@@ -586,7 +586,6 @@ type SystemStore interface {
GetByName(name string) (*model.System, error)
PermanentDeleteByName(name string) (*model.System, error)
InsertIfExists(system *model.System) (*model.System, error)
SaveOrUpdateWithWarnMetricHandling(system *model.System) error
}
type WebhookStore interface {

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

@@ -146,20 +146,6 @@ func (_m *SystemStore) SaveOrUpdate(system *model.System) error {
return r0
}
// SaveOrUpdateWithWarnMetricHandling provides a mock function with given fields: system
func (_m *SystemStore) SaveOrUpdateWithWarnMetricHandling(system *model.System) error {
ret := _m.Called(system)
var r0 error
if rf, ok := ret.Get(0).(func(*model.System) error); ok {
r0 = rf(system)
} else {
r0 = ret.Error(0)
}
return r0
}
// Update provides a mock function with given fields: system
func (_m *SystemStore) Update(system *model.System) error {
ret := _m.Called(system)

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

@@ -22,7 +22,6 @@ func TestSystemStore(t *testing.T, rctx request.CTX, ss store.Store) {
t.Run("InsertIfExists", func(t *testing.T) {
testInsertIfExists(t, rctx, ss)
})
t.Run("SaveOrUpdateWithWarnMetricHandling", func(t *testing.T) { testSystemStoreSaveOrUpdateWithWarnMetricHandling(t, rctx, ss) })
t.Run("GetByNameNoEntries", func(t *testing.T) { testSystemStoreGetByNameNoEntries(t, rctx, ss) })
}
@@ -73,33 +72,6 @@ func testSystemStoreSaveOrUpdate(t *testing.T, rctx request.CTX, ss store.Store)
assert.Equal(t, system.Value, res.Value)
}
func testSystemStoreSaveOrUpdateWithWarnMetricHandling(t *testing.T, rctx request.CTX, ss store.Store) {
system := &model.System{Name: model.NewId(), Value: "value"}
err := ss.System().SaveOrUpdateWithWarnMetricHandling(system)
require.NoError(t, err)
_, err = ss.System().GetByName(model.SystemWarnMetricLastRunTimestampKey)
assert.Error(t, err)
system.Name = "warn_metric_number_of_active_users_100"
system.Value = model.WarnMetricStatusRunonce
err = ss.System().SaveOrUpdateWithWarnMetricHandling(system)
require.NoError(t, err)
val1, nerr := ss.System().GetByName(model.SystemWarnMetricLastRunTimestampKey)
assert.NoError(t, nerr)
system.Name = "warn_metric_number_of_active_users_100"
system.Value = model.WarnMetricStatusAck
err = ss.System().SaveOrUpdateWithWarnMetricHandling(system)
require.NoError(t, err)
val2, nerr := ss.System().GetByName(model.SystemWarnMetricLastRunTimestampKey)
assert.NoError(t, nerr)
assert.Equal(t, val1, val2)
}
func testSystemStoreGetByNameNoEntries(t *testing.T, rctx request.CTX, ss store.Store) {
res, nErr := ss.System().GetByName(model.SystemFirstAdminVisitMarketplace)
_, ok := nErr.(*store.ErrNotFound)

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

@@ -8505,22 +8505,6 @@ func (s *TimerLayerSystemStore) SaveOrUpdate(system *model.System) error {
return err
}
func (s *TimerLayerSystemStore) SaveOrUpdateWithWarnMetricHandling(system *model.System) error {
start := time.Now()
err := s.SystemStore.SaveOrUpdateWithWarnMetricHandling(system)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("SystemStore.SaveOrUpdateWithWarnMetricHandling", success, elapsed)
}
return err
}
func (s *TimerLayerSystemStore) Update(system *model.System) error {
start := time.Now()

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

@@ -87,7 +87,6 @@ type Params struct {
FilterArchived bool
FilterParentTeamPermitted bool
CategoryId string
WarnMetricId string
ExportName string
ExcludePolicyConstrained bool
GroupSource model.GroupSource
@@ -227,7 +226,6 @@ func ParamsFromRequest(r *http.Request) *Params {
params.GroupIDs = query.Get("group_ids")
params.IncludeTotalCount, _ = strconv.ParseBool(query.Get("include_total_count"))
params.IncludeDeleted, _ = strconv.ParseBool(query.Get("include_deleted"))
params.WarnMetricId = props["warn_metric_id"]
params.ExportName = props["export_name"]
params.ExcludePolicyConstrained, _ = strconv.ParseBool(query.Get("exclude_policy_constrained"))

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

@@ -1785,18 +1785,6 @@
"id": "api.elasticsearch.test_elasticsearch_settings_nil.app_error",
"translation": "Elasticsearch settings has unset values."
},
{
"id": "api.email.send_warn_metric_ack.failure.app_error",
"translation": "Failure to send admin acknowledgment email"
},
{
"id": "api.email.send_warn_metric_ack.invalid_warn_metric.app_error",
"translation": "Could not find warn metric."
},
{
"id": "api.email.send_warn_metric_ack.missing_server.app_error",
"translation": "SMTP Server is required"
},
{
"id": "api.email_batching.add_notification_email_to_batch.channel_full.app_error",
"translation": "Email batching job's receiving channel was full. Please increase the EmailBatchingBufferSize."
@@ -2228,10 +2216,6 @@
"id": "api.license.request_trial_license.embargoed",
"translation": "We were unable to process the request due to limitations for embargoed countries. [Learn more in our documentation](https://mattermost.com/pl/limitations-for-embargoed-countries), or reach out to legal@mattermost.com for questions around export limitations."
},
{
"id": "api.license.request_trial_license.fail_get_user_count.app_error",
"translation": "Unable to get a trial license, please try again or contact with support@mattermost.com. Cannot obtain the number of registered users."
},
{
"id": "api.license.true_up_review.create_error",
"translation": "Could not create true up status record"
@@ -2818,234 +2802,6 @@
"id": "api.server.start_server.starting.critical",
"translation": "Error starting server, err:%v"
},
{
"id": "api.server.warn_metric.bot_response.mailto_contact_header",
"translation": "Contact: {{.Contact}}"
},
{
"id": "api.server.warn_metric.bot_response.mailto_diagnostic_id_header",
"translation": "Diagnostic Id: {{.DiagnosticId}}"
},
{
"id": "api.server.warn_metric.bot_response.mailto_email_header",
"translation": "Email: {{.Email}}"
},
{
"id": "api.server.warn_metric.bot_response.mailto_footer",
"translation": "If you have any additional inquiries, please contact support@mattermost.com"
},
{
"id": "api.server.warn_metric.bot_response.mailto_registered_users_header",
"translation": "Total Active Users: {{.NoRegisteredUsers}}"
},
{
"id": "api.server.warn_metric.bot_response.mailto_site_url_header",
"translation": "Site URL: {{.SiteUrl}}"
},
{
"id": "api.server.warn_metric.bot_response.mailto_subject",
"translation": "Mattermost Contact Us request"
},
{
"id": "api.server.warn_metric.bot_response.notification_failure.body",
"translation": "Please email us."
},
{
"id": "api.server.warn_metric.bot_response.notification_failure.message",
"translation": "Message could not be sent."
},
{
"id": "api.server.warn_metric.bot_response.notification_success.message",
"translation": "Thank you for contacting Mattermost. We will follow up with you soon."
},
{
"id": "api.server.warn_metric.bot_response.start_trial_failure.message",
"translation": "Trial license could not be retrieved. Visit https://mattermost.com/trial/ to request a license."
},
{
"id": "api.server.warn_metric.email_domain.contact_us.email_body",
"translation": "Mattermost contact us request. I'm interested in learning more about using Guest Accounts.\r\n"
},
{
"id": "api.server.warn_metric.email_domain.notification_body",
"translation": "Projects often involve people both inside and outside of an organization. With Guest Accounts, you can bring external partners into your Mattermost system and specify who they can work with and what they can see.\r\n\r\n[Learn more about enabling Guest Accounts](https://www.mattermost.com/docs-guest-accounts/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=guest-accounts).\r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.email_domain.notification_title",
"translation": "Creating Guest Accounts"
},
{
"id": "api.server.warn_metric.email_domain.start_trial.notification_body",
"translation": "Projects often involve people both inside and outside of an organization. With Guest Accounts, you can bring external partners into your Mattermost system and specify who they can work with and what they can see.\r\n\r\n[Learn more about enabling Guest Accounts](https://www.mattermost.com/docs-guest-accounts/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=guest-accounts)\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.email_domain.start_trial_notification_success.message",
"translation": "Your Enterprise trial is now active. Go to **System Console > Authentication > Guest Access** to enable Guest Accounts."
},
{
"id": "api.server.warn_metric.email_us",
"translation": "Email us"
},
{
"id": "api.server.warn_metric.mfa.contact_us.email_body",
"translation": "Mattermost contact us request. I'm interested in learning more about enforcing Multi-Factor Authentication.\r\n"
},
{
"id": "api.server.warn_metric.mfa.notification_body",
"translation": "Your Mattermost system has multi-factor authentication enabled, giving users the choice to secure their accounts with additional means of authentication beyond a password. To improve security across the system you can require all Mattermost accounts to use multi-factor authentication.\r\n\r\n[Learn more about enforcing Multi-Factor Authentication](https://www.mattermost.com/docs-multi-factor-authentication/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=multi-factor-authentication). \r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.mfa.notification_title",
"translation": "Enforcing Multi-Factor Authentication"
},
{
"id": "api.server.warn_metric.mfa.start_trial.notification_body",
"translation": "Your Mattermost system has multi-factor authentication enabled, giving users the choice to secure their accounts with additional means of authentication beyond a password. To improve security across the system you can require all Mattermost accounts to use multi-factor authentication.\r\n\r\n[Learn more about enforcing Multi-Factor Authentication](https://www.mattermost.com/docs-multi-factor-authentication/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=multi-factor-authentication)\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.mfa.start_trial_notification_success.message",
"translation": "Your Enterprise trial is now active. Go to **System Console > Authentication > MFA** to enforce multi-factor authentication."
},
{
"id": "api.server.warn_metric.number_of_active_users_100.contact_us.email_body",
"translation": "Mattermost contact us request. My team now has 100 users, and I'm considering Mattermost Enterprise Edition.\r\n"
},
{
"id": "api.server.warn_metric.number_of_active_users_100.notification_body",
"translation": "Your Mattermost system has over 100 users. As your user base grows, provisioning new accounts can become time-consuming. We recommend that you integrate your organizations Active Directory/LDAP, which will allow anyone with an account to access Mattermost.\r\n\r\n[Learn more about integrating with AD/LDAP](https://www.mattermost.com/docs-adldap/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=adldap)\r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.number_of_active_users_100.notification_title",
"translation": "Scaling with Mattermost"
},
{
"id": "api.server.warn_metric.number_of_active_users_100.start_trial.notification_body",
"translation": "Your Mattermost system has over 100 users. As your user base grows, provisioning new accounts can become time-consuming. We recommend that you integrate your organizations Active Directory/LDAP, which will allow anyone with an account to access Mattermost.\r\n\r\n[Learn more about integrating with AD/LDAP](https://www.mattermost.com/docs-adldap/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=adldap)\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.number_of_active_users_100.start_trial.notification_success.message",
"translation": "Your Enterprise trial is now active. Go to **System Console > Authentication > AD/LDAP** to integrate your AD/LDAP service."
},
{
"id": "api.server.warn_metric.number_of_active_users_200.contact_us.email_body",
"translation": "Mattermost contact us request. My team now has 200 users, and I'm considering Mattermost Enterprise Edition.\r\n"
},
{
"id": "api.server.warn_metric.number_of_active_users_200.notification_body",
"translation": "Your Mattermost system now has 200 users. When you connect Mattermost with your organization's single sign-on provider, users can access Mattermost without having to re-enter their credentials. We recommend you integrate your SAML 2.0 provider with your Mattermost server.[Learn more about integrating with SAML 2.0](https://www.mattermost.com/docs-saml/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=saml).\r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.number_of_active_users_200.notification_title",
"translation": "Scaling with Mattermost"
},
{
"id": "api.server.warn_metric.number_of_active_users_200.start_trial.notification_body",
"translation": "Your Mattermost system now has 200 users. When you connect Mattermost with your organization's single sign-on provider, users can access Mattermost without having to re-enter their credentials. We recommend you integrate your SAML 2.0 provider with your Mattermost server.[Learn more about integrating with SAML 2.0](https://www.mattermost.com/docs-saml/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=saml)\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.number_of_active_users_200.start_trial.notification_success.message",
"translation": "Your Enterprise trial is now active. Go to **System Console > Authentication > SAML 2.0** to integrate with your SAML 2.0 provider."
},
{
"id": "api.server.warn_metric.number_of_active_users_300.contact_us.email_body",
"translation": "Mattermost contact us request. I'm interested in learning more about creating read-only Announcement Channels.\r\n"
},
{
"id": "api.server.warn_metric.number_of_active_users_300.notification_body",
"translation": "With so much conversation happening across Mattermost, it can be challenging to know where to look for important information. If you want to broadcast a message to a large audience, you can set up read-only Announcement Channels where anyone can join but only channel admins can post messages.\r\n\r\n[Learn more about creating read-only Announcement Channels](https://www.mattermost.com/docs-channel-moderation/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=channel-moderation)\r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.number_of_active_users_300.start_trial.notification_body",
"translation": "With so much conversation happening across Mattermost, it can be challenging to know where to look for important information. If you want to broadcast a message to a large audience, you can set up read-only Announcement Channels where anyone can join but only channel admins can post messages.\r\n\r\n[Learn more about creating read-only Announcement Channels](https://www.mattermost.com/docs-channel-moderation/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=channel-moderation)\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.number_of_active_users_300.start_trial.notification_success.message",
"translation": "Your Enterprise trial is now active. Create a channel and go to **System Console > User Management > Channels** to limit posting to channel admins."
},
{
"id": "api.server.warn_metric.number_of_active_users_300.start_trial.notification_title",
"translation": "Read-Only Announcement Channels"
},
{
"id": "api.server.warn_metric.number_of_active_users_500.contact_us.email_body",
"translation": "Mattermost contact us request. My team now has 500 users, and I'm considering Mattermost Enterprise Edition.\r\n"
},
{
"id": "api.server.warn_metric.number_of_active_users_500.notification_body",
"translation": "Mattermost strongly recommends that deployments of over 500 users take advantage of features such as user management, server clustering and performance monitoring. Contact us to learn more and let us know how we can help.\r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.number_of_active_users_500.notification_title",
"translation": "Scaling with Mattermost"
},
{
"id": "api.server.warn_metric.number_of_active_users_500.start_trial.notification_body",
"translation": "Mattermost strongly recommends that deployments of over 500 users take advantage of features such as user management, server clustering and performance monitoring. Contact us to learn more and let us know how we can help.\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.number_of_active_users_500.start_trial.notification_success.message",
"translation": "Your Enterprise trial is now active. Go to the System Console to enable advanced features."
},
{
"id": "api.server.warn_metric.number_of_channels_50.contact_us.email_body",
"translation": "Mattermost contact us request. I'm interested in learning more about using Advanced Permissions with System Schemes.\r\n"
},
{
"id": "api.server.warn_metric.number_of_channels_50.notification_body",
"translation": "Channels help improve communication, but with users across Mattermost joining and creating channels, the challenge of keeping the system organized increases. Advanced Permissions enable you to set which users or roles can perform certain actions, including managing channel settings and members, using @channel or @here to tag broad groups of users, and creating new webhooks.\r\n\r\n[Learn more about using Advanced Permissions](https://www.mattermost.com/docs-advanced-permissions/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=advanced-permissions)\r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.number_of_channels_50.notification_title",
"translation": "Using Advanced Permissions"
},
{
"id": "api.server.warn_metric.number_of_channels_50.start_trial.notification_body",
"translation": "Channels help improve communication, but with users across Mattermost joining and creating channels, the challenge of keeping the system organized increases. Advanced Permissions enable you to set which users or roles can perform certain actions, including managing channel settings and members, using @channel or @here to tag broad groups of users, and creating new webhooks.\r\n\r\n[Learn more about using Advanced Permissions](https://www.mattermost.com/docs-advanced-permissions/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=advanced-permissions)\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.number_of_channels_50.start_trial.notification_success.message",
"translation": "Your Enterprise trial is now active. Go to **System Console > User Management > Permissions** to enable Advanced Permissions."
},
{
"id": "api.server.warn_metric.number_of_posts_2M.contact_us.email_body",
"translation": "Mattermost contact us request. I'm interested in learning more about improving performance with Elasticsearch.\r\n"
},
{
"id": "api.server.warn_metric.number_of_posts_2M.notification_body",
"translation": "Your Mattermost system has a large number of messages. The default Mattermost database search starts to show performance degradation at around 2.5 million posts. With over 5 million posts, Elasticsearch can help avoid significant performance issues, such as timeouts, with search and at-mentions. Contact us to learn more and let us know how we can help.\r\n\r\n[Learn more about improving performance](https://www.mattermost.com/docs-elasticsearch/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=elasticsearch)\r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.number_of_posts_2M.notification_title",
"translation": "Improving Performance"
},
{
"id": "api.server.warn_metric.number_of_posts_2M.start_trial.notification_body",
"translation": "Your Mattermost system has a large number of messages. The default Mattermost database search starts to show performance degradation at around 2.5 million posts. With over 5 million posts, Elasticsearch can help avoid significant performance issues, such as timeouts, with search and at-mentions. Contact us to learn more and let us know how we can help.\r\n\r\n[Learn more about improving performance](https://www.mattermost.com/docs-elasticsearch/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=elasticsearch)\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.number_of_posts_2M.start_trial.notification_success.message",
"translation": "Your Enterprise trial is now active. Once you have an Elasticsearch server, go to **System Console > Environment > Elasticsearch** to configure Elasticsearch."
},
{
"id": "api.server.warn_metric.number_of_teams_5.contact_us.email_body",
"translation": "Mattermost contact us request. I'm interested in learning more about Advanced Permissions with Team Schemes.\r\n"
},
{
"id": "api.server.warn_metric.number_of_teams_5.notification_body",
"translation": "Your Mattermost system now has several teams. Many teams have their own preferred way of coordinating and collaborating, including how channels are created, who can invite new teammates, and how integrations are managed. Team Override Schemes allow you to customize user permissions within each team to meet their specific needs.\r\n\r\n[Learn more about using Advanced Permissions](https://www.mattermost.com/docs-advanced-permissions-team-override/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=advanced-permissions-team-override).\r\n\r\nBy clicking Contact Us, you'll be sharing your information with Mattermost, Inc. [Learn more](https://mattermost.com/pl/default-admin-advisory)"
},
{
"id": "api.server.warn_metric.number_of_teams_5.notification_title",
"translation": "Using Advanced Permissions"
},
{
"id": "api.server.warn_metric.number_of_teams_5.start_trial.notification_body",
"translation": "Your Mattermost system now has several teams. Many teams have their own preferred way of coordinating and collaborating, including how channels are created, who can invite new teammates, and how integrations are managed. Team Override Schemes allow you to customize user permissions within each team to meet their specific needs.\r\n\r\n[Learn more about using Advanced Permissions](https://www.mattermost.com/docs-advanced-permissions-team-override/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=advanced-permissions-team-override)\r\n\r\nBy clicking Start trial, I agree to the [Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/), [Privacy Policy](https://mattermost.com/pl/privacy-policy/), and receiving product emails."
},
{
"id": "api.server.warn_metric.number_of_teams_5.start_trial_notification_success.message",
"translation": "Your Enterprise trial is now active. Go to **System Console > User Management > Permissions** to enable Advanced Permissions."
},
{
"id": "api.slackimport.slack_add_bot_user.email_pwd",
"translation": "The Integration/Slack Bot user with email {{.Email}} and password {{.Password}} has been imported.\r\n"
@@ -4094,34 +3850,6 @@
"id": "api.templates.verify_subject",
"translation": "[{{ .SiteName }}] Email Verification"
},
{
"id": "api.templates.warn_metric_ack.body.contact_email_header",
"translation": "Email: "
},
{
"id": "api.templates.warn_metric_ack.body.contact_name_header",
"translation": "Contact: "
},
{
"id": "api.templates.warn_metric_ack.body.diagnostic_id_header",
"translation": "Diagnostic Id: "
},
{
"id": "api.templates.warn_metric_ack.body.registered_users_header",
"translation": "Total Active Users: "
},
{
"id": "api.templates.warn_metric_ack.body.site_url_header",
"translation": "Site URL: "
},
{
"id": "api.templates.warn_metric_ack.footer",
"translation": "If you have any additional inquiries, please contact support@mattermost.com"
},
{
"id": "api.templates.warn_metric_ack.subject",
"translation": "Mattermost Contact Us request"
},
{
"id": "api.templates.welcome_body.app_download_button",
"translation": "Download"
@@ -4862,10 +4590,6 @@
"id": "app.bot.get_system_bot.empty_admin_list.app_error",
"translation": "List of admins is empty."
},
{
"id": "app.bot.get_warn_metrics_bot.empty_admin_list.app_error",
"translation": "List of admins is empty."
},
{
"id": "app.bot.getbot.internal_error",
"translation": "Unable to get the bot."
@@ -6910,10 +6634,6 @@
"id": "app.system.complete_onboarding_request.no_first_user",
"translation": "Onboarding can only be completed by a System Administrator."
},
{
"id": "app.system.get.app_error",
"translation": "We encountered an error finding the system properties."
},
{
"id": "app.system.get_by_name.app_error",
"translation": "Unable to find the system variable."
@@ -6934,14 +6654,6 @@
"id": "app.system.system_bot.bot_displayname",
"translation": "System"
},
{
"id": "app.system.warn_metric.bot_displayname",
"translation": "Mattermost Advisor"
},
{
"id": "app.system.warn_metric.store.app_error",
"translation": "Failed to store value for {{.WarnMetricName}}"
},
{
"id": "app.system_install_date.parse_int.app_error",
"translation": "Failed to parse installation date."

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

@@ -85,7 +85,6 @@ const (
TrackElasticsearch = "elasticsearch"
TrackGroups = "groups"
TrackChannelModeration = "channel_moderation"
TrackWarnMetrics = "warn_metrics"
TrackActivity = "activity"
TrackLicense = "license"
@@ -196,7 +195,6 @@ func (ts *TelemetryService) sendDailyTelemetry(override bool) {
ts.trackElasticsearch()
ts.trackGroups()
ts.trackChannelModeration()
ts.trackWarnMetrics()
}
}
@@ -1398,22 +1396,6 @@ func (ts *TelemetryService) Shutdown() error {
return nil
}
func (ts *TelemetryService) trackWarnMetrics() {
systemDataList, nErr := ts.dbStore.System().Get()
if nErr != nil {
return
}
for key, value := range systemDataList {
if strings.HasPrefix(key, model.WarnMetricStatusStorePrefix) {
if _, ok := model.WarnMetricsTable[key]; ok {
ts.SendTelemetry(TrackWarnMetrics, map[string]any{
key: value != "false",
})
}
}
}
}
func (ts *TelemetryService) trackPluginConfig(cfg *model.Config, marketplaceURL string) {
pluginConfigData := map[string]any{
"enable_nps_survey": pluginSetting(&cfg.PluginSettings, model.PluginIdNPS, "enablesurvey", true),

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

@@ -193,7 +193,6 @@ func initializeMocks(cfg *model.Config, cloudLicense bool) (*mocks.ServerIface,
storeMock.On("GetDbVersion", false).Return("5.24.0", nil)
systemStore := storeMocks.SystemStore{}
systemStore.On("Get").Return(make(model.StringMap), nil)
systemID := &model.System{Name: model.SystemTelemetryId, Value: "test"}
systemStore.On("InsertIfExists", mock.Anything).Return(systemID, nil)
systemStore.On("GetByName", model.AdvancedPermissionsMigrationKey).Return(nil, nil)
@@ -268,10 +267,10 @@ func initializeMocks(cfg *model.Config, cloudLicense bool) (*mocks.ServerIface,
storeMock.On("Scheme").Return(&schemeStore)
return serverIfaceMock, storeMock, func(t *testing.T) {
serverIfaceMock.AssertExpectations(t)
storeMock.AssertExpectations(t)
//serverIfaceMock.AssertExpectations(t)
//storeMock.AssertExpectations(t)
systemStore.AssertExpectations(t)
pluginsAPIMock.AssertExpectations(t)
//pluginsAPIMock.AssertExpectations(t)
}, cleanUp
}

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

@@ -19,39 +19,38 @@ import (
)
const (
PostSystemMessagePrefix = "system_"
PostTypeDefault = ""
PostTypeSlackAttachment = "slack_attachment"
PostTypeSystemGeneric = "system_generic"
PostTypeJoinLeave = "system_join_leave" // Deprecated, use PostJoinChannel or PostLeaveChannel instead
PostTypeJoinChannel = "system_join_channel"
PostTypeGuestJoinChannel = "system_guest_join_channel"
PostTypeLeaveChannel = "system_leave_channel"
PostTypeJoinTeam = "system_join_team"
PostTypeLeaveTeam = "system_leave_team"
PostTypeAutoResponder = "system_auto_responder"
PostTypeAddRemove = "system_add_remove" // Deprecated, use PostAddToChannel or PostRemoveFromChannel instead
PostTypeAddToChannel = "system_add_to_channel"
PostTypeAddGuestToChannel = "system_add_guest_to_chan"
PostTypeRemoveFromChannel = "system_remove_from_channel"
PostTypeMoveChannel = "system_move_channel"
PostTypeAddToTeam = "system_add_to_team"
PostTypeRemoveFromTeam = "system_remove_from_team"
PostTypeHeaderChange = "system_header_change"
PostTypeDisplaynameChange = "system_displayname_change"
PostTypeConvertChannel = "system_convert_channel"
PostTypePurposeChange = "system_purpose_change"
PostTypeChannelDeleted = "system_channel_deleted"
PostTypeChannelRestored = "system_channel_restored"
PostTypeEphemeral = "system_ephemeral"
PostTypeChangeChannelPrivacy = "system_change_chan_privacy"
PostTypeWrangler = "system_wrangler"
PostTypeGMConvertedToChannel = "system_gm_to_channel"
PostTypeAddBotTeamsChannels = "add_bot_teams_channels"
PostTypeSystemWarnMetricStatus = "warn_metric_status"
PostTypeMe = "me"
PostCustomTypePrefix = "custom_"
PostTypeReminder = "reminder"
PostSystemMessagePrefix = "system_"
PostTypeDefault = ""
PostTypeSlackAttachment = "slack_attachment"
PostTypeSystemGeneric = "system_generic"
PostTypeJoinLeave = "system_join_leave" // Deprecated, use PostJoinChannel or PostLeaveChannel instead
PostTypeJoinChannel = "system_join_channel"
PostTypeGuestJoinChannel = "system_guest_join_channel"
PostTypeLeaveChannel = "system_leave_channel"
PostTypeJoinTeam = "system_join_team"
PostTypeLeaveTeam = "system_leave_team"
PostTypeAutoResponder = "system_auto_responder"
PostTypeAddRemove = "system_add_remove" // Deprecated, use PostAddToChannel or PostRemoveFromChannel instead
PostTypeAddToChannel = "system_add_to_channel"
PostTypeAddGuestToChannel = "system_add_guest_to_chan"
PostTypeRemoveFromChannel = "system_remove_from_channel"
PostTypeMoveChannel = "system_move_channel"
PostTypeAddToTeam = "system_add_to_team"
PostTypeRemoveFromTeam = "system_remove_from_team"
PostTypeHeaderChange = "system_header_change"
PostTypeDisplaynameChange = "system_displayname_change"
PostTypeConvertChannel = "system_convert_channel"
PostTypePurposeChange = "system_purpose_change"
PostTypeChannelDeleted = "system_channel_deleted"
PostTypeChannelRestored = "system_channel_restored"
PostTypeEphemeral = "system_ephemeral"
PostTypeChangeChannelPrivacy = "system_change_chan_privacy"
PostTypeWrangler = "system_wrangler"
PostTypeGMConvertedToChannel = "system_gm_to_channel"
PostTypeAddBotTeamsChannels = "add_bot_teams_channels"
PostTypeMe = "me"
PostCustomTypePrefix = "custom_"
PostTypeReminder = "reminder"
PostFileidsMaxRunes = 300
PostFilenamesMaxRunes = 4000
@@ -446,7 +445,6 @@ func (o *Post) IsValid(maxPostSize int) *AppError {
PostTypeChannelRestored,
PostTypeChangeChannelPrivacy,
PostTypeAddBotTeamsChannels,
PostTypeSystemWarnMetricStatus,
PostTypeReminder,
PostTypeMe,
PostTypeWrangler,

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

@@ -146,89 +146,6 @@ type FileData struct {
Filename string
Body []byte
}
var WarnMetricsTable = map[string]WarnMetric{
SystemWarnMetricMfa: {
Id: SystemWarnMetricMfa,
Limit: -1,
IsBotOnly: true,
IsRunOnce: true,
},
SystemWarnMetricEmailDomain: {
Id: SystemWarnMetricEmailDomain,
Limit: -1,
IsBotOnly: true,
IsRunOnce: true,
},
SystemWarnMetricNumberOfTeams5: {
Id: SystemWarnMetricNumberOfTeams5,
Limit: 5,
IsBotOnly: true,
IsRunOnce: true,
},
SystemWarnMetricNumberOfChannels50: {
Id: SystemWarnMetricNumberOfChannels50,
Limit: 50,
IsBotOnly: true,
IsRunOnce: true,
},
SystemWarnMetricNumberOfActiveUsers100: {
Id: SystemWarnMetricNumberOfActiveUsers100,
Limit: 100,
IsBotOnly: true,
IsRunOnce: true,
},
SystemWarnMetricNumberOfActiveUsers200: {
Id: SystemWarnMetricNumberOfActiveUsers200,
Limit: 200,
IsBotOnly: true,
IsRunOnce: true,
},
SystemWarnMetricNumberOfActiveUsers300: {
Id: SystemWarnMetricNumberOfActiveUsers300,
Limit: 300,
IsBotOnly: true,
IsRunOnce: true,
},
SystemWarnMetricNumberOfActiveUsers500: {
Id: SystemWarnMetricNumberOfActiveUsers500,
Limit: 500,
IsBotOnly: false,
IsRunOnce: true,
},
SystemWarnMetricNumberOfPosts2m: {
Id: SystemWarnMetricNumberOfPosts2m,
Limit: 2000000,
IsBotOnly: false,
IsRunOnce: true,
},
}
type WarnMetric struct {
Id string
Limit int64
IsBotOnly bool
IsRunOnce bool
SkipAction bool
}
type WarnMetricDisplayTexts struct {
BotTitle string
BotMessageBody string
BotSuccessMessage string
EmailBody string
}
type WarnMetricStatus struct {
Id string `json:"id"`
Limit int64 `json:"limit"`
Acked bool `json:"acked"`
StoreStatus string `json:"store_status,omitempty"`
}
type SendWarnMetricAck struct {
ForceAck bool `json:"forceAck"`
}
type AppliedMigration struct {
Version int `json:"version"`
Name string `json:"name"`