mirror of
https://github.com/mattermost/focalboard.git
synced 2024-12-21 13:38:56 +02:00
Fixed duplicate attachment in board template (#4444)
* Fixed duplicate attachment in board template * Linter fixes * Duplicate attachment fix * Code optimismed * Linter fixes * Updated test cases * update some error handling, update attachments on duplicate card * Fixed attachment section width --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com>
This commit is contained in:
parent
c91a67fbe6
commit
5a89960b64
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/model"
|
"github.com/mattermost/focalboard/server/model"
|
||||||
"github.com/mattermost/focalboard/server/services/notify"
|
"github.com/mattermost/focalboard/server/services/notify"
|
||||||
@ -309,14 +310,26 @@ func (a *App) CopyCardFiles(sourceBoardID string, copiedBlocks []*model.Block) e
|
|||||||
|
|
||||||
for i := range copiedBlocks {
|
for i := range copiedBlocks {
|
||||||
block := copiedBlocks[i]
|
block := copiedBlocks[i]
|
||||||
|
fileName := ""
|
||||||
|
isOk := false
|
||||||
|
|
||||||
fileName, ok := block.Fields["fileId"]
|
switch block.Type {
|
||||||
if !ok || fileName == "" {
|
case model.TypeImage:
|
||||||
continue // doesn't have a file attachment
|
fileName, isOk = block.Fields["fileId"].(string)
|
||||||
|
if !isOk || fileName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case model.TypeAttachment:
|
||||||
|
fileName, isOk = block.Fields["attachmentId"].(string)
|
||||||
|
if !isOk || fileName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// create unique filename in case we are copying cards within the same board.
|
// create unique filename in case we are copying cards within the same board.
|
||||||
ext := filepath.Ext(fileName.(string))
|
ext := filepath.Ext(fileName)
|
||||||
destFilename := utils.NewID(utils.IDTypeNone) + ext
|
destFilename := utils.NewID(utils.IDTypeNone) + ext
|
||||||
|
|
||||||
if destBoardID == "" || block.BoardID != destBoardID {
|
if destBoardID == "" || block.BoardID != destBoardID {
|
||||||
@ -328,7 +341,7 @@ func (a *App) CopyCardFiles(sourceBoardID string, copiedBlocks []*model.Block) e
|
|||||||
destTeamID = destBoard.TeamID
|
destTeamID = destBoard.TeamID
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceFilePath := filepath.Join(sourceBoard.TeamID, sourceBoard.ID, fileName.(string))
|
sourceFilePath := filepath.Join(sourceBoard.TeamID, sourceBoard.ID, fileName)
|
||||||
destinationFilePath := filepath.Join(destTeamID, block.BoardID, destFilename)
|
destinationFilePath := filepath.Join(destTeamID, block.BoardID, destFilename)
|
||||||
|
|
||||||
a.logger.Debug(
|
a.logger.Debug(
|
||||||
@ -345,8 +358,25 @@ func (a *App) CopyCardFiles(sourceBoardID string, copiedBlocks []*model.Block) e
|
|||||||
mlog.Err(err),
|
mlog.Err(err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if block.Type == model.TypeAttachment {
|
||||||
|
block.Fields["attachmentId"] = destFilename
|
||||||
|
parts := strings.Split(fileName, ".")
|
||||||
|
fileInfoID := parts[0][1:]
|
||||||
|
fileInfo, err := a.store.GetFileInfo(fileInfoID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("CopyCardFiles: cannot retrieve original fileinfo: %w", err)
|
||||||
|
}
|
||||||
|
newParts := strings.Split(destFilename, ".")
|
||||||
|
newFileID := newParts[0][1:]
|
||||||
|
fileInfo.Id = newFileID
|
||||||
|
err = a.store.SaveFileInfo(fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("CopyCardFiles: cannot create fileinfo: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
block.Fields["fileId"] = destFilename
|
block.Fields["fileId"] = destFilename
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -202,15 +202,23 @@ func (a *App) DuplicateBoard(boardID, userID, toTeam string, asTemplate bool) (*
|
|||||||
blockPatches := make([]model.BlockPatch, 0)
|
blockPatches := make([]model.BlockPatch, 0)
|
||||||
|
|
||||||
for _, block := range bab.Blocks {
|
for _, block := range bab.Blocks {
|
||||||
if fileID, ok := block.Fields["fileId"]; ok {
|
fieldName := ""
|
||||||
|
if block.Type == model.TypeImage {
|
||||||
|
fieldName = "fileId"
|
||||||
|
} else if block.Type == model.TypeAttachment {
|
||||||
|
fieldName = "attachmentId"
|
||||||
|
}
|
||||||
|
if fieldName != "" {
|
||||||
|
if fieldID, ok := block.Fields[fieldName]; ok {
|
||||||
blockIDs = append(blockIDs, block.ID)
|
blockIDs = append(blockIDs, block.ID)
|
||||||
blockPatches = append(blockPatches, model.BlockPatch{
|
blockPatches = append(blockPatches, model.BlockPatch{
|
||||||
UpdatedFields: map[string]interface{}{
|
UpdatedFields: map[string]interface{}{
|
||||||
"fileId": fileID,
|
fieldName: fieldID,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
a.logger.Debug("Duplicate boards patching file IDs", mlog.Int("count", len(blockIDs)))
|
a.logger.Debug("Duplicate boards patching file IDs", mlog.Int("count", len(blockIDs)))
|
||||||
|
|
||||||
if len(blockIDs) != 0 {
|
if len(blockIDs) != 0 {
|
||||||
|
@ -98,13 +98,9 @@ describe('Card URL Property', () => {
|
|||||||
const addView = (type: ViewType) => {
|
const addView = (type: ViewType) => {
|
||||||
cy.log(`**Add ${type} view**`)
|
cy.log(`**Add ${type} view**`)
|
||||||
|
|
||||||
// Intercept and wait for getUser request because it is the last one in the effects for BoardPage
|
|
||||||
// After this last request the BoardPage component will not have additional rerenders
|
|
||||||
cy.intercept('POST', '/api/v2/users').as('getUser')
|
|
||||||
cy.findByRole('button', {name: 'View menu'}).click()
|
cy.findByRole('button', {name: 'View menu'}).click()
|
||||||
cy.findByText('Add view').realHover()
|
cy.findByText('Add view').realHover()
|
||||||
cy.findByRole('button', {name: type}).click()
|
cy.findByRole('button', {name: type}).click()
|
||||||
cy.wait('@getUser')
|
|
||||||
cy.findByRole('textbox', {name: `${type} view`}).should('exist')
|
cy.findByRole('textbox', {name: `${type} view`}).should('exist')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
.Attachment {
|
.Attachment {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.attachment-header {
|
.attachment-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -13,7 +14,6 @@
|
|||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
width: 550px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-plus-icon {
|
.attachment-plus-icon {
|
||||||
|
@ -12,6 +12,7 @@ import {BoardView, ISortOption, createBoardView, KanbanCalculationFields} from '
|
|||||||
import {Card, createCard} from './blocks/card'
|
import {Card, createCard} from './blocks/card'
|
||||||
import {ContentBlock} from './blocks/contentBlock'
|
import {ContentBlock} from './blocks/contentBlock'
|
||||||
import {CommentBlock} from './blocks/commentBlock'
|
import {CommentBlock} from './blocks/commentBlock'
|
||||||
|
import {AttachmentBlock} from './blocks/attachmentBlock'
|
||||||
import {FilterGroup} from './blocks/filterGroup'
|
import {FilterGroup} from './blocks/filterGroup'
|
||||||
import octoClient from './octoClient'
|
import octoClient from './octoClient'
|
||||||
import undoManager from './undomanager'
|
import undoManager from './undomanager'
|
||||||
@ -26,6 +27,7 @@ import store from './store'
|
|||||||
import {updateBoards} from './store/boards'
|
import {updateBoards} from './store/boards'
|
||||||
import {updateViews} from './store/views'
|
import {updateViews} from './store/views'
|
||||||
import {updateCards} from './store/cards'
|
import {updateCards} from './store/cards'
|
||||||
|
import {updateAttachments} from './store/attachments'
|
||||||
import {updateComments} from './store/comments'
|
import {updateComments} from './store/comments'
|
||||||
import {updateContents} from './store/contents'
|
import {updateContents} from './store/contents'
|
||||||
import {addBoardUsers, removeBoardUsersById} from './store/users'
|
import {addBoardUsers, removeBoardUsersById} from './store/users'
|
||||||
@ -35,6 +37,7 @@ function updateAllBoardsAndBlocks(boards: Board[], blocks: Block[]) {
|
|||||||
store.dispatch(updateBoards(boards.filter((b: Board) => b.deleteAt !== 0) as Board[]))
|
store.dispatch(updateBoards(boards.filter((b: Board) => b.deleteAt !== 0) as Board[]))
|
||||||
store.dispatch(updateViews(blocks.filter((b: Block) => b.type === 'view' || b.deleteAt !== 0) as BoardView[]))
|
store.dispatch(updateViews(blocks.filter((b: Block) => b.type === 'view' || b.deleteAt !== 0) as BoardView[]))
|
||||||
store.dispatch(updateCards(blocks.filter((b: Block) => b.type === 'card' || b.deleteAt !== 0) as Card[]))
|
store.dispatch(updateCards(blocks.filter((b: Block) => b.type === 'card' || b.deleteAt !== 0) as Card[]))
|
||||||
|
store.dispatch(updateAttachments(blocks.filter((b: Block) => b.type === 'attachment' || b.deleteAt !== 0) as AttachmentBlock[]))
|
||||||
store.dispatch(updateComments(blocks.filter((b: Block) => b.type === 'comment' || b.deleteAt !== 0) as CommentBlock[]))
|
store.dispatch(updateComments(blocks.filter((b: Block) => b.type === 'comment' || b.deleteAt !== 0) as CommentBlock[]))
|
||||||
store.dispatch(updateContents(blocks.filter((b: Block) => b.type !== 'card' && b.type !== 'view' && b.type !== 'board' && b.type !== 'comment') as ContentBlock[]))
|
store.dispatch(updateContents(blocks.filter((b: Block) => b.type !== 'card' && b.type !== 'view' && b.type !== 'board' && b.type !== 'comment') as ContentBlock[]))
|
||||||
})
|
})
|
||||||
|
@ -215,13 +215,15 @@ const BoardPage = (props: Props): JSX.Element => {
|
|||||||
UserSettings.setLastViewId(match.params.boardId, viewId)
|
UserSettings.setLastViewId(match.params.boardId, viewId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props.readonly && me) {
|
|
||||||
loadOrJoinBoard(me.id, teamId, match.params.boardId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [teamId, match.params.boardId, viewId, me?.id])
|
}, [teamId, match.params.boardId, viewId, me?.id])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (match.params.boardId && !props.readonly && me) {
|
||||||
|
loadOrJoinBoard(me.id, teamId, match.params.boardId)
|
||||||
|
}
|
||||||
|
}, [teamId, match.params.boardId, me?.id])
|
||||||
|
|
||||||
const handleUnhideBoard = async (boardID: string) => {
|
const handleUnhideBoard = async (boardID: string) => {
|
||||||
if (!me || !category) {
|
if (!me || !category) {
|
||||||
return
|
return
|
||||||
|
@ -26,7 +26,9 @@ const attachmentSlice = createSlice({
|
|||||||
state.attachmentsByCard[attachment.parentId] = [attachment]
|
state.attachmentsByCard[attachment.parentId] = [attachment]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (state.attachmentsByCard[attachment.parentId].findIndex((a) => a.id === attachment.id) === -1) {
|
||||||
state.attachmentsByCard[attachment.parentId].push(attachment)
|
state.attachmentsByCard[attachment.parentId].push(attachment)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const parentId = state.attachments[attachment.id]?.parentId
|
const parentId = state.attachments[attachment.id]?.parentId
|
||||||
if (!state.attachmentsByCard[parentId]) {
|
if (!state.attachmentsByCard[parentId]) {
|
||||||
|
Loading…
Reference in New Issue
Block a user