Compare commits

..

5 Commits

Author SHA1 Message Date
renovate bf6fcf94d1 chore(deps): update dev-dependencies
continuous-integration/drone/pr Build is passing Details
2024-04-09 17:08:16 +00:00
kolaente bc19a2fb78
fix(migration): import card comments from Trello when migrating
continuous-integration/drone/push Build is passing Details
Related: https://community.vikunja.io/t/trello-import-comments-and-assignments/2174/3
2024-04-09 13:56:17 +02:00
kolaente 994aaeb920
fix(migration): trello: only fetch attachments when the card actually has attachments 2024-04-09 13:25:03 +02:00
kolaente ee3d20e1d2
fix(navigation): do not hide shadows of dropdown menu
continuous-integration/drone/push Build is passing Details
2024-04-09 13:07:01 +02:00
Elscrux 8458e77341 feat(migration): Trello organization based migration (#2211)
continuous-integration/drone/push Build is failing Details
Migrate Trello organization after organization to limit total memory allocation.
Related discussion: https://community.vikunja.io/t/trello-import-issues/2110

Co-authored-by: Elscrux <nickposer2102@gmail.com>
Co-authored-by: konrad <k@knt.li>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #2211
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Elscrux <elscrux@gmail.com>
Co-committed-by: Elscrux <elscrux@gmail.com>
2024-04-09 10:54:38 +00:00
6 changed files with 445 additions and 257 deletions

View File

@ -5,7 +5,7 @@
"eslint.packageManager": "pnpm",
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll": "explicit"
},
"eslint.format.enable": true,
"[javascript]": {

View File

@ -132,8 +132,8 @@
"@cypress/vite-dev-server": "5.0.7",
"@cypress/vue": "6.0.0",
"@faker-js/faker": "8.4.1",
"@histoire/plugin-screenshot": "0.17.16",
"@histoire/plugin-vue": "0.17.16",
"@histoire/plugin-screenshot": "0.17.17",
"@histoire/plugin-vue": "0.17.17",
"@rushstack/eslint-patch": "1.10.1",
"@tsconfig/node18": "18.2.4",
"@types/codemirror": "5.60.15",
@ -162,7 +162,7 @@
"eslint": "8.57.0",
"eslint-plugin-vue": "9.24.1",
"happy-dom": "14.7.1",
"histoire": "0.17.16",
"histoire": "0.17.17",
"postcss": "8.4.38",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",

View File

@ -256,11 +256,11 @@ devDependencies:
specifier: 8.4.1
version: 8.4.1
'@histoire/plugin-screenshot':
specifier: 0.17.16
version: 0.17.16(histoire@0.17.16)
specifier: 0.17.17
version: 0.17.17(histoire@0.17.17)
'@histoire/plugin-vue':
specifier: 0.17.16
version: 0.17.16(histoire@0.17.16)(vite@5.2.8)(vue@3.4.21)
specifier: 0.17.17
version: 0.17.17(histoire@0.17.17)(vite@5.2.8)(vue@3.4.21)
'@rushstack/eslint-patch':
specifier: 1.10.1
version: 1.10.1
@ -343,8 +343,8 @@ devDependencies:
specifier: 14.7.1
version: 14.7.1
histoire:
specifier: 0.17.16
version: 0.17.16(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8)
specifier: 0.17.17
version: 0.17.17(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8)
postcss:
specifier: 8.4.38
version: 8.4.38
@ -2497,12 +2497,12 @@ packages:
'@hapi/hoek': 9.2.1
dev: true
/@histoire/app@0.17.16(vite@5.2.8):
resolution: {integrity: sha512-ds7aF9xzMSaLl+aQZYPGw8EllPJwn9Jb+DdVRdfkqAx2ZN4Lc2Ut3j5E+2IVz2DKLvP8Zio7CPqoatP2UfaoDg==}
/@histoire/app@0.17.17(vite@5.2.8):
resolution: {integrity: sha512-2i1V38o08V+eaR0d3L0/EA6AYG14xyQBJbyYv0Hz3r4sH3Elj1FoJiwolbCfTDmkOnSgwWTc7+JoCqkLIbxfhA==}
dependencies:
'@histoire/controls': 0.17.16(vite@5.2.8)
'@histoire/shared': 0.17.16(vite@5.2.8)
'@histoire/vendors': 0.17.16
'@histoire/controls': 0.17.17(vite@5.2.8)
'@histoire/shared': 0.17.17(vite@5.2.8)
'@histoire/vendors': 0.17.17
'@types/flexsearch': 0.7.6
flexsearch: 0.7.21
shiki-es: 0.2.0
@ -2510,8 +2510,8 @@ packages:
- vite
dev: true
/@histoire/controls@0.17.16(vite@5.2.8):
resolution: {integrity: sha512-yU5xGpAckcExYVdfnN1w7HFg1SwnOHW96RM+6THgu3ejPjRDVAoyMumM4xRWuY/y3PzlmW7kSoLJu9ykwufIGg==}
/@histoire/controls@0.17.17(vite@5.2.8):
resolution: {integrity: sha512-W22HZ/X078IZmE09XEKj4Fq7LxQPP/w/aMYAzm94V2NIGhI0fkiSaBDvyTUl7NYrGT66Wq5+9Po1IWPMllk3cQ==}
dependencies:
'@codemirror/commands': 6.3.2
'@codemirror/lang-json': 6.0.1
@ -2520,21 +2520,21 @@ packages:
'@codemirror/state': 6.3.2
'@codemirror/theme-one-dark': 6.1.2
'@codemirror/view': 6.22.1
'@histoire/shared': 0.17.16(vite@5.2.8)
'@histoire/vendors': 0.17.16
'@histoire/shared': 0.17.17(vite@5.2.8)
'@histoire/vendors': 0.17.17
transitivePeerDependencies:
- vite
dev: true
/@histoire/plugin-screenshot@0.17.16(histoire@0.17.16):
resolution: {integrity: sha512-+KdQM5bkWfXJTUKpL1mQvDF/riQncijzRcFHTpXR++XXPAKMW2sRMf2psn51YJxQybJQuMPj5dEZlHowUfvSiw==}
/@histoire/plugin-screenshot@0.17.17(histoire@0.17.17):
resolution: {integrity: sha512-R5JL22bz274VZwLNu83Q3+xVDkdXOUuqjUc3ARVHWAObq8bZfHm5vNtQzE9uEN7DQi5NXZtnDEts8OfOxSu6rA==}
peerDependencies:
histoire: ^0.17.16
histoire: ^0.17.17
dependencies:
capture-website: 2.4.1
defu: 6.1.3
fs-extra: 10.1.0
histoire: 0.17.16(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8)
histoire: 0.17.17(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8)
pathe: 1.1.1
transitivePeerDependencies:
- bufferutil
@ -2543,18 +2543,18 @@ packages:
- utf-8-validate
dev: true
/@histoire/plugin-vue@0.17.16(histoire@0.17.16)(vite@5.2.8)(vue@3.4.21):
resolution: {integrity: sha512-sne2u5pIWy8hyivhYGA5P1lA3z/jstOCkNN9DnneLjan347exD3ASjbpVyMQjISUKCfNlY0dXKnlCgac7d6Qlg==}
/@histoire/plugin-vue@0.17.17(histoire@0.17.17)(vite@5.2.8)(vue@3.4.21):
resolution: {integrity: sha512-O5h/Ww6IT2CygVVT4onN27IZt11Z2qE8XeHeXJCEese3dxnnVWRhjMpsaWAU5XqgfjKNAiALJk86b49/6NQaRg==}
peerDependencies:
histoire: ^0.17.16
histoire: ^0.17.17
vue: ^3.2.47
dependencies:
'@histoire/controls': 0.17.16(vite@5.2.8)
'@histoire/shared': 0.17.16(vite@5.2.8)
'@histoire/vendors': 0.17.16
'@histoire/controls': 0.17.17(vite@5.2.8)
'@histoire/shared': 0.17.17(vite@5.2.8)
'@histoire/vendors': 0.17.17
change-case: 4.1.2
globby: 13.2.2
histoire: 0.17.16(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8)
histoire: 0.17.17(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8)
launch-editor: 2.6.1
pathe: 1.1.1
vue: 3.4.21(typescript@5.4.4)
@ -2562,12 +2562,12 @@ packages:
- vite
dev: true
/@histoire/shared@0.17.16(vite@5.2.8):
resolution: {integrity: sha512-VsrBSh6EVQDttb/K512vurgUb1PwkAavt4VQWZ7LrQNCGhuoz/EJ6wLMpI0xkMRGvblBjoy204HOh+0W9vDBog==}
/@histoire/shared@0.17.17(vite@5.2.8):
resolution: {integrity: sha512-ueGtURysonT0MujCObPCR57+mgZluMEXCrbc2FBgKAD/DoAt38tNwSGsmLldk2O6nTr7lr6ClbVSgWrLwgY6Xw==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
dependencies:
'@histoire/vendors': 0.17.16
'@histoire/vendors': 0.17.17
'@types/fs-extra': 9.0.13
'@types/markdown-it': 12.2.3
chokidar: 3.5.3
@ -2576,8 +2576,8 @@ packages:
vite: 5.2.8(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)
dev: true
/@histoire/vendors@0.17.16:
resolution: {integrity: sha512-J/e1sjT/TaFT44aDkszRbqHnNvL7Bwrbl0GgqqH+T1MlkFv9e6prDIGzs8wvtdIDQbCq2T8NZOYhO/PGrcuJUA==}
/@histoire/vendors@0.17.17:
resolution: {integrity: sha512-QZvmffdoJlLuYftPIkOU5Q2FPAdG2JjMuQ5jF7NmEl0n1XnmbMqtRkdYTZ4eF6CO1KLZ0Zyf6gBQvoT1uWNcjA==}
dev: true
/@humanwhocodes/config-array@0.11.14:
@ -6296,17 +6296,17 @@ packages:
engines: {node: '>=12.0.0'}
dev: false
/histoire@0.17.16(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8):
resolution: {integrity: sha512-zN67L0mbhJzreCz6VM9N9E203FBs0Adanf597bA8e4GAiLABHmcimsihOLaHQRG9g0qvjr3mU0H7Wi2wsYDRkg==}
/histoire@0.17.17(@types/node@20.12.6)(sass@1.74.1)(terser@5.24.0)(vite@5.2.8):
resolution: {integrity: sha512-DAwY4sgIoP7NGE5ldaws2d3RWz4OOQcwhS8elRMiA2euqzLvDU2IXm+ZjeDDFVtGkvmQNQyfZBDKLCLHfRkSUg==}
hasBin: true
peerDependencies:
vite: ^2.9.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
dependencies:
'@akryum/tinypool': 0.3.1
'@histoire/app': 0.17.16(vite@5.2.8)
'@histoire/controls': 0.17.16(vite@5.2.8)
'@histoire/shared': 0.17.16(vite@5.2.8)
'@histoire/vendors': 0.17.16
'@histoire/app': 0.17.17(vite@5.2.8)
'@histoire/controls': 0.17.17(vite@5.2.8)
'@histoire/shared': 0.17.17(vite@5.2.8)
'@histoire/vendors': 0.17.17
'@types/flexsearch': 0.7.6
'@types/markdown-it': 12.2.3
birpc: 0.1.1

View File

@ -155,7 +155,6 @@ const savedFilterProjects = computed(() => projectStore.savedFilterProjects)
bottom: 0;
left: 0;
transform: translateX(-100%);
overflow-x: auto;
width: $navbar-width;
@media screen and (max-width: $tablet) {

View File

@ -107,80 +107,109 @@ func (m *Migration) AuthURL() string {
"&return_url=" + config.MigrationTrelloRedirectURL.GetString()
}
func getTrelloData(token string) (trelloData []*trello.Board, err error) {
allArg := trello.Arguments{"fields": "all"}
client := trello.NewClient(config.MigrationTrelloKey.GetString(), token)
client.Logger = log.GetLogger()
func getTrelloBoards(client *trello.Client) (trelloData []*trello.Board, err error) {
log.Debugf("[Trello Migration] Getting boards...")
trelloData, err = client.GetMyBoards(trello.Defaults())
if err != nil {
return
return nil, err
}
log.Debugf("[Trello Migration] Got %d trello boards", len(trelloData))
for _, board := range trelloData {
log.Debugf("[Trello Migration] Getting projects for board %s", board.ID)
return
}
board.Lists, err = board.GetLists(trello.Defaults())
if err != nil {
return
func getTrelloOrganizationsWithBoards(boards []*trello.Board) (boardsByOrg map[string][]*trello.Board) {
boardsByOrg = make(map[string][]*trello.Board)
for _, board := range boards {
// Trello boards without an organization are considered personal boards
if board.IDOrganization == "" {
board.IDOrganization = "Personal"
}
log.Debugf("[Trello Migration] Got %d projects for board %s", len(board.Lists), board.ID)
listMap := make(map[string]*trello.List, len(board.Lists))
for _, list := range board.Lists {
listMap[list.ID] = list
_, has := boardsByOrg[board.IDOrganization]
if !has {
boardsByOrg[board.IDOrganization] = []*trello.Board{}
}
log.Debugf("[Trello Migration] Getting cards for board %s", board.ID)
boardsByOrg[board.IDOrganization] = append(boardsByOrg[board.IDOrganization], board)
}
cards, err := board.GetCards(allArg)
if err != nil {
return nil, err
return
}
func fillCardData(client *trello.Client, board *trello.Board) (err error) {
allArg := trello.Arguments{"fields": "all"}
log.Debugf("[Trello Migration] Getting projects for board %s", board.ID)
board.Lists, err = board.GetLists(trello.Defaults())
if err != nil {
return err
}
log.Debugf("[Trello Migration] Got %d projects for board %s", len(board.Lists), board.ID)
listMap := make(map[string]*trello.List, len(board.Lists))
for _, list := range board.Lists {
listMap[list.ID] = list
}
log.Debugf("[Trello Migration] Getting cards for board %s", board.ID)
cards, err := board.GetCards(allArg)
if err != nil {
return
}
log.Debugf("[Trello Migration] Got %d cards for board %s", len(cards), board.ID)
for _, card := range cards {
list, exists := listMap[card.IDList]
if !exists {
continue
}
log.Debugf("[Trello Migration] Got %d cards for board %s", len(cards), board.ID)
for _, card := range cards {
list, exists := listMap[card.IDList]
if !exists {
continue
}
if card.Badges.Attachments > 0 {
card.Attachments, err = card.GetAttachments(allArg)
if err != nil {
return nil, err
return
}
if len(card.IDCheckLists) > 0 {
for _, checkListID := range card.IDCheckLists {
checklist, err := client.GetChecklist(checkListID, allArg)
if err != nil {
return nil, err
}
checklist.CheckItems = []trello.CheckItem{}
err = client.Get("checklists/"+checkListID+"/checkItems", allArg, &checklist.CheckItems)
if err != nil {
return nil, err
}
card.Checklists = append(card.Checklists, checklist)
log.Debugf("Retrieved checklist %s for card %s", checkListID, card.ID)
}
}
list.Cards = append(list.Cards, card)
}
log.Debugf("[Trello Migration] Looked for attachements on all cards of board %s", board.ID)
if card.Badges.Comments > 0 {
card.Actions, err = card.GetCommentActions()
if err != nil {
return
}
}
if len(card.IDCheckLists) > 0 {
for _, checkListID := range card.IDCheckLists {
checklist, err := client.GetChecklist(checkListID, allArg)
if err != nil {
return err
}
checklist.CheckItems = []trello.CheckItem{}
err = client.Get("checklists/"+checkListID+"/checkItems", allArg, &checklist.CheckItems)
if err != nil {
return err
}
card.Checklists = append(card.Checklists, checklist)
log.Debugf("Retrieved checklist %s for card %s", checkListID, card.ID)
}
}
list.Cards = append(list.Cards, card)
}
log.Debugf("[Trello Migration] Looked for attachements on all cards of board %s", board.ID)
return
}
@ -196,7 +225,7 @@ func convertMarkdownToHTML(input string) (output string, err error) {
// Converts all previously obtained data from trello into the vikunja format.
// `trelloData` should contain all boards with their projects and cards respectively.
func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullVikunjaHierachie []*models.ProjectWithTasksAndBuckets, err error) {
func convertTrelloDataToVikunja(organizationName string, trelloData []*trello.Board, token string, currentMember *trello.Member) (fullVikunjaHierachie []*models.ProjectWithTasksAndBuckets, err error) {
log.Debugf("[Trello Migration] ")
@ -205,7 +234,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
{
Project: models.Project{
ID: pseudoParentID,
Title: "Imported from Trello",
Title: organizationName,
},
},
}
@ -252,9 +281,11 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
log.Debugf("[Trello Migration] Converting card %s", card.ID)
// The usual stuff: Title, description, position, bucket id
task := &models.Task{
Title: card.Name,
BucketID: bucketID,
task := &models.TaskWithComments{
Task: models.Task{
Title: card.Name,
BucketID: bucketID,
},
}
task.Description, err = convertMarkdownToHTML(card.Desc)
@ -362,7 +393,32 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
task.CoverImageAttachmentID = coverAttachment.ID
}
project.Tasks = append(project.Tasks, &models.TaskWithComments{Task: *task})
for _, action := range card.Actions {
if action.DidCommentCard() {
if task.Comments == nil {
task.Comments = []*models.TaskComment{}
}
comment := &models.TaskComment{
Comment: action.Data.Text,
Created: action.Date,
Updated: action.Date,
}
if currentMember == nil || action.IDMemberCreator != currentMember.ID {
comment.Comment = "*" + action.MemberCreator.FullName + "*:\n\n" + comment.Comment
}
comment.Comment, err = convertMarkdownToHTML(comment.Comment)
if err != nil {
return
}
task.Comments = append(task.Comments, comment)
}
}
project.Tasks = append(project.Tasks, task)
}
project.Buckets = append(project.Buckets, bucket)
@ -392,29 +448,62 @@ func (m *Migration) Migrate(u *user.User) (err error) {
log.Debugf("[Trello Migration] Starting migration for user %d", u.ID)
log.Debugf("[Trello Migration] Getting all trello data for user %d", u.ID)
trelloData, err := getTrelloData(m.Token)
client := trello.NewClient(config.MigrationTrelloKey.GetString(), m.Token)
client.Logger = log.GetLogger()
boards, err := getTrelloBoards(client)
if err != nil {
return
}
log.Debugf("[Trello Migration] Got all trello data for user %d", u.ID)
log.Debugf("[Trello Migration] Start converting trello data for user %d", u.ID)
fullVikunjaHierachie, err := convertTrelloDataToVikunja(trelloData, m.Token)
if err != nil {
return
organizationMap := getTrelloOrganizationsWithBoards(boards)
for organizationID, boards := range organizationMap {
log.Debugf("[Trello Migration] Getting organization with id %s for user %d", organizationID, u.ID)
orgName := organizationID
if organizationID != "Personal" {
organization, err := client.GetOrganization(organizationID, trello.Defaults())
if err != nil {
return err
}
orgName = organization.DisplayName
}
for _, board := range boards {
log.Debugf("[Trello Migration] Getting card data for board %s for user %d for organization %s", board.ID, u.ID, organizationID)
err = fillCardData(client, board)
if err != nil {
return err
}
log.Debugf("[Trello Migration] Got card data for board %s for user %d for organization %s", board.ID, u.ID, organizationID)
}
log.Debugf("[Trello Migration] Start converting trello data for user %d for organization %s", u.ID, organizationID)
currentMember, err := client.GetMyMember(trello.Defaults())
if err != nil {
return err
}
hierarchy, err := convertTrelloDataToVikunja(orgName, boards, client.Token, currentMember)
if err != nil {
return err
}
log.Debugf("[Trello Migration] Done migrating trello data for user %d for organization %s", u.ID, organizationID)
log.Debugf("[Trello Migration] Start inserting trello data for user %d for organization %s", u.ID, organizationID)
err = migration.InsertFromStructure(hierarchy, u)
if err != nil {
return err
}
log.Debugf("[Trello Migration] Done inserting trello data for user %d for organization %s", u.ID, organizationID)
}
log.Debugf("[Trello Migration] Done migrating trello data for user %d", u.ID)
log.Debugf("[Trello Migration] Start inserting trello data for user %d", u.ID)
log.Debugf("[Trello Migration] Done migrating all trello data for user %d", u.ID)
err = migration.InsertFromStructure(fullVikunjaHierachie, u)
if err != nil {
return
}
log.Debugf("[Trello Migration] Done inserting trello data for user %d", u.ID)
log.Debugf("[Trello Migration] Migration done for user %d", u.ID)
return nil
return
}

View File

@ -32,20 +32,23 @@ import (
"github.com/stretchr/testify/require"
)
func TestConvertTrelloToVikunja(t *testing.T) {
func getTestBoard(t *testing.T) ([]*trello.Board, time.Time) {
config.InitConfig()
time1, err := time.Parse(time.RFC3339Nano, "2014-09-26T08:25:05Z")
require.NoError(t, err)
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg")
require.NoError(t, err)
trelloData := []*trello.Board{
{
Name: "TestBoard",
Desc: "This is a description",
Closed: false,
Name: "TestBoard",
Organization: trello.Organization{
ID: "orgid",
DisplayName: "TestOrg",
},
IDOrganization: "orgid",
Desc: "This is a description",
Closed: false,
Lists: []*trello.List{
{
Name: "Test Project 1",
@ -168,8 +171,13 @@ func TestConvertTrelloToVikunja(t *testing.T) {
},
},
{
Name: "TestBoard 2",
Closed: false,
Organization: trello.Organization{
ID: "orgid2",
DisplayName: "TestOrg2",
},
IDOrganization: "orgid2",
Name: "TestBoard 2",
Closed: false,
Lists: []*trello.List{
{
Name: "Test Project 4",
@ -183,8 +191,13 @@ func TestConvertTrelloToVikunja(t *testing.T) {
},
},
{
Name: "TestBoard Archived",
Closed: true,
Organization: trello.Organization{
ID: "orgid",
DisplayName: "TestOrg",
},
IDOrganization: "orgid",
Name: "TestBoard Archived",
Closed: true,
Lists: []*trello.List{
{
Name: "Test Project 5",
@ -197,67 +210,91 @@ func TestConvertTrelloToVikunja(t *testing.T) {
},
},
},
{
Name: "Personal Board",
Lists: []*trello.List{
{
Name: "Test Project 6",
Cards: []*trello.Card{
{
Name: "Test Card 5659",
Pos: 123,
},
},
},
},
},
}
trelloData[0].Prefs.BackgroundImage = "https://vikunja.io/testimage.jpg" // Using an image which we are hosting, so it'll still be up
expectedHierachie := []*models.ProjectWithTasksAndBuckets{
{
Project: models.Project{
ID: 1,
Title: "Imported from Trello",
},
},
{
Project: models.Project{
ID: 2,
ParentProjectID: 1,
Title: "TestBoard",
Description: "This is a description",
BackgroundInformation: bytes.NewBuffer(exampleFile),
},
Buckets: []*models.Bucket{
{
return trelloData, time1
}
func TestConvertTrelloToVikunja(t *testing.T) {
trelloData, time1 := getTestBoard(t)
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg")
require.NoError(t, err)
expectedHierarchyOrg := map[string][]*models.ProjectWithTasksAndBuckets{
"orgid": {
{
Project: models.Project{
ID: 1,
Title: "Test Project 1",
},
{
ID: 2,
Title: "Test Project 2",
Title: "orgid",
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Test Card 1",
Description: "<p>Card Description <strong>bold</strong></p>\n",
BucketID: 1,
DueDate: time1,
Labels: []*models.Label{
{
Title: "Label 1",
HexColor: trelloColorMap["green"],
{
Project: models.Project{
ID: 2,
ParentProjectID: 1,
Title: "TestBoard",
Description: "This is a description",
BackgroundInformation: bytes.NewBuffer(exampleFile),
},
Buckets: []*models.Bucket{
{
ID: 1,
Title: "Test Project 1",
},
{
ID: 2,
Title: "Test Project 2",
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Test Card 1",
Description: "<p>Card Description <strong>bold</strong></p>\n",
BucketID: 1,
DueDate: time1,
Labels: []*models.Label{
{
Title: "Label 1",
HexColor: trelloColorMap["green"],
},
{
Title: "Label 2",
HexColor: trelloColorMap["orange"],
},
},
{
Title: "Label 2",
HexColor: trelloColorMap["orange"],
},
},
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "Testimage.jpg",
Mime: "image/jpg",
Size: uint64(len(exampleFile)),
FileContent: exampleFile,
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "Testimage.jpg",
Mime: "image/jpg",
Size: uint64(len(exampleFile)),
FileContent: exampleFile,
},
},
},
},
},
},
{
Task: models.Task{
Title: "Test Card 2",
Description: `
{
Task: models.Task{
Title: "Test Card 2",
Description: `
<h2> Checkproject 1</h2>
@ -270,117 +307,180 @@ func TestConvertTrelloToVikunja(t *testing.T) {
<ul data-type="taskList">
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p>Pending Task</p></div></li>
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label><div><p>Another Pending Task</p></div></li></ul>`,
BucketID: 1,
BucketID: 1,
},
},
},
{
Task: models.Task{
Title: "Test Card 3",
BucketID: 1,
{
Task: models.Task{
Title: "Test Card 3",
BucketID: 1,
},
},
},
{
Task: models.Task{
Title: "Test Card 4",
BucketID: 1,
Labels: []*models.Label{
{
Title: "Label 2",
HexColor: trelloColorMap["orange"],
{
Task: models.Task{
Title: "Test Card 4",
BucketID: 1,
Labels: []*models.Label{
{
Title: "Label 2",
HexColor: trelloColorMap["orange"],
},
},
},
},
},
{
Task: models.Task{
Title: "Test Card 5",
BucketID: 2,
Labels: []*models.Label{
{
Title: "Label 3",
HexColor: trelloColorMap["blue"],
},
{
Title: "Label 4",
HexColor: trelloColorMap["green_dark"],
},
{
Title: "Label 5",
HexColor: trelloColorMap["transparent"],
{
Task: models.Task{
Title: "Test Card 5",
BucketID: 2,
Labels: []*models.Label{
{
Title: "Label 3",
HexColor: trelloColorMap["blue"],
},
{
Title: "Label 4",
HexColor: trelloColorMap["green_dark"],
},
{
Title: "Label 5",
HexColor: trelloColorMap["transparent"],
},
},
},
},
},
{
Task: models.Task{
Title: "Test Card 6",
BucketID: 2,
DueDate: time1,
{
Task: models.Task{
Title: "Test Card 6",
BucketID: 2,
DueDate: time1,
},
},
{
Task: models.Task{
Title: "Test Card 7",
BucketID: 2,
},
},
{
Task: models.Task{
Title: "Test Card 8",
BucketID: 2,
},
},
},
{
Task: models.Task{
Title: "Test Card 7",
BucketID: 2,
},
{
Project: models.Project{
ID: 3,
ParentProjectID: 1,
Title: "TestBoard Archived",
IsArchived: true,
},
Buckets: []*models.Bucket{
{
ID: 3,
Title: "Test Project 5",
},
},
{
Task: models.Task{
Title: "Test Card 8",
BucketID: 2,
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Test Card 63423",
BucketID: 3,
},
},
},
},
},
{
Project: models.Project{
ID: 3,
ParentProjectID: 1,
Title: "TestBoard 2",
},
Buckets: []*models.Bucket{
{
ID: 3,
Title: "Test Project 4",
"orgid2": {
{
Project: models.Project{
ID: 1,
Title: "orgid2",
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Test Card 634",
BucketID: 3,
{
Project: models.Project{
ID: 2,
ParentProjectID: 1,
Title: "TestBoard 2",
},
Buckets: []*models.Bucket{
{
ID: 1,
Title: "Test Project 4",
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Test Card 634",
BucketID: 1,
},
},
},
},
},
{
Project: models.Project{
ID: 4,
ParentProjectID: 1,
Title: "TestBoard Archived",
IsArchived: true,
},
Buckets: []*models.Bucket{
{
ID: 4,
Title: "Test Project 5",
"Personal": {
{
Project: models.Project{
ID: 1,
Title: "Personal",
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Test Card 63423",
BucketID: 4,
{
Project: models.Project{
ID: 2,
ParentProjectID: 1,
Title: "Personal Board",
},
Buckets: []*models.Bucket{
{
ID: 1,
Title: "Test Project 6",
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Test Card 5659",
BucketID: 1,
},
},
},
},
},
}
hierachie, err := convertTrelloDataToVikunja(trelloData, "")
require.NoError(t, err)
assert.NotNil(t, hierachie)
if diff, equal := messagediff.PrettyDiff(hierachie, expectedHierachie); !equal {
t.Errorf("converted trello data = %v, want %v, diff: %v", hierachie, expectedHierachie, diff)
organizationMap := getTrelloOrganizationsWithBoards(trelloData)
for organizationID, boards := range organizationMap {
hierarchy, err := convertTrelloDataToVikunja(organizationID, boards, "", nil)
require.NoError(t, err)
assert.NotNil(t, hierarchy)
if diff, equal := messagediff.PrettyDiff(hierarchy, expectedHierarchyOrg[organizationID]); !equal {
t.Errorf("converted trello data = %v,\nwant %v,\ndiff: %v", hierarchy, expectedHierarchyOrg[organizationID], diff)
}
}
}
func TestCreateOrganizationMap(t *testing.T) {
trelloData, _ := getTestBoard(t)
organizationMap := getTrelloOrganizationsWithBoards(trelloData)
expectedMap := map[string][]*trello.Board{
"orgid": {
trelloData[0],
trelloData[2],
},
"orgid2": {
trelloData[1],
},
"Personal": {
trelloData[3],
},
}
if diff, equal := messagediff.PrettyDiff(organizationMap, expectedMap); !equal {
t.Errorf("converted trello data = %v,\nwant %v,\ndiff: %v", organizationMap, expectedMap, diff)
}
}