mirror of
https://github.com/mattermost/mattermost.git
synced 2026-08-31 01:11:29 +08:00
Content flagging notifications (#34092)
* linter fix * WIP * sent post flagging confirmation message * fixed i18n nissues * fixed i18n nissues * CI * WIP * WIP * Added API call * test: add test for Client4.flagPost API call in FlagPostModal * fix: remove userEvent.setup() from flag post modal test * test: wrap submit button click in act for proper state updates * Updated tests * lint fix * Updated test * fix: reset contentFlaggingGroupId for test isolation in content flagging tests * removed cached group ID * removed debug log * CI * Updated to allow special characters in comments * Handled empty comment * Created getContentFlaggingFields API * created getPostPropertyValues API * Used finally * WIP * Created useContentFlaggingFields hook * WIP * WIP * Added option to retain data for reviewers * Displayed deleted post's preview * DIsplayed all properties * Adding field name i18n * WIP - managing i18n able texts * Finished displaying all fields * Manual cleanup * lint fixes * team role filter logic fix * Fixed tests * created new API to fetch flagged posts * lint fix * Added new client methods * test: add comprehensive tests for content flagging APIs * Added new API tests * fixed openapi spec * Fixed DataSpillageReport tests * Fixed PostMarkdown test * Fixed PostPreviewPropertyRenderer test * Added metadata to card renderer * test fixes * Added no comment placeholder * Added view detail button * Created RemoveFlaggedMessageConfirmationModal modal * Added key and remove flag request modal * IMplemented delete flagged post * Handled edge cases of deleting flagged post * keep message * UI integration * Added WS event for post report update and handled deleted files of flagged post * Added error handling in keep/remove forms * i18n fixes * Fixed test * Updated OpenAPI specs * fixed types * fixed types * refactoring * refactor: improve test mocking for data spillage report component * test mock updates * Fixed tests * Updated reducer * not resetting mocks * Added migrations for content flagging tables * Created new structure * review fixes * Used correct ot name * WIP * review fixes * review fixes * Added new property translations * CI * CI * CI * Improved test * fixed test * CI * New UI component * WIP * Updated settings APIs * cached DB data * used cached reviewer data * Updated tests * Lint fixes * test: add tests for saveContentFlaggingSettings and getContentFlaggingSettings APIs * test fix * test: add tests for SaveContentFlaggingConfig and GetContentFlaggingConfigReviewerIDs * Updated tests * test: add content flagging test for local cache layer * test: add comprehensive tests for content flagging store cache * Updated tests * lint fix * Updated mobile text * Added content flagging SQL store mocks * Added API specs for new APIs * fixed tests * feat: add TestContentFlaggingStore function for content flagging store testing * feat: add comprehensive tests for content flagging store * Added SQL store tests * test: add content flagging test for local cache layer * test: add tests for content flagging store caching * Added cache layer tests * Updated tests * Fixed * Handled JSON error * fixes * fixes * Fixed retry layer test * fixerdf i18n * Fixed test * CI * building index concurrently * CI * fixed a test * CI * cleanup * Implemented reviewer search API * feat: add tests for SearchCommonContentFlaggingReviewers and SearchTeamContentFlaggingReviewers * Added store tests * test: add comprehensive tests for SearchReviewers function * feat: add comprehensive tests for searchReviewers endpoint * API tests * Integrate flag post api (#33798) * WIP * WIP * Added API call * test: add test for Client4.flagPost API call in FlagPostModal * fix: remove userEvent.setup() from flag post modal test * test: wrap submit button click in act for proper state updates * Updated tests * lint fix * CI * Updated to allow special characters in comments * Handled empty comment * Used finally * CI * Fixed test * Spillage card integration (#33832) * Created getContentFlaggingFields API * created getPostPropertyValues API * WIP * Created useContentFlaggingFields hook * WIP * WIP * Added option to retain data for reviewers * Displayed deleted post's preview * DIsplayed all properties * Adding field name i18n * WIP - managing i18n able texts * Finished displaying all fields * Manual cleanup * lint fixes * team role filter logic fix * Fixed tests * created new API to fetch flagged posts * lint fix * Added new client methods * test: add comprehensive tests for content flagging APIs * Added new API tests * fixed openapi spec * Fixed DataSpillageReport tests * Fixed PostMarkdown test * Fixed PostPreviewPropertyRenderer test * Added metadata to card renderer * test fixes * Added no comment placeholder * Fixed test * refactor: improve test mocking for data spillage report component * test mock updates * Updated reducer * not resetting mocks * WIP * review fixes * CI * Fixed * fixes * Content flagging actions implementation (#33852) * Added view detail button * Created RemoveFlaggedMessageConfirmationModal modal * Added key and remove flag request modal * IMplemented delete flagged post * Handled edge cases of deleting flagged post * keep message * UI integration * Added WS event for post report update and handled deleted files of flagged post * Added error handling in keep/remove forms * i18n fixes * Updated OpenAPI specs * fixed types * fixed types * refactoring * Fixed tests * review fixes * Added new property translations * Improved test * fixed test * CI * fixes * CI * fixed a test * fixed abad commit * CI * WIP * IMplemented assign reviewer API * Display reviewers * Review fixes * UI integration * lint fix * Added API docs * test: add comprehensive tests for assignFlaggedPostReviewer function * test: add comprehensive tests for AssignFlaggedPostReviewer * Added tests * Fixed test * Sequential tests * minor improvemenmts * WIP * Added keep/delete message notifications * refactor: update AssignFlaggedPostReviewer method signature to include context * test: add tests for getReviewerPostsForFlaggedPost and postReviewerMessage * lint fixes * handled reviewer updates * Handled preference * Implemented notifications * test: add comprehensive tests for content flagging notification functions * refactor: Replace th.UpdateConfig with SaveContentFlaggingConfig in tests * test: add test case for content flagging with string comparison * refactor: simplify content flagging test config setup * refactor: Update content flagging notification settings types in test cases * refactor: Update content flagging tests to use exact message matching * Added tests * lint fixes * review fixes * Fixed a test * Fixed a test * review fixes * Reduced code duplication * Refactored tests to reduce duplication * review fixes * lint fix * Review fixes * WIP * test: add comprehensive tests for sendFlaggedPostRemovalNotification and sendKeepFlaggedPostNotification * Updated tests * review fixes * Updated logs * i18n fixes
This commit is contained in:
@@ -138,7 +138,7 @@ func (a *App) FlagPost(rctx request.CTX, post *model.Post, teamId, reportingUser
|
||||
|
||||
_, err = a.Srv().propertyService.CreatePropertyValues(propertyValues)
|
||||
if err != nil {
|
||||
return model.NewAppError("FlagPostForContentReview", "app.content_flagging.create_property_values.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("FlagPost", "app.content_flagging.create_property_values.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
contentReviewBot, appErr := a.getContentReviewBot(rctx)
|
||||
@@ -149,7 +149,7 @@ func (a *App) FlagPost(rctx request.CTX, post *model.Post, teamId, reportingUser
|
||||
if *a.Config().ContentFlaggingSettings.AdditionalSettings.HideFlaggedContent {
|
||||
_, appErr = a.DeletePost(rctx, post.Id, contentReviewBot.UserId)
|
||||
if appErr != nil {
|
||||
return model.NewAppError("FlagPostForContentReview", "app.content_flagging.delete_post.app_error", nil, appErr.Error(), http.StatusInternalServerError).Wrap(appErr)
|
||||
return appErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,13 +165,19 @@ func (a *App) FlagPost(rctx request.CTX, post *model.Post, teamId, reportingUser
|
||||
}
|
||||
})
|
||||
|
||||
a.Srv().Go(func() {
|
||||
if appErr := a.sendFlagPostNotification(rctx, post); appErr != nil {
|
||||
rctx.Logger().Error("Failed to send flag post notification", mlog.Err(appErr), mlog.String("post_id", post.Id))
|
||||
}
|
||||
})
|
||||
|
||||
return a.sendContentFlaggingConfirmationMessage(rctx, reportingUserId, post.UserId, post.ChannelId)
|
||||
}
|
||||
|
||||
func (a *App) ContentFlaggingGroupId() (string, *model.AppError) {
|
||||
group, err := a.Srv().propertyService.GetPropertyGroup(model.ContentFlaggingGroupName)
|
||||
if err != nil {
|
||||
return "", model.NewAppError("getContentFlaggingGroupId", "app.content_flagging.get_group.error", nil, err.Error(), http.StatusInternalServerError)
|
||||
return "", model.NewAppError("getContentFlaggingGroupId", "app.content_flagging.get_group.error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
return group.ID, nil
|
||||
}
|
||||
@@ -414,7 +420,7 @@ func (a *App) getAllUsersInTeamForRoles(teamId string, systemRoles, teamRoles []
|
||||
|
||||
additionalReviewers, err := utils.Pager(fetchFunc, options.PerPage)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("getReviewersForTeam", "app.content_flagging.get_users_in_team.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return nil, model.NewAppError("getAllUsersInTeamForRoles", "app.content_flagging.get_users_in_team.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return additionalReviewers, nil
|
||||
@@ -498,7 +504,7 @@ func (a *App) PermanentDeleteFlaggedPost(rctx request.CTX, actionRequest *model.
|
||||
|
||||
statusValue := strings.Trim(string(status.Value), `"`)
|
||||
if statusValue != model.ContentFlaggingStatusPending && statusValue != model.ContentFlaggingStatusAssigned {
|
||||
return model.NewAppError("removeFlaggedPost", "api.content_flagging.error.post_not_in_progress", nil, "", http.StatusBadRequest)
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "api.content_flagging.error.post_not_in_progress", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
editHistories, appErr := a.GetEditHistoryForPost(flaggedPost.Id)
|
||||
@@ -539,7 +545,7 @@ func (a *App) PermanentDeleteFlaggedPost(rctx request.CTX, actionRequest *model.
|
||||
scrubPost(flaggedPost)
|
||||
_, err := a.Srv().Store().Post().Overwrite(rctx, flaggedPost)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.permanently_delete.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.permanently_delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
groupId, appErr := a.ContentFlaggingGroupId()
|
||||
@@ -578,13 +584,13 @@ func (a *App) PermanentDeleteFlaggedPost(rctx request.CTX, actionRequest *model.
|
||||
|
||||
_, err = a.Srv().propertyService.CreatePropertyValues(propertyValues)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.create_property_values.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.create_property_values.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
status.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
||||
_, err = a.Srv().propertyService.UpdatePropertyValue(groupId, status)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.permanently_delete.update_property_value.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.permanently_delete.update_property_value.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
a.Srv().Go(func() {
|
||||
@@ -601,10 +607,7 @@ func (a *App) PermanentDeleteFlaggedPost(rctx request.CTX, actionRequest *model.
|
||||
})
|
||||
|
||||
a.Srv().Go(func() {
|
||||
postErr := a.postDeletePostReviewerMessage(rctx, flaggedPost.Id, reviewerId, actionRequest.Comment, groupId)
|
||||
if postErr != nil {
|
||||
rctx.Logger().Error("Failed to post delete post reviewer message after permanently removing flagged post", mlog.Err(postErr), mlog.String("post_id", flaggedPost.Id))
|
||||
}
|
||||
a.sendFlaggedPostRemovalNotification(rctx, flaggedPost, reviewerId, actionRequest.Comment, groupId)
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -621,7 +624,7 @@ func (a *App) KeepFlaggedPost(rctx request.CTX, actionRequest *model.FlagContent
|
||||
|
||||
statusValue := strings.Trim(string(status.Value), `"`)
|
||||
if statusValue != model.ContentFlaggingStatusPending && statusValue != model.ContentFlaggingStatusAssigned {
|
||||
return model.NewAppError("removeFlaggedPost", "api.content_flagging.error.post_not_in_progress", nil, "", http.StatusBadRequest)
|
||||
return model.NewAppError("KeepFlaggedPost", "api.content_flagging.error.post_not_in_progress", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if flaggedPost.DeleteAt > 0 {
|
||||
@@ -630,12 +633,12 @@ func (a *App) KeepFlaggedPost(rctx request.CTX, actionRequest *model.FlagContent
|
||||
flaggedPost.PreCommit()
|
||||
_, err := a.Srv().Store().Post().Overwrite(rctx, flaggedPost)
|
||||
if err != nil {
|
||||
return model.NewAppError("KeepFlaggedPost", "app.content_flagging.keep_post.undelete.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("KeepFlaggedPost", "app.content_flagging.keep_post.undelete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
err = a.Srv().Store().FileInfo().RestoreForPostByIds(rctx, flaggedPost.Id, flaggedPost.FileIds)
|
||||
if err != nil {
|
||||
return model.NewAppError("KeepFlaggedPost", "app.content_flagging.restore_file_info.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("KeepFlaggedPost", "app.content_flagging.restore_file_info.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,13 +686,13 @@ func (a *App) KeepFlaggedPost(rctx request.CTX, actionRequest *model.FlagContent
|
||||
|
||||
_, err = a.Srv().propertyService.CreatePropertyValues(propertyValues)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.create_property_values.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("KeepFlaggedPost", "app.content_flagging.create_property_values.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
status.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
||||
_, err = a.Srv().propertyService.UpdatePropertyValue(groupId, status)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.keep_post.status_update.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("KeepFlaggedPost", "app.content_flagging.keep_post.status_update.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
a.Srv().Go(func() {
|
||||
@@ -713,12 +716,16 @@ func (a *App) KeepFlaggedPost(rctx request.CTX, actionRequest *model.FlagContent
|
||||
a.invalidateCacheForChannelPosts(flaggedPost.ChannelId)
|
||||
|
||||
a.Srv().Go(func() {
|
||||
postErr := a.postKeepPostReviewerMessage(rctx, flaggedPost.Id, reviewerId, actionRequest.Comment, groupId)
|
||||
_, postErr := a.postKeepPostReviewerMessage(rctx, flaggedPost.Id, reviewerId, actionRequest.Comment, groupId)
|
||||
if postErr != nil {
|
||||
rctx.Logger().Error("Failed to post keep post reviewer message after retaining flagged post", mlog.Err(postErr), mlog.String("post_id", flaggedPost.Id))
|
||||
}
|
||||
})
|
||||
|
||||
a.Srv().Go(func() {
|
||||
a.sendKeepFlaggedPostNotification(rctx, flaggedPost, reviewerId, actionRequest.Comment, groupId)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -739,7 +746,7 @@ func (a *App) publishContentFlaggingReportUpdateEvent(targetId, teamId string, p
|
||||
|
||||
bytes, err := json.Marshal(propertyValues)
|
||||
if err != nil {
|
||||
return model.NewAppError("publishContentFlaggingReportUpdateEvent", "app.content_flagging.marshal_property_values.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("publishContentFlaggingReportUpdateEvent", "app.content_flagging.marshal_property_values.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
for _, userId := range reviewersUserIDs {
|
||||
@@ -788,7 +795,7 @@ func (a *App) clearContentFlaggingConfigCache() {
|
||||
func (a *App) GetContentFlaggingConfigReviewerIDs() (*model.ReviewerIDsSettings, *model.AppError) {
|
||||
reviewerSettings, err := a.Srv().Store().ContentFlagging().GetReviewerSettings()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetContentFlaggingConfig", "app.content_flagging.get_reviewer_settings.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return nil, model.NewAppError("GetContentFlaggingConfigReviewerIDs", "app.content_flagging.get_reviewer_settings.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return reviewerSettings, nil
|
||||
@@ -896,19 +903,19 @@ func (a *App) AssignFlaggedPostReviewer(rctx request.CTX, flaggedPostId, flagged
|
||||
|
||||
assigneePropertyValue, err := a.Srv().propertyService.UpsertPropertyValue(assigneePropertyValue)
|
||||
if err != nil {
|
||||
return model.NewAppError("AssignFlaggedPostReviewer", "app.content_flagging.assign_reviewer.upsert_property_value.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("AssignFlaggedPostReviewer", "app.content_flagging.assign_reviewer.upsert_property_value.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if status == model.ContentFlaggingStatusPending {
|
||||
statusPropertyValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusAssigned))
|
||||
statusPropertyValue, err = a.Srv().propertyService.UpdatePropertyValue(groupId, statusPropertyValue)
|
||||
if err != nil {
|
||||
return model.NewAppError("AssignFlaggedPostReviewer", "app.content_flagging.assign_reviewer.update_status_property_value.app_error", nil, err.Error(), http.StatusInternalServerError).Wrap(err)
|
||||
return model.NewAppError("AssignFlaggedPostReviewer", "app.content_flagging.assign_reviewer.update_status_property_value.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
a.Srv().Go(func() {
|
||||
postErr := a.postAssignReviewerMessage(rctx, groupId, flaggedPostId, reviewerId, assigneeId)
|
||||
_, postErr := a.postAssignReviewerMessage(rctx, groupId, flaggedPostId, reviewerId, assigneeId)
|
||||
if postErr != nil {
|
||||
rctx.Logger().Error("Failed to post assign reviewer message", mlog.Err(postErr), mlog.String("flagged_post_id", flaggedPostId), mlog.String("reviewer_id", reviewerId), mlog.String("assignee_id", assigneeId))
|
||||
}
|
||||
@@ -924,19 +931,19 @@ func (a *App) AssignFlaggedPostReviewer(rctx request.CTX, flaggedPostId, flagged
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) postAssignReviewerMessage(rctx request.CTX, contentFlaggingGroupId, flaggedPostId, reviewerId, assignedById string) *model.AppError {
|
||||
func (a *App) postAssignReviewerMessage(rctx request.CTX, contentFlaggingGroupId, flaggedPostId, reviewerId, assignedById string) ([]*model.Post, *model.AppError) {
|
||||
notificationSettings := a.Config().ContentFlaggingSettings.NotificationSettings
|
||||
if notificationSettings == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !slices.Contains(notificationSettings.EventTargetMapping[model.EventAssigned], model.TargetReviewers) {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
reviewerUser, appErr := a.GetUser(reviewerId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
var assignedByUser *model.User
|
||||
@@ -945,7 +952,7 @@ func (a *App) postAssignReviewerMessage(rctx request.CTX, contentFlaggingGroupId
|
||||
} else {
|
||||
assignedByUser, appErr = a.GetUser(assignedById)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
return nil, appErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,10 +960,10 @@ func (a *App) postAssignReviewerMessage(rctx request.CTX, contentFlaggingGroupId
|
||||
return a.postReviewerMessage(rctx, message, contentFlaggingGroupId, flaggedPostId)
|
||||
}
|
||||
|
||||
func (a *App) postDeletePostReviewerMessage(rctx request.CTX, flaggedPostId, actorUserId, comment, contentFlaggingGroupId string) *model.AppError {
|
||||
func (a *App) postDeletePostReviewerMessage(rctx request.CTX, flaggedPostId, actorUserId, comment, contentFlaggingGroupId string) ([]*model.Post, *model.AppError) {
|
||||
actorUser, appErr := a.GetUser(actorUserId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("The flagged message was removed by @%s", actorUser.Username)
|
||||
@@ -967,10 +974,10 @@ func (a *App) postDeletePostReviewerMessage(rctx request.CTX, flaggedPostId, act
|
||||
return a.postReviewerMessage(rctx, message, contentFlaggingGroupId, flaggedPostId)
|
||||
}
|
||||
|
||||
func (a *App) postKeepPostReviewerMessage(rctx request.CTX, flaggedPostId, actorUserId, comment, contentFlaggingGroupId string) *model.AppError {
|
||||
func (a *App) postKeepPostReviewerMessage(rctx request.CTX, flaggedPostId, actorUserId, comment, contentFlaggingGroupId string) ([]*model.Post, *model.AppError) {
|
||||
actorUser, appErr := a.GetUser(actorUserId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("The flagged message was retained by @%s", actorUser.Username)
|
||||
@@ -981,27 +988,94 @@ func (a *App) postKeepPostReviewerMessage(rctx request.CTX, flaggedPostId, actor
|
||||
return a.postReviewerMessage(rctx, message, contentFlaggingGroupId, flaggedPostId)
|
||||
}
|
||||
|
||||
func (a *App) postReviewerMessage(rctx request.CTX, message, contentFlaggingGroupId, flaggedPostId string) *model.AppError {
|
||||
func (a *App) getReporterUserId(flaggedPostId, contentFlaggingGroupId string) (string, *model.AppError) {
|
||||
mappedFields, appErr := a.GetContentFlaggingMappedFields(contentFlaggingGroupId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
return "", appErr
|
||||
}
|
||||
|
||||
flaggedPostIdField, ok := mappedFields[contentFlaggingPropertyNameFlaggedPostId]
|
||||
reporterUserIdField, ok := mappedFields[contentFlaggingPropertyNameReportingUserID]
|
||||
if !ok {
|
||||
return model.NewAppError("postAssignReviewerMessage", "app.content_flagging.missing_flagged_post_id_field.app_error", nil, "", http.StatusInternalServerError)
|
||||
return "", model.NewAppError("getReporterUserId", "app.content_flagging.missing_reporting_user_id_field.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
postIds, appErr := a.getReviewerPostsForFlaggedPost(contentFlaggingGroupId, flaggedPostId, flaggedPostIdField.ID)
|
||||
propertyValues, appErr := a.GetPostContentFlaggingPropertyValues(flaggedPostId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
return "", appErr
|
||||
}
|
||||
|
||||
var reporterPropertyValue *model.PropertyValue
|
||||
for _, pv := range propertyValues {
|
||||
if pv.FieldID == reporterUserIdField.ID {
|
||||
reporterPropertyValue = pv
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if reporterPropertyValue == nil {
|
||||
return "", model.NewAppError("getReporterUserId", "app.content_flagging.missing_reporting_user_id_property_value.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
reporterUserId := strings.Trim(string(reporterPropertyValue.Value), `"`)
|
||||
return reporterUserId, nil
|
||||
}
|
||||
|
||||
func (a *App) postContentReviewBotMessage(rctx request.CTX, flaggedPost *model.Post, messageTemplate string, flaggedPostAuthorUserId string) (*model.Post, *model.AppError) {
|
||||
channel, appErr := a.GetChannel(rctx, flaggedPost.ChannelId)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
contentReviewBot, appErr := a.getContentReviewBot(rctx)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
dmChannel, appErr := a.GetOrCreateDirectChannel(rctx, flaggedPostAuthorUserId, contentReviewBot.UserId)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
post := &model.Post{
|
||||
Message: fmt.Sprintf(messageTemplate, flaggedPost.Id, channel.DisplayName),
|
||||
UserId: contentReviewBot.UserId,
|
||||
ChannelId: dmChannel.Id,
|
||||
}
|
||||
|
||||
return a.CreatePost(rctx, post, dmChannel, model.CreatePostFlags{})
|
||||
}
|
||||
|
||||
func (a *App) postMessageToReporter(rctx request.CTX, contentFlaggingGroupId string, flaggedPost *model.Post, messageTemplate string) (*model.Post, *model.AppError) {
|
||||
userId, appErr := a.getReporterUserId(flaggedPost.Id, contentFlaggingGroupId)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return a.postContentReviewBotMessage(rctx, flaggedPost, messageTemplate, userId)
|
||||
}
|
||||
|
||||
func (a *App) postReviewerMessage(rctx request.CTX, message, contentFlaggingGroupId, flaggedPostId string) ([]*model.Post, *model.AppError) {
|
||||
mappedFields, appErr := a.GetContentFlaggingMappedFields(contentFlaggingGroupId)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
flaggedPostIdField, ok := mappedFields[contentFlaggingPropertyNameFlaggedPostId]
|
||||
if !ok {
|
||||
return nil, model.NewAppError("postReviewerMessage", "app.content_flagging.missing_flagged_post_id_field.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
postIds, appErr := a.getReviewerPostsForFlaggedPost(contentFlaggingGroupId, flaggedPostId, flaggedPostIdField.ID)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
contentReviewBot, appErr := a.getContentReviewBot(rctx)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
createdPosts := make([]*model.Post, 0, len(postIds))
|
||||
for _, postId := range postIds {
|
||||
reviewerPost, appErr := a.GetSinglePost(rctx, postId, false)
|
||||
if appErr != nil {
|
||||
@@ -1022,14 +1096,15 @@ func (a *App) postReviewerMessage(rctx request.CTX, message, contentFlaggingGrou
|
||||
RootId: postId,
|
||||
}
|
||||
|
||||
_, appErr = a.CreatePost(rctx, post, channel, model.CreatePostFlags{})
|
||||
createdPost, appErr := a.CreatePost(rctx, post, channel, model.CreatePostFlags{})
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Failed to create assign reviewer post in one of the channels", mlog.Err(appErr), mlog.String("channel_id", channel.Id), mlog.String("post_id", postId))
|
||||
continue
|
||||
}
|
||||
createdPosts = append(createdPosts, createdPost)
|
||||
}
|
||||
|
||||
return nil
|
||||
return createdPosts, nil
|
||||
}
|
||||
|
||||
func (a *App) getReviewerPostsForFlaggedPost(contentFlaggingGroupId, flaggedPostId, flaggedPostIdFieldId string) ([]string, *model.AppError) {
|
||||
@@ -1066,3 +1141,121 @@ func (a *App) getReviewerPostsForFlaggedPost(contentFlaggingGroupId, flaggedPost
|
||||
|
||||
return reviewerPostIds, nil
|
||||
}
|
||||
|
||||
func (a *App) sendFlagPostNotification(rctx request.CTX, flaggedPost *model.Post) *model.AppError {
|
||||
notificationSettings := a.Config().ContentFlaggingSettings.NotificationSettings
|
||||
flagPostNotifications := notificationSettings.EventTargetMapping[model.EventFlagged]
|
||||
if flagPostNotifications == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !slices.Contains(flagPostNotifications, model.TargetAuthor) {
|
||||
return nil
|
||||
}
|
||||
|
||||
channel, appErr := a.GetChannel(rctx, flaggedPost.ChannelId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
contentReviewBot, appErr := a.getContentReviewBot(rctx)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
dmChannel, appErr := a.GetOrCreateDirectChannel(rctx, flaggedPost.UserId, contentReviewBot.UserId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
post := &model.Post{
|
||||
Message: fmt.Sprintf("Your post having ID `%s` in the channel `%s` has been flagged for review.", flaggedPost.Id, channel.DisplayName),
|
||||
UserId: contentReviewBot.UserId,
|
||||
ChannelId: dmChannel.Id,
|
||||
}
|
||||
|
||||
_, appErr = a.CreatePost(rctx, post, dmChannel, model.CreatePostFlags{})
|
||||
return appErr
|
||||
}
|
||||
|
||||
func (a *App) sendFlaggedPostRemovalNotification(rctx request.CTX, flaggedPost *model.Post, actorUserId, comment, contentFlaggingGroupId string) []*model.Post {
|
||||
notificationSettings := a.Config().ContentFlaggingSettings.NotificationSettings
|
||||
deletePostNotifications := notificationSettings.EventTargetMapping[model.EventContentRemoved]
|
||||
if deletePostNotifications == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var createdPosts []*model.Post
|
||||
|
||||
if slices.Contains(deletePostNotifications, model.TargetReviewers) {
|
||||
posts, appErr := a.postDeletePostReviewerMessage(rctx, flaggedPost.Id, actorUserId, comment, contentFlaggingGroupId)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Failed to post delete post reviewer message after permanently removing flagged post", mlog.Err(appErr), mlog.String("post_id", flaggedPost.Id))
|
||||
} else {
|
||||
createdPosts = posts
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(deletePostNotifications, model.TargetAuthor) {
|
||||
template := "Your post having ID `%s` in the channel `%s` which was flagged for review has been permanently removed by a reviewer."
|
||||
post, appErr := a.postContentReviewBotMessage(rctx, flaggedPost, template, flaggedPost.UserId)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Failed to post delete post author message after permanently removing flagged post", mlog.Err(appErr), mlog.String("post_id", flaggedPost.Id))
|
||||
} else {
|
||||
createdPosts = append(createdPosts, post)
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(deletePostNotifications, model.TargetReporter) {
|
||||
template := "The post having ID `%s` in the channel `%s` which you flagged for review has been permanently removed by a reviewer."
|
||||
post, appErr := a.postMessageToReporter(rctx, contentFlaggingGroupId, flaggedPost, template)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Failed to post delete post reporter message after permanently removing flagged post", mlog.Err(appErr), mlog.String("post_id", flaggedPost.Id))
|
||||
} else {
|
||||
createdPosts = append(createdPosts, post)
|
||||
}
|
||||
}
|
||||
|
||||
return createdPosts
|
||||
}
|
||||
|
||||
func (a *App) sendKeepFlaggedPostNotification(rctx request.CTX, flaggedPost *model.Post, actorUserId, comment, contentFlaggingGroupId string) []*model.Post {
|
||||
notificationSettings := a.Config().ContentFlaggingSettings.NotificationSettings
|
||||
keepPostNotifications := notificationSettings.EventTargetMapping[model.EventContentDismissed]
|
||||
if keepPostNotifications == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var createdPosts []*model.Post
|
||||
|
||||
if slices.Contains(keepPostNotifications, model.TargetReviewers) {
|
||||
posts, appErr := a.postKeepPostReviewerMessage(rctx, flaggedPost.Id, actorUserId, comment, contentFlaggingGroupId)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Failed to post retain post reviewer message after restoring flagged post", mlog.Err(appErr), mlog.String("post_id", flaggedPost.Id))
|
||||
} else {
|
||||
createdPosts = posts
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(keepPostNotifications, model.TargetAuthor) {
|
||||
template := "Your post having ID `%s` in the channel `%s` which was flagged for review has been restored by a reviewer."
|
||||
post, appErr := a.postContentReviewBotMessage(rctx, flaggedPost, template, flaggedPost.UserId)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Failed to post retain post author message after restoring flagged post", mlog.Err(appErr), mlog.String("post_id", flaggedPost.Id))
|
||||
} else {
|
||||
createdPosts = append(createdPosts, post)
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(keepPostNotifications, model.TargetReporter) {
|
||||
template := "The post having ID `%s` in the channel `%s` which you flagged for review has been restored by a reviewer."
|
||||
post, appErr := a.postMessageToReporter(rctx, contentFlaggingGroupId, flaggedPost, template)
|
||||
if appErr != nil {
|
||||
rctx.Logger().Error("Failed to post retain post reporter message after restoring flagged post", mlog.Err(appErr), mlog.String("post_id", flaggedPost.Id))
|
||||
} else {
|
||||
createdPosts = append(createdPosts, post)
|
||||
}
|
||||
}
|
||||
|
||||
return createdPosts
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -13,6 +14,46 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getBaseConfig(th *TestHelper) model.ContentFlaggingSettingsRequest {
|
||||
config := model.ContentFlaggingSettingsRequest{}
|
||||
config.SetDefaults()
|
||||
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
||||
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
||||
config.ReviewerSettings.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
||||
config.ReviewerSettings.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
||||
config.AdditionalSettings.ReporterCommentRequired = model.NewPointer(false)
|
||||
config.AdditionalSettings.HideFlaggedContent = model.NewPointer(false)
|
||||
config.AdditionalSettings.Reasons = &[]string{"spam", "harassment", "inappropriate"}
|
||||
return config
|
||||
}
|
||||
|
||||
func setBaseConfig(th *TestHelper) *model.AppError {
|
||||
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig(th))
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupFlaggedPost(th *TestHelper) (*model.Post, *model.AppError) {
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
flagData := model.FlagContentRequest{
|
||||
Reason: "spam",
|
||||
Comment: "This is spam content",
|
||||
}
|
||||
|
||||
appErr := th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
return post, nil
|
||||
}
|
||||
|
||||
func TestContentFlaggingEnabledForTeam(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t)
|
||||
@@ -117,38 +158,13 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
|
||||
getBaseConfig := func() model.ContentFlaggingSettingsRequest {
|
||||
config := model.ContentFlaggingSettingsRequest{}
|
||||
config.SetDefaults()
|
||||
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
||||
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
||||
config.AdditionalSettings.ReporterCommentRequired = model.NewPointer(false)
|
||||
config.AdditionalSettings.HideFlaggedContent = model.NewPointer(false)
|
||||
config.AdditionalSettings.Reasons = &[]string{"spam", "harassment", "inappropriate"}
|
||||
return config
|
||||
}
|
||||
|
||||
setupFlaggedPost := func() *model.Post {
|
||||
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
flagData := model.FlagContentRequest{
|
||||
Reason: "spam",
|
||||
Comment: "This is spam content",
|
||||
}
|
||||
|
||||
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
t.Run("should successfully assign reviewer to pending flagged post", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Verify status was updated to assigned
|
||||
@@ -174,10 +190,13 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should successfully reassign reviewer to already assigned flagged post", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// First assignment
|
||||
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Second assignment (reassignment)
|
||||
@@ -207,6 +226,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should fail when trying to assign reviewer to non-flagged post", func(t *testing.T) {
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
@@ -215,13 +235,16 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should fail when trying to assign reviewer to retained post", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// First retain the post
|
||||
actionRequest := &model.FlagContentActionRequest{
|
||||
Comment: "Keeping this post",
|
||||
}
|
||||
appErr := th.App.KeepFlaggedPost(th.Context, actionRequest, th.BasicUser.Id, post)
|
||||
appErr = th.App.KeepFlaggedPost(th.Context, actionRequest, th.BasicUser.Id, post)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Try to assign reviewer to retained post
|
||||
@@ -232,13 +255,16 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should fail when trying to assign reviewer to removed post", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// First remove the post
|
||||
actionRequest := &model.FlagContentActionRequest{
|
||||
Comment: "Removing this post",
|
||||
}
|
||||
appErr := th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.BasicUser.Id, post)
|
||||
appErr = th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.BasicUser.Id, post)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Try to assign reviewer to removed post
|
||||
@@ -249,10 +275,13 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should handle assignment with same reviewer ID", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Assign reviewer
|
||||
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Assign same reviewer again
|
||||
@@ -282,9 +311,12 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should handle assignment with empty reviewer ID", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, "", th.SystemAdminUser.Id)
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, "", th.SystemAdminUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Verify status was updated to assigned
|
||||
@@ -310,6 +342,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should handle assignment with invalid post ID", func(t *testing.T) {
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
appErr := th.App.AssignFlaggedPostReviewer(th.Context, "invalid_post_id", th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
||||
@@ -1087,13 +1120,11 @@ func TestFlagPost(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should fail when comment is required but not provided", func(t *testing.T) {
|
||||
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
||||
config := getBaseConfig()
|
||||
config.AdditionalSettings.ReporterCommentRequired = model.NewPointer(true)
|
||||
appErr := th.App.SaveContentFlaggingConfig(config)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th.UpdateConfig(func(conf *model.Config) {
|
||||
conf.ContentFlaggingSettings.AdditionalSettings.ReporterCommentRequired = model.NewPointer(true)
|
||||
})
|
||||
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
flagData := model.FlagContentRequest{
|
||||
@@ -1103,11 +1134,6 @@ func TestFlagPost(t *testing.T) {
|
||||
|
||||
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
||||
require.NotNil(t, appErr)
|
||||
|
||||
// Reset config
|
||||
th.UpdateConfig(func(conf *model.Config) {
|
||||
conf.ContentFlaggingSettings.AdditionalSettings.ReporterCommentRequired = model.NewPointer(false)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should fail when trying to flag already flagged post", func(t *testing.T) {
|
||||
@@ -1132,13 +1158,11 @@ func TestFlagPost(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should hide flagged content when configured", func(t *testing.T) {
|
||||
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
||||
config := getBaseConfig()
|
||||
config.AdditionalSettings.HideFlaggedContent = model.NewPointer(true)
|
||||
appErr := th.App.SaveContentFlaggingConfig(config)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th.UpdateConfig(func(conf *model.Config) {
|
||||
conf.ContentFlaggingSettings.AdditionalSettings.HideFlaggedContent = model.NewPointer(true)
|
||||
})
|
||||
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
flagData := model.FlagContentRequest{
|
||||
@@ -1153,11 +1177,6 @@ func TestFlagPost(t *testing.T) {
|
||||
deletedPost, appErr := th.App.GetSinglePost(th.Context, post.Id, false)
|
||||
require.NotNil(t, appErr)
|
||||
require.Nil(t, deletedPost)
|
||||
|
||||
// Reset config
|
||||
th.UpdateConfig(func(conf *model.Config) {
|
||||
conf.ContentFlaggingSettings.AdditionalSettings.HideFlaggedContent = model.NewPointer(false)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should create content review post for reviewers", func(t *testing.T) {
|
||||
@@ -1569,39 +1588,11 @@ func TestGetReviewerPostsForFlaggedPost(t *testing.T) {
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
|
||||
getBaseConfig := func() model.ContentFlaggingSettingsRequest {
|
||||
config := model.ContentFlaggingSettingsRequest{}
|
||||
config.SetDefaults()
|
||||
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
||||
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
||||
config.AdditionalSettings.ReporterCommentRequired = model.NewPointer(false)
|
||||
config.AdditionalSettings.HideFlaggedContent = model.NewPointer(false)
|
||||
config.AdditionalSettings.Reasons = &[]string{"spam", "harassment", "inappropriate"}
|
||||
return config
|
||||
}
|
||||
|
||||
setupFlaggedPost := func() *model.Post {
|
||||
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
flagData := model.FlagContentRequest{
|
||||
Reason: "spam",
|
||||
Comment: "This is spam content",
|
||||
}
|
||||
|
||||
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
t.Run("should return reviewer posts for flagged post", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
// Wait for async reviewer post creation to complete
|
||||
time.Sleep(2 * time.Second)
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
@@ -1625,6 +1616,7 @@ func TestGetReviewerPostsForFlaggedPost(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should return empty list when no reviewer posts exist", func(t *testing.T) {
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
@@ -1643,7 +1635,7 @@ func TestGetReviewerPostsForFlaggedPost(t *testing.T) {
|
||||
|
||||
t.Run("should handle multiple reviewer posts for same flagged post", func(t *testing.T) {
|
||||
// Create a config with multiple reviewers
|
||||
config := getBaseConfig()
|
||||
config := getBaseConfig(th)
|
||||
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id, th.BasicUser2.Id}
|
||||
appErr := th.App.SaveContentFlaggingConfig(config)
|
||||
require.Nil(t, appErr)
|
||||
@@ -1685,6 +1677,7 @@ func TestGetReviewerPostsForFlaggedPost(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should handle invalid flagged post ID", func(t *testing.T) {
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
@@ -1707,50 +1700,19 @@ func TestPostReviewerMessage(t *testing.T) {
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
|
||||
getBaseConfig := func() model.ContentFlaggingSettingsRequest {
|
||||
config := model.ContentFlaggingSettingsRequest{}
|
||||
config.SetDefaults()
|
||||
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
||||
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
||||
config.AdditionalSettings.ReporterCommentRequired = model.NewPointer(false)
|
||||
config.AdditionalSettings.HideFlaggedContent = model.NewPointer(false)
|
||||
config.AdditionalSettings.Reasons = &[]string{"spam", "harassment", "inappropriate"}
|
||||
return config
|
||||
}
|
||||
|
||||
setupFlaggedPost := func() *model.Post {
|
||||
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
flagData := model.FlagContentRequest{
|
||||
Reason: "spam",
|
||||
Comment: "This is spam content",
|
||||
}
|
||||
|
||||
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
t.Run("should post reviewer message to thread", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
// Wait for async reviewer post creation to complete
|
||||
time.Sleep(2 * time.Second)
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
testMessage := "Test reviewer message"
|
||||
appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
||||
_, appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Wait for async message posting to complete
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Verify message was posted to the reviewer thread
|
||||
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
||||
require.Nil(t, appErr)
|
||||
@@ -1783,7 +1745,7 @@ func TestPostReviewerMessage(t *testing.T) {
|
||||
|
||||
t.Run("should handle multiple reviewer channels", func(t *testing.T) {
|
||||
// Create a config with multiple reviewers
|
||||
config := getBaseConfig()
|
||||
config := getBaseConfig(th)
|
||||
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id, th.BasicUser2.Id}
|
||||
appErr := th.App.SaveContentFlaggingConfig(config)
|
||||
require.Nil(t, appErr)
|
||||
@@ -1805,12 +1767,9 @@ func TestPostReviewerMessage(t *testing.T) {
|
||||
require.Nil(t, appErr)
|
||||
|
||||
testMessage := "Test message for multiple reviewers"
|
||||
appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
||||
_, appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Wait for async message posting to complete
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Verify message was posted to both reviewer threads
|
||||
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
||||
require.Nil(t, appErr)
|
||||
@@ -1858,35 +1817,30 @@ func TestPostReviewerMessage(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should handle case when no reviewer posts exist", func(t *testing.T) {
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
post := th.CreatePost(th.BasicChannel)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
testMessage := "Test message for non-flagged post"
|
||||
appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
||||
_, appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Should not error, but no messages should be posted since no reviewer posts exist
|
||||
// This is a graceful handling case
|
||||
})
|
||||
|
||||
t.Run("should handle message with special characters", func(t *testing.T) {
|
||||
post := setupFlaggedPost()
|
||||
require.Nil(t, setBaseConfig(th))
|
||||
|
||||
// Wait for async reviewer post creation to complete
|
||||
time.Sleep(2 * time.Second)
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
testMessage := "Test message with special chars: @user #channel ~team & <script>alert('xss')</script>"
|
||||
appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
||||
_, appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Wait for async message posting to complete
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Verify message was posted correctly with special characters preserved
|
||||
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
||||
require.Nil(t, appErr)
|
||||
@@ -1913,3 +1867,318 @@ func TestPostReviewerMessage(t *testing.T) {
|
||||
require.Equal(t, testMessage, testMessagePost.Message)
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to setup notification config for testing
|
||||
func setupNotificationConfig(th *TestHelper, eventTargetMapping map[model.ContentFlaggingEvent][]model.NotificationTarget) *model.AppError {
|
||||
config := getBaseConfig(th)
|
||||
config.NotificationSettings = &model.ContentFlaggingNotificationSettings{
|
||||
EventTargetMapping: eventTargetMapping,
|
||||
}
|
||||
return th.App.SaveContentFlaggingConfig(config)
|
||||
}
|
||||
|
||||
// Helper function to verify post message content and properties
|
||||
func verifyNotificationPost(t *testing.T, post *model.Post, expectedMessage string, expectedUserId string, expectedChannelId string) {
|
||||
require.NotNil(t, post)
|
||||
require.Equal(t, expectedMessage, post.Message)
|
||||
require.Equal(t, expectedUserId, post.UserId)
|
||||
require.Equal(t, expectedChannelId, post.ChannelId)
|
||||
require.True(t, post.CreateAt > 0)
|
||||
require.True(t, post.UpdateAt > 0)
|
||||
}
|
||||
|
||||
func TestSendFlaggedPostRemovalNotification(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
|
||||
t.Run("should send notifications to all configured targets", func(t *testing.T) {
|
||||
// Setup notification config for all targets
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentRemoved: {model.TargetReviewers, model.TargetAuthor, model.TargetReporter},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
actorComment := "This post violates community guidelines"
|
||||
createdPosts := th.App.sendFlaggedPostRemovalNotification(th.Context, post, th.SystemAdminUser.Id, actorComment, groupId)
|
||||
|
||||
// Should create 3 posts: reviewer message, author message, reporter message
|
||||
require.Len(t, createdPosts, 3)
|
||||
|
||||
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Verify reviewer message
|
||||
reviewerMessage := fmt.Sprintf("The flagged message was removed by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, actorComment)
|
||||
var reviewerPost *model.Post
|
||||
for _, p := range createdPosts {
|
||||
if p.Message == reviewerMessage {
|
||||
reviewerPost = p
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, reviewerPost)
|
||||
verifyNotificationPost(t, reviewerPost, reviewerMessage, contentReviewBot.UserId, reviewerPost.ChannelId)
|
||||
require.NotEmpty(t, reviewerPost.RootId) // Should be a thread reply to the flag review post
|
||||
|
||||
// Verify author message
|
||||
authorMessage := fmt.Sprintf("Your post having ID `%s` in the channel `%s` which was flagged for review has been permanently removed by a reviewer.", post.Id, th.BasicChannel.DisplayName)
|
||||
var authorPost *model.Post
|
||||
for _, p := range createdPosts {
|
||||
if p.Message == authorMessage {
|
||||
authorPost = p
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, authorPost)
|
||||
verifyNotificationPost(t, authorPost, authorMessage, contentReviewBot.UserId, authorPost.ChannelId)
|
||||
|
||||
// Verify reporter message
|
||||
reporterMessage := fmt.Sprintf("The post having ID `%s` in the channel `%s` which you flagged for review has been permanently removed by a reviewer.", post.Id, th.BasicChannel.DisplayName)
|
||||
var reporterPost *model.Post
|
||||
for _, p := range createdPosts {
|
||||
if p.Message == reporterMessage {
|
||||
reporterPost = p
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, reporterPost)
|
||||
verifyNotificationPost(t, reporterPost, reporterMessage, contentReviewBot.UserId, reporterPost.ChannelId)
|
||||
})
|
||||
|
||||
t.Run("should send notifications only to configured targets", func(t *testing.T) {
|
||||
// Setup notification config for only author
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentRemoved: {model.TargetReviewers},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Setup notification config for only author
|
||||
appErr = setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentRemoved: {model.TargetReviewers},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
createdPosts := th.App.sendFlaggedPostRemovalNotification(th.Context, post, th.SystemAdminUser.Id, "Test comment", groupId)
|
||||
|
||||
// Should create only 1 post for author
|
||||
require.Len(t, createdPosts, 1)
|
||||
|
||||
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
expectedMessage := fmt.Sprintf("The flagged message was removed by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, "Test comment")
|
||||
verifyNotificationPost(t, createdPosts[0], expectedMessage, contentReviewBot.UserId, createdPosts[0].ChannelId)
|
||||
})
|
||||
|
||||
t.Run("should handle empty comment", func(t *testing.T) {
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentRemoved: {model.TargetReviewers},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
createdPosts := th.App.sendFlaggedPostRemovalNotification(th.Context, post, th.SystemAdminUser.Id, "", groupId)
|
||||
|
||||
require.Len(t, createdPosts, 1)
|
||||
|
||||
expectedMessage := fmt.Sprintf("The flagged message was removed by @%s", th.SystemAdminUser.Username)
|
||||
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
||||
})
|
||||
|
||||
t.Run("should handle special characters in comment", func(t *testing.T) {
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentRemoved: {model.TargetReviewers},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
specialComment := "Comment with @mentions #channels ~teams & <script>alert('xss')</script>"
|
||||
createdPosts := th.App.sendFlaggedPostRemovalNotification(th.Context, post, th.SystemAdminUser.Id, specialComment, groupId)
|
||||
|
||||
require.Len(t, createdPosts, 1)
|
||||
|
||||
expectedMessage := fmt.Sprintf("The flagged message was removed by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, specialComment)
|
||||
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendKeepFlaggedPostNotification(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
||||
|
||||
t.Run("should send notifications to all configured targets", func(t *testing.T) {
|
||||
// Setup notification config for all targets
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentDismissed: {model.TargetReviewers, model.TargetAuthor, model.TargetReporter},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
actorComment := "This post is acceptable after review"
|
||||
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.SystemAdminUser.Id, actorComment, groupId)
|
||||
|
||||
// Should create 3 posts: reviewer message, author message, reporter message
|
||||
require.Len(t, createdPosts, 3)
|
||||
|
||||
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Verify reviewer message
|
||||
reviewerMessage := fmt.Sprintf("The flagged message was retained by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, actorComment)
|
||||
var reviewerPost *model.Post
|
||||
for _, p := range createdPosts {
|
||||
if p.Message == reviewerMessage {
|
||||
reviewerPost = p
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, reviewerPost)
|
||||
verifyNotificationPost(t, reviewerPost, reviewerMessage, contentReviewBot.UserId, reviewerPost.ChannelId)
|
||||
require.NotEmpty(t, reviewerPost.RootId) // Should be a thread reply
|
||||
|
||||
// Verify author message
|
||||
authorMessage := fmt.Sprintf("Your post having ID `%s` in the channel `%s` which was flagged for review has been restored by a reviewer.", post.Id, th.BasicChannel.DisplayName)
|
||||
var authorPost *model.Post
|
||||
for _, p := range createdPosts {
|
||||
if p.Message == authorMessage {
|
||||
authorPost = p
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, authorPost)
|
||||
verifyNotificationPost(t, authorPost, authorMessage, contentReviewBot.UserId, authorPost.ChannelId)
|
||||
|
||||
// Verify reporter message
|
||||
reporterMessage := fmt.Sprintf("The post having ID `%s` in the channel `%s` which you flagged for review has been restored by a reviewer.", post.Id, th.BasicChannel.DisplayName)
|
||||
var reporterPost *model.Post
|
||||
for _, p := range createdPosts {
|
||||
if p.Message == reporterMessage {
|
||||
reporterPost = p
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, reporterPost)
|
||||
verifyNotificationPost(t, reporterPost, reporterMessage, contentReviewBot.UserId, reporterPost.ChannelId)
|
||||
})
|
||||
|
||||
t.Run("should send notifications only to configured targets", func(t *testing.T) {
|
||||
// Setup notification config for only reporter
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentDismissed: {model.TargetReviewers},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
comment := "Test comment"
|
||||
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.SystemAdminUser.Id, comment, groupId)
|
||||
|
||||
// Should create only 1 post for reporter
|
||||
require.Len(t, createdPosts, 1)
|
||||
|
||||
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
expectedMessage := fmt.Sprintf("The flagged message was retained by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, comment)
|
||||
verifyNotificationPost(t, createdPosts[0], expectedMessage, contentReviewBot.UserId, createdPosts[0].ChannelId)
|
||||
})
|
||||
|
||||
t.Run("should handle empty comment", func(t *testing.T) {
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentDismissed: {model.TargetReviewers},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.SystemAdminUser.Id, "", groupId)
|
||||
|
||||
require.Len(t, createdPosts, 1)
|
||||
|
||||
expectedMessage := fmt.Sprintf("The flagged message was retained by @%s", th.SystemAdminUser.Username)
|
||||
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
||||
})
|
||||
|
||||
t.Run("should handle special characters in comment", func(t *testing.T) {
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentDismissed: {model.TargetReviewers},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
specialComment := "Comment with @mentions #channels ~teams & <script>alert('xss')</script>"
|
||||
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.SystemAdminUser.Id, specialComment, groupId)
|
||||
|
||||
require.Len(t, createdPosts, 1)
|
||||
|
||||
expectedMessage := fmt.Sprintf("The flagged message was retained by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, specialComment)
|
||||
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
||||
})
|
||||
|
||||
t.Run("should handle different actor users", func(t *testing.T) {
|
||||
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
||||
model.EventContentDismissed: {model.TargetReviewers},
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
post, appErr := setupFlaggedPost(th)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Use BasicUser as actor instead of SystemAdminUser
|
||||
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.BasicUser.Id, "Reviewed by different user", groupId)
|
||||
|
||||
require.Len(t, createdPosts, 1)
|
||||
|
||||
expectedMessage := fmt.Sprintf("The flagged message was retained by @%s\n\nWith comment:\n\n> %s", th.BasicUser.Username, "Reviewed by different user")
|
||||
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
||||
})
|
||||
}
|
||||
|
||||
+8
-4
@@ -5206,10 +5206,6 @@
|
||||
"id": "app.content_flagging.create_property_values.app_error",
|
||||
"translation": "Unable to save property values for the flagged post."
|
||||
},
|
||||
{
|
||||
"id": "app.content_flagging.delete_post.app_error",
|
||||
"translation": "Unable to soft-delete the flagged post."
|
||||
},
|
||||
{
|
||||
"id": "app.content_flagging.flag_post.marshal_comment.app_error",
|
||||
"translation": "Failed to marshal flagging user's comment"
|
||||
@@ -5258,6 +5254,14 @@
|
||||
"id": "app.content_flagging.missing_flagged_post_id_field.app_error",
|
||||
"translation": "Unable to find Flagged Post ID property field."
|
||||
},
|
||||
{
|
||||
"id": "app.content_flagging.missing_reporting_user_id_field.app_error",
|
||||
"translation": "Unable to find reporting user field."
|
||||
},
|
||||
{
|
||||
"id": "app.content_flagging.missing_reporting_user_id_property_value.app_error",
|
||||
"translation": "Unable to find value for reporting user field."
|
||||
},
|
||||
{
|
||||
"id": "app.content_flagging.no_status_property.app_error",
|
||||
"translation": "Cannot fetch flagged post as the post is not flagged."
|
||||
|
||||
Reference in New Issue
Block a user