Deprecate admin advisor (#26045)

* 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>
This commit is contained in:
Maria A Nunez 2024-02-25 22:35:00 -05:00 committed by GitHub
parent dc8fc773dc
commit e9b9d4ff60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 45 additions and 2285 deletions

View File

@ -1086,122 +1086,6 @@
$ref: "#/components/schemas/StatusOK"
"403":
$ref: "#/components/responses/Forbidden"
/api/v4/warn_metrics/status:
get:
tags:
- system
summary: Get the warn metrics status (enabled or disabled)
description: |
Get the status of a set of metrics (enabled or disabled) from the Systems table.
The returned JSON contains the metrics that we need to warn the admin on with regard
to their status (we return the ones whose status is "true", which means that they are
in a "warnable" state - e.g. a threshold has been crossed or some other condition has
been fulfilled).
__Minimum server version__: 5.26
##### Permissions
Must have `manage_system` permission.
operationId: GetWarnMetricsStatus
responses:
"200":
description: Warn metrics retrieval was successful.
content:
application/json:
schema:
$ref: "#/components/schemas/StatusOK"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/api/v4/warn_metrics/ack/{warn_metric_id}:
post:
tags:
- system
summary: Acknowledge a warning of a metric status
description: |
Acknowledge a warning for the warn_metric_id metric crossing a threshold (or some
similar condition being fulfilled) - attempts to send an ack email to
acknowledge@mattermost.com and sets the "ack" status for all the warn metrics in the system.
__Minimum server version__: 5.26
##### Permissions
Must have `manage_system` permission.
operationId: SendWarnMetricAck
parameters:
- name: warn_metric_id
in: path
description: Warn Metric Id.
required: true
schema:
type: string
requestBody:
description: payload that contains the ack flag
required: true
content:
application/json:
schema:
type: object
properties:
forceAck:
type: boolean
description: Flag which determines if the ack for the metric warning should be directly stored (without trying to send email first) or not
responses:
"200":
description: The acknowledgement of the warning for the metric has been successful.
content:
application/json:
schema:
$ref: "#/components/schemas/StatusOK"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/api/v4/warn_metrics/trial-license-ack/{warn_metric_id}:
post:
tags:
- system
summary: Request trial license and acknowledge a warning of a metric status
description: |
Request a trial license and acknowledge a warning for the warn_metric_id metric crossing a threshold (or some
similar condition being fulfilled) - sets the "ack" status for all the warn metrics in the system.
__Minimum server version__: 5.28
##### Permissions
Must have `manage_system` permission.
operationId: SendTrialLicenseWarnMetricAck
parameters:
- name: warn_metric_id
in: path
description: Warn Metric Id.
required: true
schema:
type: string
responses:
"200":
description: The trial license request and the subsequent acknowledgement of the warning for the metric have been successful.
content:
application/json:
schema:
$ref: "#/components/schemas/StatusOK"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/api/v4/integrity:
post:
tags:

View File

@ -13,6 +13,12 @@
describe('Support Packet Generation', () => {
before(() => {
cy.apiRequireLicense();
cy.apiUpdateConfig({
LogSettings: {
FileLevel: 'ERROR',
},
});
});
it('MM-T3849 - Commercial Support Dialog UI - E10/E20 License', () => {

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

View File

@ -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{

View File

@ -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)
}

View File

@ -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")

View File

@ -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")

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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"))

View File

@ -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."

View File

@ -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),

View File

@ -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
}

View File

@ -48,7 +48,6 @@ const (
PostTypeWrangler = "system_wrangler"
PostTypeGMConvertedToChannel = "system_gm_to_channel"
PostTypeAddBotTeamsChannels = "add_bot_teams_channels"
PostTypeSystemWarnMetricStatus = "warn_metric_status"
PostTypeMe = "me"
PostCustomTypePrefix = "custom_"
PostTypeReminder = "reminder"
@ -446,7 +445,6 @@ func (o *Post) IsValid(maxPostSize int) *AppError {
PostTypeChannelRestored,
PostTypeChangeChannelPrivacy,
PostTypeAddBotTeamsChannels,
PostTypeSystemWarnMetricStatus,
PostTypeReminder,
PostTypeMe,
PostTypeWrangler,

View File

@ -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"`

View File

@ -118,7 +118,7 @@ import RemovedFromChannelModal from 'components/removed_from_channel_modal';
import WebSocketClient from 'client/web_websocket_client';
import {loadPlugin, loadPluginsIfNecessary, removePlugin} from 'plugins';
import {getHistory} from 'utils/browser_history';
import {ActionTypes, Constants, AnnouncementBarMessages, SocketEvents, UserStatuses, ModalIdentifiers, WarnMetricTypes, PageLoadContext, StoragePrefixes} from 'utils/constants';
import {ActionTypes, Constants, AnnouncementBarMessages, SocketEvents, UserStatuses, ModalIdentifiers, PageLoadContext, StoragePrefixes} from 'utils/constants';
import {getSiteURL} from 'utils/url';
import {temporarilySetPageLoadContext} from './telemetry_actions';
@ -527,14 +527,6 @@ export function handleEvent(msg) {
handleGroupNotAssociatedToChannelEvent(msg);
break;
case SocketEvents.WARN_METRIC_STATUS_RECEIVED:
handleWarnMetricStatusReceivedEvent(msg);
break;
case SocketEvents.WARN_METRIC_STATUS_REMOVED:
handleWarnMetricStatusRemovedEvent(msg);
break;
case SocketEvents.SIDEBAR_CATEGORY_CREATED:
dispatch(handleSidebarCategoryCreated(msg));
break;
@ -1481,30 +1473,6 @@ function handleGroupNotAssociatedToChannelEvent(msg) {
});
}
function handleWarnMetricStatusReceivedEvent(msg) {
var receivedData = JSON.parse(msg.data.warnMetricStatus);
let bannerData;
if (receivedData.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
bannerData = AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS;
} else if (receivedData.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
bannerData = AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS;
}
store.dispatch(batchActions([
{
type: GeneralTypes.WARN_METRIC_STATUS_RECEIVED,
data: receivedData,
},
{
type: ActionTypes.SHOW_NOTICE,
data: [bannerData],
},
]));
}
function handleWarnMetricStatusRemovedEvent(msg) {
store.dispatch({type: GeneralTypes.WARN_METRIC_STATUS_REMOVED, data: {id: msg.data.warnMetricId}});
}
function handleSidebarCategoryCreated(msg) {
return (doDispatch, doGetState) => {
const state = doGetState();

View File

@ -17,10 +17,9 @@ import {trackEvent} from 'actions/telemetry_actions';
import PurchaseLink from 'components/announcement_bar/purchase_link/purchase_link';
import ExternalLink from 'components/external_link';
import ackIcon from 'images/icons/check-circle-outline.svg';
import alertIcon from 'images/icons/round-white-info-icon.svg';
import warningIcon from 'images/icons/warning-icon.svg';
import {AnnouncementBarTypes, AnnouncementBarMessages, WarnMetricTypes, Preferences, ConfigurationBanners, Constants, TELEMETRY_CATEGORIES} from 'utils/constants';
import {AnnouncementBarTypes, AnnouncementBarMessages, Preferences, ConfigurationBanners, Constants, TELEMETRY_CATEGORIES} from 'utils/constants';
import {t} from 'utils/i18n';
import {daysToLicenseExpire, isLicenseExpired, isLicenseExpiring, isLicensePastGracePeriod, isTrialLicense} from 'utils/license_utils';
import {getSkuDisplayName} from 'utils/subscription';
@ -76,115 +75,8 @@ const ConfigurationAnnouncementBar = (props: Props) => {
props.actions.dismissNotice(AnnouncementBarMessages.TRIAL_LICENSE_EXPIRING);
};
const dismissNumberOfActiveUsersWarnMetric = () => {
props.actions.dismissNotice(AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS);
};
const dismissNumberOfPostsWarnMetric = () => {
props.actions.dismissNotice(AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS);
};
const dismissNumberOfActiveUsersWarnMetricAck = () => {
props.actions.dismissNotice(AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS_ACK);
};
const dismissNumberOfPostsWarnMetricAck = () => {
props.actions.dismissNotice(AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS_ACK);
};
const renewLinkTelemetry = {success: 'renew_license_banner_success', error: 'renew_license_banner_fail'};
const getNoticeForWarnMetric = (warnMetricStatus: any) => {
if (!warnMetricStatus ||
(warnMetricStatus.id !== WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500 &&
warnMetricStatus.id !== WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M)) {
return null;
}
let message: JSX.Element | string = '';
let type = '';
let showModal = false;
let dismissFunc;
let isDismissed = null;
let canCloseBar = false;
if (warnMetricStatus.acked) {
message = (
<>
<img
className='advisor-icon'
src={ackIcon}
/>
<FormattedMessage
id='announcement_bar.warn_metric_status_ack.text'
defaultMessage='Thank you for contacting Mattermost. We will follow up with you soon.'
/>
</>
);
if (warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
dismissFunc = dismissNumberOfActiveUsersWarnMetricAck;
isDismissed = props.dismissedNumberOfActiveUsersWarnMetricStatusAck;
} else if (warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
dismissFunc = dismissNumberOfPostsWarnMetricAck;
isDismissed = props.dismissedNumberOfPostsWarnMetricStatusAck;
}
type = AnnouncementBarTypes.ADVISOR_ACK;
showModal = false;
canCloseBar = true;
} else {
if (warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
message = (
<>
<img
className='advisor-icon'
src={alertIcon}
/>
<FormattedMessage
id='announcement_bar.number_active_users_warn_metric_status.text'
defaultMessage='You now have over {limit} users. We strongly recommend using advanced features for large-scale servers.'
values={{
limit: warnMetricStatus.limit,
}}
/>
</>
);
dismissFunc = dismissNumberOfActiveUsersWarnMetric;
isDismissed = props.dismissedNumberOfActiveUsersWarnMetricStatus;
} else if (warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
message = (
<>
<img
className='advisor-icon'
src={alertIcon}
/>
<FormattedMessage
id='announcement_bar.number_of_posts_warn_metric_status.text'
defaultMessage='You now have over {limit} posts. We strongly recommend using advanced features for large-scale servers.'
values={{
limit: warnMetricStatus.limit,
}}
/>
</>
);
dismissFunc = dismissNumberOfPostsWarnMetric;
isDismissed = props.dismissedNumberOfPostsWarnMetricStatus;
}
type = AnnouncementBarTypes.ADVISOR;
showModal = true;
canCloseBar = false;
}
return {
Message: message,
DismissFunc: dismissFunc,
IsDismissed: isDismissed,
Type: type,
ShowModal: showModal,
CanCloseBar: canCloseBar,
};
};
// System administrators
if (props.canViewSystemErrors) {
if ((isLicensePastGracePeriod(props.license) || isLicenseExpired(props.license)) && !props.dismissedExpiredLicense) {
@ -316,29 +208,6 @@ const ConfigurationAnnouncementBar = (props: Props) => {
/>
);
}
if (props.license?.IsLicensed === 'false' &&
props.warnMetricsStatus) {
for (const status of Object.values(props.warnMetricsStatus)) {
const notice = getNoticeForWarnMetric(status);
if (!notice || notice.IsDismissed) {
continue;
}
return (
<AnnouncementBar
showCloseButton={notice.CanCloseBar}
handleClose={notice.DismissFunc}
type={notice.Type}
showModal={notice.ShowModal}
modalButtonText={t('announcement_bar.error.warn_metric_status.link')}
modalButtonDefaultText='Learn more'
warnMetricStatus={status}
message={notice.Message}
/>
);
}
}
} else {
// Regular users
if (isLicensePastGracePeriod(props.license)) { //eslint-disable-line no-lonely-if

View File

@ -25,10 +25,6 @@ function mapStateToProps(state: GlobalState) {
dismissedExpiringTrialLicense: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.TRIAL_LICENSE_EXPIRING]),
dismissedExpiredLicense: Boolean(getPreference(state, Preferences.CONFIGURATION_BANNERS, ConfigurationBanners.LICENSE_EXPIRED) === 'true'),
dismissedExpiringLicense: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.LICENSE_EXPIRING]),
dismissedNumberOfActiveUsersWarnMetricStatus: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS]),
dismissedNumberOfActiveUsersWarnMetricStatusAck: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_USERS_ACK]),
dismissedNumberOfPostsWarnMetricStatus: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS]),
dismissedNumberOfPostsWarnMetricStatusAck: Boolean(state.views.notice.hasBeenDismissed[AnnouncementBarMessages.WARN_METRIC_STATUS_NUMBER_OF_POSTS_ACK]),
currentUserId,
};
}

View File

@ -5,17 +5,11 @@ import type {ReactNode} from 'react';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import type {WarnMetricStatus} from '@mattermost/types/config';
import {trackEvent} from 'actions/telemetry_actions.jsx';
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
import OverlayTrigger from 'components/overlay_trigger';
import ToggleModalButton from 'components/toggle_modal_button';
import Tooltip from 'components/tooltip';
import WarnMetricAckModal from 'components/warn_metric_ack_modal';
import {Constants, AnnouncementBarTypes, ModalIdentifiers} from 'utils/constants';
import {Constants, AnnouncementBarTypes} from 'utils/constants';
import {isStringContainingUrl} from 'utils/url';
type Props = {
@ -34,7 +28,6 @@ type Props = {
modalButtonDefaultText?: string;
showLinkAsButton: boolean;
icon?: ReactNode;
warnMetricStatus?: WarnMetricStatus;
actions: {
incrementAnnouncementBarCount: () => void;
decrementAnnouncementBarCount: () => void;
@ -191,33 +184,6 @@ export default class AnnouncementBar extends React.PureComponent<Props, State> {
>
{message}
</span>
{
!this.props.showLinkAsButton && this.props.showCTA && this.props.modalButtonText && this.props.modalButtonDefaultText &&
<span className='announcement-bar__link'>
{this.props.showModal &&
<FormattedMessage
id={this.props.modalButtonText}
defaultMessage={this.props.modalButtonDefaultText}
>
{(linkmessage) => (
<ToggleModalButton
ariaLabel={linkmessage as unknown as string}
className={'color--link--adminack'}
dialogType={WarnMetricAckModal}
onClick={() => trackEvent('admin', 'click_warn_metric_learn_more')}
modalId={ModalIdentifiers.WARN_METRIC_ACK}
dialogProps={{
warnMetricStatus: this.props.warnMetricStatus,
closeParentComponent: this.props.handleClose,
}}
>
{linkmessage}
</ToggleModalButton>
)}
</FormattedMessage>
}
</span>
}
{
this.props.showLinkAsButton && this.props.showCTA && this.props.modalButtonText && this.props.modalButtonDefaultText &&
<button

View File

@ -9,7 +9,7 @@ import {getStandardAnalytics} from 'mattermost-redux/actions/admin';
import {getCloudSubscription, getCloudCustomer} from 'mattermost-redux/actions/cloud';
import {dismissError} from 'mattermost-redux/actions/errors';
import {Permissions} from 'mattermost-redux/constants';
import {getConfig, getLicense, warnMetricsStatus as getWarnMetricsStatus} from 'mattermost-redux/selectors/entities/general';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {haveISystemPermission} from 'mattermost-redux/selectors/entities/roles';
import {isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users';
import {getDisplayableErrors} from 'mattermost-redux/selectors/errors';
@ -25,7 +25,6 @@ function mapStateToProps(state: GlobalState) {
const license = getLicense(state);
const config = getConfig(state);
const errors = getDisplayableErrors(state);
const warnMetricsStatus = getWarnMetricsStatus(state);
const isCloud = license.Cloud === 'true';
const subscription = state.entities.cloud?.subscription;
const userIsAdmin = isCurrentUserSystemAdmin(state);
@ -40,7 +39,6 @@ function mapStateToProps(state: GlobalState) {
config,
canViewSystemErrors,
latestError,
warnMetricsStatus,
isCloud,
subscription,
userIsAdmin,

View File

@ -1,223 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/WarnMetricAckModal error display 1`] = `
<Modal
animation={true}
aria-labelledby="warnMetricAckHeaderModalLabel"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={false}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="dialog"
show={false}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
>
<ModalTitle
bsClass="modal-title"
componentClass="h1"
id="warnMetricAckHeaderModalLabel"
/>
</ModalHeader>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<div>
<br />
<div
className="form-group has-error"
>
<br />
<label
className="control-label"
>
<MemoizedFormattedMessage
defaultMessage="Support could not be reached. Please {link}."
id="warn_metric_ack_modal.mailto.message"
values={
Object {
"link": <WarnMetricAckErrorLink
defaultMessage="email us"
forceAck={true}
messageId="warn_metric_ack_modal.mailto.link"
onClickHandler={[Function]}
url="mailto:support-advisor@mattermost.com?cc=a@test.com&subject=Mattermost%20Contact%20Us%20request&body=Mattermost%20Contact%20Us%20request.%0D%0AContact%20Fake%20Person%0D%0AEmail%20a%40test.com%0D%0ASite%20URL%20http%3A%2F%2Flocalhost%3A8065%0D%0ATelemetry%20Id%20diag_0%0D%0AIf%20you%20have%20any%20additional%20inquiries%2C%20please%20contact%20support%40mattermost.com"
/>,
}
}
/>
</label>
</div>
<br />
<div
className="help__format-text"
style={
Object {
"display": "flex",
"flexWrap": "wrap",
"opacity": "0.56",
}
}
>
<MemoizedFormattedMessage
defaultMessage="By clicking Acknowledge, you will be sharing your information with Mattermost Inc. {link}"
id="warn_metric_ack_modal.subtext"
values={
Object {
"link": <ErrorLink
defaultMessage="Learn more"
messageId="warn_metric_ack_modal.learn_more.link"
url="https://mattermost.com/pl/default-admin-advisory"
/>,
}
}
/>
</div>
</div>
</ModalBody>
<ModalFooter
bsClass="modal-footer"
componentClass="div"
>
<button
autoFocus={true}
className="btn btn-primary save-button"
data-dismiss="modal"
disabled={false}
onClick={[Function]}
>
<Memo(LoadingWrapper)
loading={false}
text="Sending email"
>
<MemoizedFormattedMessage
defaultMessage="Acknowledge"
id="warn_metric_ack_modal.contact_support"
/>
</Memo(LoadingWrapper)>
</button>
</ModalFooter>
</Modal>
`;
exports[`components/WarnMetricAckModal should match snapshot, init 1`] = `
<Modal
animation={true}
aria-labelledby="warnMetricAckHeaderModalLabel"
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal"
dialogComponentClass={[Function]}
enforceFocus={true}
keyboard={false}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[Function]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
role="dialog"
show={false}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
>
<ModalTitle
bsClass="modal-title"
componentClass="h1"
id="warnMetricAckHeaderModalLabel"
/>
</ModalHeader>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<div>
<br />
<br />
<div
className="help__format-text"
style={
Object {
"display": "flex",
"flexWrap": "wrap",
"opacity": "0.56",
}
}
>
<MemoizedFormattedMessage
defaultMessage="By clicking Acknowledge, you will be sharing your information with Mattermost Inc. {link}"
id="warn_metric_ack_modal.subtext"
values={
Object {
"link": <ErrorLink
defaultMessage="Learn more"
messageId="warn_metric_ack_modal.learn_more.link"
url="https://mattermost.com/pl/default-admin-advisory"
/>,
}
}
/>
</div>
</div>
</ModalBody>
<ModalFooter
bsClass="modal-footer"
componentClass="div"
>
<button
autoFocus={true}
className="btn btn-primary save-button"
data-dismiss="modal"
disabled={false}
onClick={[Function]}
>
<Memo(LoadingWrapper)
loading={false}
text="Sending email"
>
<MemoizedFormattedMessage
defaultMessage="Acknowledge"
id="warn_metric_ack_modal.contact_support"
/>
</Memo(LoadingWrapper)>
</button>
</ModalFooter>
</Modal>
`;

View File

@ -1,52 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import type {Dispatch} from 'redux';
import {sendWarnMetricAck} from 'mattermost-redux/actions/admin';
import {getFilteredUsersStats} from 'mattermost-redux/actions/users';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/common';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getFilteredUsersStats as selectFilteredUserStats} from 'mattermost-redux/selectors/entities/users';
import {closeModal} from 'actions/views/modals';
import {isModalOpen} from 'selectors/views/modals';
import {ModalIdentifiers} from 'utils/constants';
import type {GlobalState} from 'types/store';
import WarnMetricAckModal from './warn_metric_ack_modal';
type Props = {
closeParentComponent: () => Promise<void>;
};
function mapStateToProps(state: GlobalState, ownProps: Props) {
const config = getConfig(state);
return {
totalUsers: selectFilteredUserStats(state)?.total_users_count || 0,
user: getCurrentUser(state),
telemetryId: config.DiagnosticId,
show: isModalOpen(state, ModalIdentifiers.WARN_METRIC_ACK),
closeParentComponent: ownProps.closeParentComponent,
};
}
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators(
{
closeModal,
sendWarnMetricAck,
getFilteredUsersStats,
},
dispatch,
),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(WarnMetricAckModal);

View File

@ -1,100 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {Modal} from 'react-bootstrap';
import type {UserProfile} from '@mattermost/types/users';
import WarnMetricAckModal from 'components/warn_metric_ack_modal/warn_metric_ack_modal';
describe('components/WarnMetricAckModal', () => {
const serverError = 'some error';
const baseProps = {
stats: {
registered_users: 200,
},
user: {
id: 'someUserId',
first_name: 'Fake',
last_name: 'Person',
email: 'a@test.com',
} as UserProfile,
show: false,
telemetryId: 'diag_0',
closeParentComponent: jest.fn(),
warnMetricStatus: {
id: 'metric1',
limit: 500,
acked: false,
store_status: 'status1',
},
actions: {
closeModal: jest.fn(),
getFilteredUsersStats: jest.fn(),
sendWarnMetricAck: jest.fn().mockResolvedValue({}),
},
};
test('should match snapshot, init', () => {
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('error display', () => {
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...baseProps}/>,
);
wrapper.setState({serverError});
expect(wrapper).toMatchSnapshot();
});
test('should match state when onHide is called', () => {
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...baseProps}/>,
);
wrapper.setState({saving: true});
wrapper.instance().onHide();
expect(wrapper.state('saving')).toEqual(false);
});
test('should match state when onHideWithParent is called', () => {
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...baseProps}/>,
);
wrapper.setState({saving: true});
wrapper.instance().onHide();
expect(baseProps.closeParentComponent).toHaveBeenCalledTimes(1);
expect(wrapper.state('saving')).toEqual(false);
});
test('send ack on acknowledge button click', () => {
const props = {...baseProps};
const wrapper = shallow<WarnMetricAckModal>(
<WarnMetricAckModal {...props}/>,
);
wrapper.setState({saving: false});
wrapper.find('.save-button').simulate('click');
expect(props.actions.sendWarnMetricAck).toHaveBeenCalledTimes(1);
});
test('should have called props.onHide when Modal.onExited is called', () => {
const props = {...baseProps};
const wrapper = shallow(
<WarnMetricAckModal {...props}/>,
);
wrapper.find(Modal).props().onExited!(document.createElement('div'));
expect(baseProps.actions.closeModal).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,305 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import type {CSSProperties} from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import type {WarnMetricStatus} from '@mattermost/types/config';
import type {ServerError} from '@mattermost/types/errors';
import type {GetFilteredUsersStatsOpts, UsersStats, UserProfile} from '@mattermost/types/users';
import type {ActionResult} from 'mattermost-redux/types/actions';
import {trackEvent} from 'actions/telemetry_actions';
import ErrorLink from 'components/error_page/error_link';
import ExternalLink from 'components/external_link';
import LoadingWrapper from 'components/widgets/loading/loading_wrapper';
import {ModalIdentifiers, WarnMetricTypes} from 'utils/constants';
import {t} from 'utils/i18n';
import {getSiteURL} from 'utils/url';
import * as Utils from 'utils/utils';
type Props = {
user: UserProfile;
telemetryId?: string;
show: boolean;
closeParentComponent?: () => Promise<void>;
totalUsers?: number;
warnMetricStatus: WarnMetricStatus;
actions: {
closeModal: (modalId: string) => void;
sendWarnMetricAck: (warnMetricId: string, forceAck: boolean) => Promise<ActionResult>;
getFilteredUsersStats: (filters: GetFilteredUsersStatsOpts) => Promise<{
data?: UsersStats;
error?: ServerError;
}>;
};
}
type State = {
serverError: string | null;
gettingTrial: boolean;
gettingTrialError: string | null;
saving: boolean;
}
const containerStyles: CSSProperties = {
display: 'flex',
opacity: '0.56',
flexWrap: 'wrap',
};
export default class WarnMetricAckModal extends React.PureComponent<Props, State> {
public constructor(props: Props) {
super(props);
this.state = {
saving: false,
serverError: null,
gettingTrial: false,
gettingTrialError: null,
};
}
componentDidMount() {
this.props.actions.getFilteredUsersStats({include_bots: false, include_deleted: false});
}
onContactUsClick = async (e: any) => {
if (this.state.saving) {
return;
}
this.setState({saving: true, serverError: null});
let forceAck = false;
if (e && e.target && e.target.dataset && e.target.dataset.forceack) {
forceAck = true;
trackEvent('admin', 'click_warn_metric_mailto', {metric: this.props.warnMetricStatus.id});
} else {
trackEvent('admin', 'click_warn_metric_contact_us', {metric: this.props.warnMetricStatus.id});
}
const {error} = await this.props.actions.sendWarnMetricAck(this.props.warnMetricStatus.id, forceAck);
if (error) {
this.setState({serverError: error, saving: false});
} else {
this.onHide();
}
};
onHide = () => {
this.setState({serverError: null, saving: false});
this.setState({gettingTrialError: null, gettingTrial: false});
this.props.actions.closeModal(ModalIdentifiers.WARN_METRIC_ACK);
if (this.props.closeParentComponent) {
this.props.closeParentComponent();
}
};
renderContactUsError = () => {
const {serverError} = this.state;
if (!serverError) {
return '';
}
const mailRecipient = 'support-advisor@mattermost.com';
const mailSubject = 'Mattermost Contact Us request';
let mailBody = 'Mattermost Contact Us request.';
if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
mailBody = 'Mattermost Contact Us request.\r\nMy team now has 500 users, and I am considering Mattermost Enterprise Edition.';
} else if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
mailBody = 'Mattermost Contact Us request.\r\nI am interested in learning more about improving performance with Elasticsearch.';
}
mailBody += '\r\n';
mailBody += 'Contact ' + this.props.user.first_name + ' ' + this.props.user.last_name;
mailBody += '\r\n';
mailBody += 'Email ' + this.props.user.email;
mailBody += '\r\n';
if (this.props.totalUsers) {
mailBody += 'Registered Users ' + this.props.totalUsers;
mailBody += '\r\n';
}
mailBody += 'Site URL ' + getSiteURL();
mailBody += '\r\n';
mailBody += 'Telemetry Id ' + this.props.telemetryId;
mailBody += '\r\n';
mailBody += 'If you have any additional inquiries, please contact support@mattermost.com';
const mailToLinkText = 'mailto:' + mailRecipient + '?cc=' + this.props.user.email + '&subject=' + encodeURIComponent(mailSubject) + '&body=' + encodeURIComponent(mailBody);
return (
<div className='form-group has-error'>
<br/>
<label className='control-label'>
<FormattedMessage
id='warn_metric_ack_modal.mailto.message'
defaultMessage='Support could not be reached. Please {link}.'
values={{
link: (
<WarnMetricAckErrorLink
url={mailToLinkText}
messageId={t('warn_metric_ack_modal.mailto.link')}
forceAck={true}
defaultMessage='email us'
onClickHandler={this.onContactUsClick}
/>
),
}}
/>
</label>
</div>
);
};
render() {
let headerTitle;
if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
headerTitle = (
<FormattedMessage
id='warn_metric_ack_modal.number_of_users.header.title'
defaultMessage='Scaling with Mattermost'
/>
);
} else if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
headerTitle = (
<FormattedMessage
id='warn_metric_ack_modal.number_of_posts.header.title'
defaultMessage='Improve Performance'
/>
);
}
let descriptionText;
const learnMoreLink = 'https://mattermost.com/pl/default-admin-advisory';
if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500) {
descriptionText = (
<FormattedMessage
id='warn_metric_ack_modal.number_of_active_users.description'
defaultMessage='Mattermost strongly recommends that deployments of over {limit}} 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.'
values={{
limit: this.props.warnMetricStatus.limit,
}}
/>
);
} else if (this.props.warnMetricStatus.id === WarnMetricTypes.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M) {
descriptionText = (
<FormattedMessage
id='warn_metric_ack_modal.number_of_posts.description'
defaultMessage='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.'
values={{
limit: this.props.warnMetricStatus.limit,
}}
/>
);
}
const subText = (
<div
style={containerStyles}
className='help__format-text'
>
<FormattedMessage
id='warn_metric_ack_modal.subtext'
defaultMessage='By clicking Acknowledge, you will be sharing your information with Mattermost Inc. {link}'
values={{
link: (
<ErrorLink
url={learnMoreLink}
messageId={t('warn_metric_ack_modal.learn_more.link')}
defaultMessage='Learn more'
/>
),
}}
/>
</div>
);
const error = this.renderContactUsError();
const footer = (
<Modal.Footer>
<button
className='btn btn-primary save-button'
data-dismiss='modal'
disabled={this.state.saving}
autoFocus={true}
onClick={this.onContactUsClick}
>
<LoadingWrapper
loading={this.state.saving}
text={Utils.localizeMessage('admin.warn_metric.sending-email', 'Sending email')}
>
<FormattedMessage
id='warn_metric_ack_modal.contact_support'
defaultMessage='Acknowledge'
/>
</LoadingWrapper>
</button>
</Modal.Footer>
);
return (
<Modal
dialogClassName='a11y__modal'
show={this.props.show}
keyboard={false}
onHide={this.onHide}
onExited={this.onHide}
role='dialog'
aria-labelledby='warnMetricAckHeaderModalLabel'
>
<Modal.Header closeButton={true}>
<Modal.Title
componentClass='h1'
id='warnMetricAckHeaderModalLabel'
>
{headerTitle}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
{descriptionText}
<br/>
{error}
<br/>
{subText}
</div>
</Modal.Body>
{footer}
</Modal>
);
}
}
type ErrorLinkProps = {
defaultMessage: string;
messageId: string;
onClickHandler: (e: React.MouseEvent) => Promise<void>;
url: string;
forceAck: boolean;
};
const WarnMetricAckErrorLink: React.FC<ErrorLinkProps> = ({defaultMessage, messageId, onClickHandler, url, forceAck}: ErrorLinkProps) => {
return (
<ExternalLink
href={url}
data-forceAck={forceAck}
onClick={onClickHandler}
location='warn_metric_ack_modal'
>
<FormattedMessage
id={messageId}
defaultMessage={defaultMessage}
/>
</ExternalLink>
);
};

View File

@ -2746,7 +2746,6 @@
"admin.userManagement.userDetail.username": "Username",
"admin.viewArchivedChannelsHelpText": "When true, allows users to view, share and search for content of channels that have been archived. Users can only view the content in channels of which they were a member before the channel was archived.",
"admin.viewArchivedChannelsTitle": "Allow users to view archived channels:",
"admin.warn_metric.sending-email": "Sending email",
"admin.webserverModeDisabled": "Disabled",
"admin.webserverModeDisabledDescription": "The Mattermost server will not serve static files.",
"admin.webserverModeGzip": "gzip",
@ -2826,15 +2825,7 @@
"announcement_bar.error.trial_license_expiring": "There are {days} days left on your free trial.",
"announcement_bar.error.trial_license_expiring_last_day": "This is the last day of your free trial. Purchase a license now to continue using Mattermost Professional and Enterprise features.",
"announcement_bar.error.trial_license_expiring_last_day.short": "This is the last day of your free trial.",
"announcement_bar.error.warn_metric_status.link": "Learn more",
"announcement_bar.notification.email_verified": "Email verified",
"announcement_bar.number_active_users_warn_metric_status.text": "You now have over {limit} users. We strongly recommend using advanced features for large-scale servers.",
"announcement_bar.number_of_posts_warn_metric_status.text": "You now have over {limit} posts. We strongly advise using advanced features to avoid degraded performance.",
"announcement_bar.warn_metric_status_ack.text": "Thank you for contacting Mattermost. We will follow up with you soon.",
"announcement_bar.warn_metric_status.number_of_posts_ack.text": "Thank you for contacting Mattermost. We will follow up with you soon.",
"announcement_bar.warn_metric_status.number_of_posts.text": "You now have over 2,000,000 posts. We strongly advise using advanced features to avoid degraded performance.",
"announcement_bar.warn_metric_status.number_of_users_ack.text": "Thank you for contacting Mattermost. We will follow up with you soon.",
"announcement_bar.warn_metric_status.number_of_users.text": "You now have over 500 users. We strongly recommend using advanced features for large-scale servers.",
"announcement_bar.warn.contact_support_text": "To renew your license, contact support at support@mattermost.com.",
"announcement_bar.warn.email_support": "[Contact support](!{email}).",
"announcement_bar.warn.no_internet_connection": "Looks like you do not have access to the internet.",
@ -5849,15 +5840,6 @@
"view_image.zoom_reset": "Reset Zoom",
"view_user_group_modal.ldapSynced": "AD/LDAP SYNCED",
"view_user_group_modal.memberCount": "{member_count} {member_count, plural, one {Member} other {Members}}",
"warn_metric_ack_modal.contact_support": "Acknowledge",
"warn_metric_ack_modal.learn_more.link": "Learn more",
"warn_metric_ack_modal.mailto.link": "email us",
"warn_metric_ack_modal.mailto.message": "Support could not be reached. Please {link}.",
"warn_metric_ack_modal.number_of_active_users.description": "Mattermost strongly recommends that deployments of over {limit} 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.",
"warn_metric_ack_modal.number_of_posts.description": "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.",
"warn_metric_ack_modal.number_of_posts.header.title": "Improve Performance",
"warn_metric_ack_modal.number_of_users.header.title": "Scaling with Mattermost",
"warn_metric_ack_modal.subtext": "By clicking Acknowledge, you will be sharing your information with Mattermost Inc. {link}",
"web.footer.about": "About",
"web.footer.help": "Help",
"web.footer.privacy": "Privacy Policy",

View File

@ -24,9 +24,6 @@ export default keyMirror({
SET_CONFIG_AND_LICENSE: null,
WARN_METRIC_STATUS_RECEIVED: null,
WARN_METRIC_STATUS_REMOVED: null,
FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED: null,
FIRST_ADMIN_COMPLETE_SETUP_RECEIVED: null,
SHOW_LAUNCHING_WORKSPACE: null,

View File

@ -1033,19 +1033,6 @@ describe('Actions.Admin', () => {
expect(nock.isDone()).toBe(true);
});
it('sendWarnMetricAck', async () => {
const warnMetricAck = {
id: 'metric1',
};
nock(Client4.getBaseRoute()).
post('/warn_metrics/ack/metric1').
reply(200, OK_RESPONSE);
await store.dispatch(Actions.sendWarnMetricAck(warnMetricAck.id, false));
expect(nock.isDone()).toBe(true);
});
it('getDataRetentionCustomPolicies', async () => {
const policies = {
policies: [

View File

@ -560,19 +560,6 @@ export function setSamlIdpCertificateFromMetadata(certData: string) {
});
}
export function sendWarnMetricAck(warnMetricId: string, forceAck: boolean): ActionFuncAsync {
return async (dispatch) => {
try {
Client4.trackEvent('api', 'api_request_send_metric_ack', {warnMetricId});
await Client4.sendWarnMetricAck(warnMetricId, forceAck);
return {data: true};
} catch (e) {
dispatch(logError(e as ServerError));
return {error: (e as ServerError).message};
}
};
}
export function getDataRetentionCustomPolicies(page = 0, perPage = 10): ActionFuncAsync<GetDataRetentionCustomPoliciesRequest> {
return async (dispatch, getState) => {
let data;

View File

@ -31,7 +31,6 @@ export const PostTypes = {
COMBINED_USER_ACTIVITY: 'system_combined_user_activity' as PostType,
ME: 'me' as PostType,
ADD_BOT_TEAMS_CHANNELS: 'add_bot_teams_channels' as PostType,
SYSTEM_WARN_METRIC_STATUS: 'warn_metric_status' as PostType,
REMINDER: 'reminder' as PostType,
WRANGLER: 'system_wrangler' as PostType,
GM_CONVERTED_TO_CHANNEL: 'system_gm_to_channel' as PostType,

View File

@ -47,8 +47,6 @@ const WebsocketEvents = {
RECEIVED_GROUP_NOT_ASSOCIATED_TO_TEAM: 'group_not_associated_to_team',
RECEIVED_GROUP_ASSOCIATED_TO_CHANNEL: 'group_associated_to_channel',
RECEIVED_GROUP_NOT_ASSOCIATED_TO_CHANNEL: 'group_not_associated_to_channel',
WARN_METRIC_STATUS_RECEIVED: 'warn_metric_status_received',
WARN_METRIC_STATUS_REMOVED: 'warn_metric_status_removed',
THREAD_UPDATED: 'thread_updated',
THREAD_FOLLOW_CHANGED: 'thread_follow_changed',
THREAD_READ_CHANGED: 'thread_read_changed',

View File

@ -48,25 +48,6 @@ function serverVersion(state = '', action: AnyAction) {
}
}
function warnMetricsStatus(state: any = {}, action: AnyAction) {
switch (action.type) {
case GeneralTypes.WARN_METRIC_STATUS_RECEIVED: {
const nextState = {...state};
nextState[action.data.id] = action.data;
return nextState;
}
case GeneralTypes.WARN_METRIC_STATUS_REMOVED: {
const nextState = {...state};
const newParams = Object.assign({}, nextState[action.data.id]);
newParams.acked = true;
nextState[action.data.id] = newParams;
return nextState;
}
default:
return state;
}
}
function firstAdminVisitMarketplaceStatus(state = false, action: AnyAction) {
switch (action.type) {
case GeneralTypes.FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED:
@ -91,7 +72,6 @@ export default combineReducers({
config,
license,
serverVersion,
warnMetricsStatus,
firstAdminVisitMarketplaceStatus,
firstAdminCompleteSetup,
});

View File

@ -31,10 +31,6 @@ export const isCloudLicense: (state: GlobalState) => boolean = createSelector(
(license: ClientLicense) => license?.Cloud === 'true',
);
export function warnMetricsStatus(state: GlobalState): any {
return state.entities.general.warnMetricsStatus;
}
export function isCompatibleWithJoinViewTeamPermissions(state: GlobalState): boolean {
const version = state.entities.general.serverVersion;
return isMinimumServerVersion(version, 5, 10, 0) ||

View File

@ -12,7 +12,6 @@ const state: GlobalState = {
config: {},
license: {},
serverVersion: '',
warnMetricsStatus: {},
firstAdminVisitMarketplaceStatus: false,
firstAdminCompleteSetup: false,
},

View File

@ -330,18 +330,6 @@ export const PostRequestTypes = keyMirror({
AFTER_ID: null,
});
export const WarnMetricTypes = {
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_100: 'warn_metric_number_of_active_users_100',
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200: 'warn_metric_number_of_active_users_200',
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_300: 'warn_metric_number_of_active_users_300',
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500: 'warn_metric_number_of_active_users_500',
SYSTEM_WARN_METRIC_NUMBER_OF_TEAMS_5: 'warn_metric_number_of_teams_5',
SYSTEM_WARN_METRIC_NUMBER_OF_CHANNELS_5: 'warn_metric_number_of_channels_50',
SYSTEM_WARN_METRIC_MFA: 'warn_metric_mfa',
SYSTEM_WARN_METRIC_EMAIL_DOMAIN: 'warn_metric_email_domain',
SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M: 'warn_metric_number_of_posts_2M',
};
export const ModalIdentifiers = {
ABOUT: 'about',
TEAM_SETTINGS: 'team_settings',
@ -381,7 +369,6 @@ export const ModalIdentifiers = {
EDIT_CATEGORY: 'edit_category',
DELETE_CATEGORY: 'delete_category',
SIDEBAR_WHATS_NEW_MODAL: 'sidebar_whats_new_modal',
WARN_METRIC_ACK: 'warn_metric_acknowledgement',
UPGRADE_CLOUD_ACCOUNT: 'upgrade_cloud_account',
START_TRIAL_MODAL: 'start_trial_modal',
TRIAL_BENEFITS_MODAL: 'trial_benefits_modal',
@ -638,8 +625,6 @@ export const SocketEvents = {
RECEIVED_GROUP_NOT_ASSOCIATED_TO_TEAM: 'received_group_not_associated_to_team',
RECEIVED_GROUP_ASSOCIATED_TO_CHANNEL: 'received_group_associated_to_channel',
RECEIVED_GROUP_NOT_ASSOCIATED_TO_CHANNEL: 'received_group_not_associated_to_channel',
WARN_METRIC_STATUS_RECEIVED: 'warn_metric_status_received',
WARN_METRIC_STATUS_REMOVED: 'warn_metric_status_removed',
SIDEBAR_CATEGORY_CREATED: 'sidebar_category_created',
SIDEBAR_CATEGORY_UPDATED: 'sidebar_category_updated',
SIDEBAR_CATEGORY_DELETED: 'sidebar_category_deleted',
@ -933,10 +918,6 @@ export const AnnouncementBarMessages = {
LICENSE_PAST_GRACE: t('announcement_bar.error.past_grace'),
PREVIEW_MODE: t('announcement_bar.error.preview_mode'),
WEBSOCKET_PORT_ERROR: t('channel_loader.socketError'),
WARN_METRIC_STATUS_NUMBER_OF_USERS: t('announcement_bar.warn_metric_status.number_of_users.text'),
WARN_METRIC_STATUS_NUMBER_OF_USERS_ACK: t('announcement_bar.warn_metric_status.number_of_users_ack.text'),
WARN_METRIC_STATUS_NUMBER_OF_POSTS: t('announcement_bar.warn_metric_status.number_of_posts.text'),
WARN_METRIC_STATUS_NUMBER_OF_POSTS_ACK: t('announcement_bar.warn_metric_status.number_of_posts_ack.text'),
TRIAL_LICENSE_EXPIRING: t('announcement_bar.error.trial_license_expiring'),
};

View File

@ -2517,20 +2517,6 @@ export default class Client4 {
);
};
getWarnMetricsStatus = async () => {
return this.doFetch(
`${this.getBaseRoute()}/warn_metrics/status`,
{method: 'get'},
);
};
sendWarnMetricAck = async (warnMetricId: string, forceAckVal: boolean) => {
return this.doFetch(
`${this.getBaseRoute()}/warn_metrics/ack/${encodeURI(warnMetricId)}`,
{method: 'post', body: JSON.stringify({forceAck: forceAckVal})},
);
}
setFirstAdminVisitMarketplaceStatus = async () => {
return this.doFetch<StatusOK>(
`${this.getPluginsRoute()}/marketplace/first_admin_visit`,

View File

@ -9,7 +9,6 @@ export type GeneralState = {
firstAdminCompleteSetup: boolean;
license: ClientLicense;
serverVersion: string;
warnMetricsStatus: Record<string, WarnMetricStatus>;
};
export type SystemSetting = {