* Migrate feature/wrangler to mono-repo

* Add wrangler files

* Fix linters, types, etc

* Fix snapshots

* Fix playwright

* Fix pipelines

* Fix more pipeline

* Fixes for pipelines

* More changes for pipeline

* Fix types

* Add support for a feature flag, but leave it defaulted on for spinwick usage for now

* Update snapshot

* fix js error when removing last value of multiselect, support CSV marshaling to string array for textsetting

* Fix linter

* Remove TODO

* Remove another TODO

* fix tests

* Fix i18n

* Add server tests

* Fix linter

* Fix linter

* Use proper icon for dot menu

* Update snapshot

* Add Cypress UI tests for various entrypoints to move thread modal, split SCSS out from forward post into its own thing

* clean up

* fix linter

* More cleanup

* Revert files to master

* Fix linter for e2e tests

* Make ForwardPostChannelSelect channel types configurable with a prop

* Add missing return

* Fixes from PR feedback

* First batch of PR Feedback

* Another batch of PR changes

* Fix linter

* Update snapshots

* Wrangler system messages are translated to each user's locale

* Initially translate Wrangler into system locale rather than initiating user

* More fixes for PR Feedback

* Fix some server tests

* More updates with master. Fixes around pipelines. Enforce Enterprise license on front/back end

* Add tests for dot_menu

* More pipeline fixes

* Fix e2etests prettier

* Update cypress tests, change occurrences of 'Wrangler' with 'Move Thread'

* Fix linter

* Remove enterprise lock

* A couple more occurrences of wrangler strings, and one more enterprise lock

* Fix server tests

* Fix i18n

* Fix e2e linter

* Feature flag shouldn't be on by default

* Enable move threads feature in smoke tests (#25657)

* enable move threads feature
* add @prod tag

* Fix move_thread_from_public_channel e2e test

* Fix e2e style

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Этот коммит содержится в:
Nick Misasi
2023-12-11 15:27:34 -05:00
коммит произвёл GitHub
родитель bfb8320afd
Коммит f0a336ba07
56 изменённых файлов: 3107 добавлений и 16 удалений

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

@@ -2349,3 +2349,255 @@ func (a *App) applyPostWillBeConsumedHook(post **model.Post) {
return true
}, plugin.MessagesWillBeConsumedID)
}
func makePostLink(siteURL, teamName, postID string) string {
return fmt.Sprintf("%s/%s/pl/%s", siteURL, teamName, postID)
}
// validateMoveOrCopy performs validation on a provided post list to determine
// if all permissions are in place to allow the for the posts to be moved or
// copied.
func (a *App) ValidateMoveOrCopy(c request.CTX, wpl *model.WranglerPostList, originalChannel *model.Channel, targetChannel *model.Channel, user *model.User) error {
if wpl.NumPosts() == 0 {
return errors.New("The wrangler post list contains no posts")
}
config := a.Config().WranglerSettings
switch originalChannel.Type {
case model.ChannelTypePrivate:
if !*config.MoveThreadFromPrivateChannelEnable {
return errors.New("Wrangler is currently configured to not allow moving posts from private channels")
}
case model.ChannelTypeDirect:
if !*config.MoveThreadFromDirectMessageChannelEnable {
return errors.New("Wrangler is currently configured to not allow moving posts from direct message channels")
}
case model.ChannelTypeGroup:
if !*config.MoveThreadFromGroupMessageChannelEnable {
return errors.New("Wrangler is currently configured to not allow moving posts from group message channels")
}
}
if !originalChannel.IsGroupOrDirect() && !targetChannel.IsGroupOrDirect() {
// DM and GM channels are "teamless" so it doesn't make sense to check
// the MoveThreadToAnotherTeamEnable config when dealing with those.
if !*config.MoveThreadToAnotherTeamEnable && targetChannel.TeamId != originalChannel.TeamId {
return errors.New("Wrangler is currently configured to not allow moving messages to different teams")
}
}
if *config.MoveThreadMaxCount != int64(0) && *config.MoveThreadMaxCount < int64(wpl.NumPosts()) {
return fmt.Errorf("the thread is %d posts long, but this command is configured to only move threads of up to %d posts", wpl.NumPosts(), *config.MoveThreadMaxCount)
}
_, appErr := a.GetChannelMember(c, targetChannel.Id, user.Id)
if appErr != nil {
return fmt.Errorf("channel with ID %s doesn't exist or you are not a member", targetChannel.Id)
}
_, appErr = a.GetChannelMember(c, originalChannel.Id, user.Id)
if appErr != nil {
return fmt.Errorf("channel with ID %s doesn't exist or you are not a member", originalChannel.Id)
}
return nil
}
func (a *App) CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, targetChannel *model.Channel) (*model.Post, *model.AppError) {
var appErr *model.AppError
var newRootPost *model.Post
if wpl.ContainsFileAttachments() {
// The thread contains at least one attachment. To properly move the
// thread, the files will have to be re-uploaded. This is completed
// before any messages are moved.
// TODO: check number of files that need to be re-uploaded or file size?
c.Logger().Info("Wrangler is re-uploading file attachments",
mlog.String("file_count", fmt.Sprintf("%d", wpl.FileAttachmentCount)),
)
for _, post := range wpl.Posts {
var newFileIDs []string
var fileBytes []byte
var oldFileInfo, newFileInfo *model.FileInfo
for _, fileID := range post.FileIds {
oldFileInfo, appErr = a.GetFileInfo(c, fileID)
if appErr != nil {
return nil, appErr
}
fileBytes, appErr = a.GetFile(c, fileID)
if appErr != nil {
return nil, appErr
}
newFileInfo, appErr = a.UploadFile(c, fileBytes, targetChannel.Id, oldFileInfo.Name)
if appErr != nil {
return nil, appErr
}
newFileIDs = append(newFileIDs, newFileInfo.Id)
}
post.FileIds = newFileIDs
}
}
for i, post := range wpl.Posts {
var reactions []*model.Reaction
// Store reactions to be reapplied later.
reactions, appErr = a.GetReactionsForPost(post.Id)
if appErr != nil {
// Reaction-based errors are logged, but do not abort
c.Logger().Error("Failed to get reactions on original post")
}
newPost := post.Clone()
newPost = newPost.CleanPost()
newPost.ChannelId = targetChannel.Id
if i == 0 {
newPost, appErr = a.CreatePost(c, newPost, targetChannel, false, false)
if appErr != nil {
return nil, appErr
}
newRootPost = newPost.Clone()
} else {
newPost.RootId = newRootPost.Id
newPost, appErr = a.CreatePost(c, newPost, targetChannel, false, false)
if appErr != nil {
return nil, appErr
}
}
for _, reaction := range reactions {
reaction.PostId = newPost.Id
_, appErr = a.SaveReactionForPost(c, reaction)
if appErr != nil {
// Reaction-based errors are logged, but do not abort
c.Logger().Error("Failed to reapply reactions to post")
}
}
}
return newRootPost, nil
}
func (a *App) MoveThread(c request.CTX, postID string, sourceChannelID, channelID string, user *model.User) *model.AppError {
postListResponse, appErr := a.GetPostThread(postID, model.GetPostsOptions{}, user.Id)
if appErr != nil {
return model.NewAppError("getPostThread", "app.post.move_thread_command.error", nil, "postID="+postID+", "+"UserId="+user.Id+"", http.StatusBadRequest)
}
wpl := postListResponse.BuildWranglerPostList()
originalChannel, appErr := a.GetChannel(c, sourceChannelID)
if appErr != nil {
return appErr
}
targetChannel, appErr := a.GetChannel(c, channelID)
if appErr != nil {
return appErr
}
err := a.ValidateMoveOrCopy(c, wpl, originalChannel, targetChannel, user)
if err != nil {
return model.NewAppError("validateMoveOrCopy", "app.post.move_thread_command.error", nil, err.Error(), http.StatusBadRequest)
}
var targetTeam *model.Team
if targetChannel.IsGroupOrDirect() {
if !originalChannel.IsGroupOrDirect() {
targetTeam, appErr = a.GetTeam(originalChannel.TeamId)
}
} else {
targetTeam, appErr = a.GetTeam(targetChannel.TeamId)
}
if appErr != nil {
return appErr
}
if targetTeam == nil {
return model.NewAppError("validateMoveOrCopy", "app.post.move_thread_command.error", nil, "target team is nil", http.StatusBadRequest)
}
// Begin creating the new thread.
c.Logger().Info("Wrangler is moving a thread", mlog.String("user_id", user.Id), mlog.String("original_post_id", wpl.RootPost().Id), mlog.String("original_channel_id", originalChannel.Id))
// To simulate the move, we first copy the original messages(s) to the
// new channel and later delete the original messages(s).
newRootPost, appErr := a.CopyWranglerPostlist(c, wpl, targetChannel)
if appErr != nil {
return appErr
}
T, err := i18n.GetTranslationsBySystemLocale()
if err != nil {
return model.NewAppError("MoveThread", "app.post.move_thread_command.error", nil, err.Error(), http.StatusInternalServerError)
}
ephemeralPostProps := model.StringInterface{
"TranslationID": "app.post.move_thread.from_another_channel",
}
_, appErr = a.CreatePost(c, &model.Post{
UserId: user.Id,
Type: model.PostTypeWrangler,
RootId: newRootPost.Id,
ChannelId: channelID,
Message: T("app.post.move_thread.from_another_channel"),
Props: ephemeralPostProps,
}, targetChannel, false, false)
if appErr != nil {
return appErr
}
// Cleanup is handled by simply deleting the root post. Any comments/replies
// are automatically marked as deleted for us.
_, appErr = a.DeletePost(c, wpl.RootPost().Id, user.Id)
if appErr != nil {
return appErr
}
c.Logger().Info("Wrangler thread move complete", mlog.String("user_id", user.Id), mlog.String("new_post_id", newRootPost.Id), mlog.String("channel_id", channelID))
// Translate to the system locale, webapp will attempt to render in each user's specific locale (based on the TranslationID prop) before falling back on the initiating user's locale
ephemeralPostProps = model.StringInterface{}
msg := T("app.post.move_thread_command.direct_or_group.multiple_messages", model.StringInterface{"NumMessages": wpl.NumPosts()})
ephemeralPostProps["TranslationID"] = "app.post.move_thread_command.direct_or_group.multiple_messages"
if wpl.NumPosts() == 1 {
msg = T("app.post.move_thread_command.direct_or_group.one_message")
ephemeralPostProps["TranslationID"] = "app.post.move_thread_command.direct_or_group.one_message"
}
if targetChannel.TeamId != "" {
targetTeam, teamErr := a.GetTeam(targetChannel.TeamId)
if teamErr != nil {
return teamErr
}
targetName := targetTeam.Name
newPostLink := makePostLink(*a.Config().ServiceSettings.SiteURL, targetName, newRootPost.Id)
msg = T("app.post.move_thread_command.channel.multiple_messages", model.StringInterface{"NumMessages": wpl.NumPosts(), "Link": newPostLink})
ephemeralPostProps["TranslationID"] = "app.post.move_thread_command.channel.multiple_messages"
if wpl.NumPosts() == 1 {
msg = T("app.post.move_thread_command.channel.one_message", model.StringInterface{"Link": newPostLink})
ephemeralPostProps["TranslationID"] = "app.post.move_thread_command.channel.one_message"
}
ephemeralPostProps["MovedThreadPermalink"] = newPostLink
}
ephemeralPostProps["NumMessages"] = wpl.NumPosts()
_, appErr = a.CreatePost(c, &model.Post{
UserId: user.Id,
Type: model.PostTypeWrangler,
ChannelId: originalChannel.Id,
Message: msg,
Props: ephemeralPostProps,
}, originalChannel, false, false)
if appErr != nil {
return appErr
}
c.Logger().Info(msg)
return nil
}