diff --git a/pkg/db/fixtures/buckets.yml b/pkg/db/fixtures/buckets.yml index c1ddcd94f6..92c25cf7a9 100644 --- a/pkg/db/fixtures/buckets.yml +++ b/pkg/db/fixtures/buckets.yml @@ -3,6 +3,7 @@ list_id: 1 created_by_id: 1 limit: 9999999 # This bucket has a limit we will never exceed in the tests to make sure the logic allows for buckets with limits + position: 2 created: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52 - id: 2 @@ -10,6 +11,7 @@ list_id: 1 created_by_id: 1 limit: 3 + position: 1 created: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52 - id: 3 @@ -17,6 +19,7 @@ list_id: 1 created_by_id: 1 is_done_bucket: 1 + position: 3 created: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52 - id: 4 diff --git a/pkg/db/fixtures/lists.yml b/pkg/db/fixtures/lists.yml index 45336f951d..e40f92d0dd 100644 --- a/pkg/db/fixtures/lists.yml +++ b/pkg/db/fixtures/lists.yml @@ -5,6 +5,7 @@ identifier: test1 owner_id: 1 namespace_id: 1 + position: 3 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -14,6 +15,7 @@ identifier: test2 owner_id: 3 namespace_id: 1 + position: 2 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -23,6 +25,7 @@ identifier: test3 owner_id: 3 namespace_id: 2 + position: 1 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -32,6 +35,7 @@ identifier: test4 owner_id: 3 namespace_id: 3 + position: 4 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -41,6 +45,7 @@ identifier: test5 owner_id: 5 namespace_id: 5 + position: 5 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -50,6 +55,7 @@ identifier: test6 owner_id: 6 namespace_id: 6 + position: 6 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -59,6 +65,7 @@ identifier: test7 owner_id: 6 namespace_id: 6 + position: 7 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -68,6 +75,7 @@ identifier: test8 owner_id: 6 namespace_id: 6 + position: 8 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -77,6 +85,7 @@ identifier: test9 owner_id: 6 namespace_id: 6 + position: 9 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -86,6 +95,7 @@ identifier: test10 owner_id: 6 namespace_id: 6 + position: 10 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -95,6 +105,7 @@ identifier: test11 owner_id: 6 namespace_id: 6 + position: 11 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -104,6 +115,7 @@ identifier: test12 owner_id: 6 namespace_id: 7 + position: 12 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -113,6 +125,7 @@ identifier: test13 owner_id: 6 namespace_id: 8 + position: 13 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -122,6 +135,7 @@ identifier: test14 owner_id: 6 namespace_id: 9 + position: 14 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -131,6 +145,7 @@ identifier: test15 owner_id: 6 namespace_id: 10 + position: 15 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -140,6 +155,7 @@ identifier: test16 owner_id: 6 namespace_id: 11 + position: 16 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -149,6 +165,7 @@ identifier: test17 owner_id: 6 namespace_id: 12 + position: 17 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 # This list is owned by user 7, and several other users have access to it via different methods. @@ -160,6 +177,7 @@ identifier: test18 owner_id: 7 namespace_id: 13 + position: 18 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -169,6 +187,7 @@ identifier: test19 owner_id: 7 namespace_id: 14 + position: 19 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 # User 1 does not have access to this list @@ -179,6 +198,7 @@ identifier: test20 owner_id: 13 namespace_id: 15 + position: 20 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -188,6 +208,7 @@ identifier: test21 owner_id: 1 namespace_id: 16 + position: 21 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -198,6 +219,7 @@ owner_id: 1 namespace_id: 1 is_archived: 1 + position: 22 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 - @@ -207,5 +229,6 @@ identifier: test23 owner_id: 12 namespace_id: 17 + position: 23 updated: 2018-12-02 15:13:12 created: 2018-12-01 15:13:12 diff --git a/pkg/db/fixtures/tasks.yml b/pkg/db/fixtures/tasks.yml index 4d944fd1a1..ec0abf0bc8 100644 --- a/pkg/db/fixtures/tasks.yml +++ b/pkg/db/fixtures/tasks.yml @@ -8,6 +8,7 @@ created: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04 bucket_id: 1 + position: 2 - id: 2 title: 'task #2 done' done: true @@ -17,6 +18,7 @@ created: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04 bucket_id: 1 + position: 4 - id: 3 title: 'task #3 high prio' done: false diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index 7915af7de5..bbd64c2559 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -113,49 +113,49 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by priority desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) + assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) }) t.Run("by priority asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) // should equal duedate asc t.Run("by due_date", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by duedate desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) + assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) }) // Due date without unix suffix t.Run("by duedate asc without suffix", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by due_date without suffix", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by duedate desc without suffix", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) + assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) }) t.Run("by duedate asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("invalid sort parameter", func(t *testing.T) { _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams) @@ -341,33 +341,33 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by priority desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) + assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) }) t.Run("by priority asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) // should equal duedate asc t.Run("by due_date", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by duedate desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) + assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) }) t.Run("by duedate asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("invalid parameter", func(t *testing.T) { // Invalid parameter should not sort at all diff --git a/pkg/migration/20210725153703.go b/pkg/migration/20210725153703.go new file mode 100644 index 0000000000..2eedb69d79 --- /dev/null +++ b/pkg/migration/20210725153703.go @@ -0,0 +1,71 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public Licensee as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "math" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type tasks20210725153703 struct { + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"listtask"` + Position float64 `xorm:"double null" json:"position"` + KanbanPosition float64 `xorm:"double null" json:"kanban_position"` +} + +func (tasks20210725153703) TableName() string { + return "tasks" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20210725153703", + Description: "", + Migrate: func(tx *xorm.Engine) error { + err := tx.Sync2(tasks20210725153703{}) + if err != nil { + return err + } + + tasks := []*tasks20210725153703{} + err = tx.Where("position is not null").Find(&tasks) + if err != nil { + return err + } + + // Migrate all old kanban positions to the kanban positions property + for _, task := range tasks { + task.KanbanPosition = task.Position + task.Position = float64(task.ID) * math.Pow(2, 16) + _, err = tx. + Where("id = ?", task.ID). + Cols("kanban_position", "position"). + Update(task) + if err != nil { + return err + } + } + + return nil + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/migration/20210727204942.go b/pkg/migration/20210727204942.go new file mode 100644 index 0000000000..04fb82b044 --- /dev/null +++ b/pkg/migration/20210727204942.go @@ -0,0 +1,68 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public Licensee as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "math" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type lists20210727204942 struct { + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"list"` + Position float64 `xorm:"double null" json:"position"` +} + +func (lists20210727204942) TableName() string { + return "lists" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20210727204942", + Description: "Add list position parameter", + Migrate: func(tx *xorm.Engine) error { + err := tx.Sync2(lists20210727204942{}) + if err != nil { + return err + } + + lists := []*lists20210727204942{} + err = tx.Find(&lists) + if err != nil { + return err + } + + for _, list := range lists { + list.Position = float64(list.ID) * math.Pow(2, 16) + + _, err = tx. + Where("id = ?", list.ID). + Update(list) + if err != nil { + return err + } + } + + return nil + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/migration/20210727211037.go b/pkg/migration/20210727211037.go new file mode 100644 index 0000000000..53ce74a948 --- /dev/null +++ b/pkg/migration/20210727211037.go @@ -0,0 +1,68 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2021 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public Licensee as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +package migration + +import ( + "math" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +type buckets20210727211037 struct { + ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"list"` + Position float64 `xorm:"double null" json:"position"` +} + +func (buckets20210727211037) TableName() string { + return "buckets" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20210727211037", + Description: "Add bucket position property", + Migrate: func(tx *xorm.Engine) error { + err := tx.Sync2(buckets20210727211037{}) + if err != nil { + return err + } + + buckets := []*buckets20210727211037{} + err = tx.Find(&buckets) + if err != nil { + return err + } + + for _, bucket := range buckets { + bucket.Position = float64(bucket.ID) * math.Pow(2, 16) + + _, err = tx. + Where("id = ?", bucket.ID). + Update(bucket) + if err != nil { + return err + } + } + + return nil + }, + Rollback: func(tx *xorm.Engine) error { + return nil + }, + }) +} diff --git a/pkg/models/kanban.go b/pkg/models/kanban.go index 6c00163779..2407bc3180 100644 --- a/pkg/models/kanban.go +++ b/pkg/models/kanban.go @@ -41,6 +41,9 @@ type Bucket struct { // If this bucket is the "done bucket". All tasks moved into this bucket will automatically marked as done. All tasks marked as done from elsewhere will be moved into this bucket. IsDoneBucket bool `xorm:"BOOL" json:"is_done_bucket"` + // The position this bucket has when querying all buckets. See the tasks.position property on how to use this. + Position float64 `xorm:"double null" json:"position"` + // A timestamp when this bucket was created. You cannot change this value. Created time.Time `xorm:"created not null" json:"created"` // A timestamp when this bucket was last updated. You cannot change this value. @@ -134,7 +137,10 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int // Get all buckets for this list buckets := []*Bucket{} - err = s.Where("list_id = ?", b.ListID).Find(&buckets) + err = s. + Where("list_id = ?", b.ListID). + OrderBy("position"). + Find(&buckets) if err != nil { return } @@ -167,7 +173,7 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int opts.sortby = []*sortParam{ { orderBy: orderAscending, - sortBy: taskPropertyPosition, + sortBy: taskPropertyKanbanPosition, }, } opts.page = page @@ -251,6 +257,12 @@ func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) { b.CreatedByID = b.CreatedBy.ID _, err = s.Insert(b) + if err != nil { + return + } + + b.Position = calculateDefaultPosition(b.ID, b.Position) + _, err = s.Where("id = ?", b.ID).Update(b) return } @@ -289,6 +301,7 @@ func (b *Bucket) Update(s *xorm.Session, a web.Auth) (err error) { "title", "limit", "is_done_bucket", + "position", ). Update(b) return diff --git a/pkg/models/kanban_test.go b/pkg/models/kanban_test.go index 1d41809a08..079da0f408 100644 --- a/pkg/models/kanban_test.go +++ b/pkg/models/kanban_test.go @@ -49,21 +49,23 @@ func TestBucket_ReadAll(t *testing.T) { assert.Len(t, buckets, 3) // Assert all tasks are in the right bucket - assert.Len(t, buckets[0].Tasks, 12) - assert.Len(t, buckets[1].Tasks, 3) + assert.Len(t, buckets[0].Tasks, 3) + assert.Len(t, buckets[1].Tasks, 12) assert.Len(t, buckets[2].Tasks, 3) - // Assert we have bucket 0, 1, 2, 3 but not 4 (that belongs to a different list) - assert.Equal(t, int64(1), buckets[0].ID) - assert.Equal(t, int64(2), buckets[1].ID) + // Assert we have bucket 1, 2, 3 but not 4 (that belongs to a different list) and their position + assert.Equal(t, int64(2), buckets[0].ID) + assert.Equal(t, int64(1), buckets[1].ID) assert.Equal(t, int64(3), buckets[2].ID) // Kinda assert all tasks are in the right buckets - assert.Equal(t, int64(1), buckets[0].Tasks[0].BucketID) - assert.Equal(t, int64(1), buckets[0].Tasks[1].BucketID) - assert.Equal(t, int64(2), buckets[1].Tasks[0].BucketID) - assert.Equal(t, int64(2), buckets[1].Tasks[1].BucketID) - assert.Equal(t, int64(2), buckets[1].Tasks[2].BucketID) + assert.Equal(t, int64(1), buckets[1].Tasks[0].BucketID) + assert.Equal(t, int64(1), buckets[1].Tasks[1].BucketID) + + assert.Equal(t, int64(2), buckets[0].Tasks[0].BucketID) + assert.Equal(t, int64(2), buckets[0].Tasks[1].BucketID) + assert.Equal(t, int64(2), buckets[0].Tasks[2].BucketID) + assert.Equal(t, int64(3), buckets[2].Tasks[0].BucketID) assert.Equal(t, int64(3), buckets[2].Tasks[1].BucketID) assert.Equal(t, int64(3), buckets[2].Tasks[2].BucketID) @@ -87,7 +89,8 @@ func TestBucket_ReadAll(t *testing.T) { buckets := bucketsInterface.([]*Bucket) assert.Len(t, buckets, 3) - assert.Equal(t, int64(2), buckets[0].Tasks[0].ID) + assert.Equal(t, int64(2), buckets[1].Tasks[0].ID) + assert.Equal(t, int64(33), buckets[1].Tasks[1].ID) }) t.Run("accessed by link share", func(t *testing.T) { db.LoadAndAssertFixtures(t) diff --git a/pkg/models/list.go b/pkg/models/list.go index 61d4ebe8ba..3c1ac7a047 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -71,6 +71,9 @@ type List struct { // Will only returned when retreiving one list. Subscription *Subscription `xorm:"-" json:"subscription,omitempty"` + // The position this list has when querying all lists. See the tasks.position property on how to use this. + Position float64 `xorm:"double null" json:"position"` + // A timestamp when this list was created. You cannot change this value. Created time.Time `xorm:"created not null" json:"created"` // A timestamp when this list was last updated. You cannot change this value. @@ -284,7 +287,10 @@ func GetListSimpleByID(s *xorm.Session, listID int64) (list *List, err error) { return nil, ErrListDoesNotExist{ID: listID} } - exists, err := s.Where("id = ?", listID).Get(list) + exists, err := s. + Where("id = ?", listID). + OrderBy("position"). + Get(list) if err != nil { return } @@ -361,6 +367,7 @@ func getUserListsStatement(userID int64) *builder.Builder { builder.Eq{"un.user_id": userID}, builder.Eq{"l.owner_id": userID}, )). + OrderBy("position"). GroupBy("l.id") } @@ -559,6 +566,12 @@ func CreateOrUpdateList(s *xorm.Session, list *List, auth web.Auth) (err error) if err != nil { return } + + list.Position = calculateDefaultPosition(list.ID, list.Position) + _, err = s.Where("id = ?", list.ID).Update(list) + if err != nil { + return + } if list.IsFavorite { if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil { return err @@ -572,6 +585,7 @@ func CreateOrUpdateList(s *xorm.Session, list *List, auth web.Auth) (err error) "identifier", "hex_color", "background_file_id", + "position", } if list.Description != "" { colsToUpdate = append(colsToUpdate, "description") diff --git a/pkg/models/list_test.go b/pkg/models/list_test.go index c80355a594..5a52b9016e 100644 --- a/pkg/models/list_test.go +++ b/pkg/models/list_test.go @@ -200,8 +200,11 @@ func TestList_ReadAll(t *testing.T) { assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) - ls := reflect.ValueOf(lists3) - assert.Equal(t, 16, ls.Len()) + ls := lists3.([]*List) + assert.Equal(t, 16, len(ls)) + assert.Equal(t, int64(3), ls[0].ID) // List 3 has a position of 1 and should be sorted first + assert.Equal(t, int64(1), ls[1].ID) + assert.Equal(t, int64(4), ls[2].ID) _ = s.Close() }) t.Run("lists for nonexistant user", func(t *testing.T) { diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 04f2bad6e7..969bd0e2f5 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -315,6 +315,7 @@ func getNamespaceSubscriptions(s *xorm.Session, namespaceIDs []int64, userID int func getListsForNamespaces(s *xorm.Session, namespaceIDs []int64, archived bool) ([]*List, error) { lists := []*List{} listQuery := s. + OrderBy("position"). In("namespace_id", namespaceIDs) if !archived { diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 3b749dbc75..ec2bf0a5ca 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -73,11 +73,11 @@ func validateTaskField(fieldName string) error { taskPropertyCreated, taskPropertyUpdated, taskPropertyPosition, + taskPropertyKanbanPosition, taskPropertyBucketID: return nil } return ErrInvalidTaskField{TaskField: fieldName} - } func getTaskFilterOptsFromCollection(tf *TaskCollection) (opts *taskOptions, err error) { diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 1cea923096..7b72cc2090 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -26,25 +26,26 @@ type ( ) const ( - taskPropertyID string = "id" - taskPropertyTitle string = "title" - taskPropertyDescription string = "description" - taskPropertyDone string = "done" - taskPropertyDoneAt string = "done_at" - taskPropertyDueDate string = "due_date" - taskPropertyCreatedByID string = "created_by_id" - taskPropertyListID string = "list_id" - taskPropertyRepeatAfter string = "repeat_after" - taskPropertyPriority string = "priority" - taskPropertyStartDate string = "start_date" - taskPropertyEndDate string = "end_date" - taskPropertyHexColor string = "hex_color" - taskPropertyPercentDone string = "percent_done" - taskPropertyUID string = "uid" - taskPropertyCreated string = "created" - taskPropertyUpdated string = "updated" - taskPropertyPosition string = "position" - taskPropertyBucketID string = "bucket_id" + taskPropertyID string = "id" + taskPropertyTitle string = "title" + taskPropertyDescription string = "description" + taskPropertyDone string = "done" + taskPropertyDoneAt string = "done_at" + taskPropertyDueDate string = "due_date" + taskPropertyCreatedByID string = "created_by_id" + taskPropertyListID string = "list_id" + taskPropertyRepeatAfter string = "repeat_after" + taskPropertyPriority string = "priority" + taskPropertyStartDate string = "start_date" + taskPropertyEndDate string = "end_date" + taskPropertyHexColor string = "hex_color" + taskPropertyPercentDone string = "percent_done" + taskPropertyUID string = "uid" + taskPropertyCreated string = "created" + taskPropertyUpdated string = "updated" + taskPropertyPosition string = "position" + taskPropertyKanbanPosition string = "kanban_position" + taskPropertyBucketID string = "bucket_id" ) const ( diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 28b70dbd9a..8452892695 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -90,6 +90,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ListID: 1, BucketID: 1, IsFavorite: true, + Position: 2, Labels: []*Label{ label4, }, @@ -160,6 +161,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { CreatedBy: user1, ListID: 1, BucketID: 1, + Position: 4, Labels: []*Label{ label4, }, @@ -517,6 +519,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { Created: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc), BucketID: 1, + Position: 2, }, }, }, @@ -1033,6 +1036,51 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, wantErr: false, }, + { + name: "order by position", + fields: fields{ + SortBy: []string{"position", "id"}, + OrderBy: []string{"asc", "asc"}, + }, + args: args{ + a: &user.User{ID: 1}, + }, + want: []*Task{ + // the other ones don't have a position set + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + task11, + task12, + task15, + task16, + task17, + task18, + task19, + task20, + task21, + task22, + task23, + task24, + task25, + task26, + task27, + task28, + task29, + task30, + task31, + task32, + task33, + // The only tasks with a position set + task1, + task2, + }, + }, } for _, tt := range tests { diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 9479c42f94..8995733421 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -118,6 +118,8 @@ type Task struct { // A 64-Bit float leaves plenty of room to initially give tasks a position with 2^16 difference to the previous task // which also leaves a lot of room for rearranging and sorting later. Position float64 `xorm:"double null" json:"position"` + // The position of tasks in the kanban board. See the docs for the `position` property on how to use this. + KanbanPosition float64 `xorm:"double null" json:"kanban_position"` // The user who initially created the task. CreatedBy *user.User `xorm:"-" json:"created_by" valid:"-"` @@ -836,6 +838,14 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke return nil } +func calculateDefaultPosition(entityID int64, position float64) float64 { + if position == 0 { + return float64(entityID) * math.Pow(2, 16) + } + + return position +} + // Create is the implementation to create a list task // @Summary Create a task // @Description Inserts a task into a list. @@ -895,9 +905,8 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err t.Index = latestTask.Index + 1 // If no position was supplied, set a default one - if t.Position == 0 { - t.Position = float64(latestTask.ID+1) * math.Pow(2, 16) - } + t.Position = calculateDefaultPosition(latestTask.ID+1, t.Position) + t.KanbanPosition = calculateDefaultPosition(latestTask.ID+1, t.KanbanPosition) if _, err = s.Insert(t); err != nil { return err } @@ -1001,6 +1010,7 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { "bucket_id", "position", "repeat_mode", + "kanban_position", } // When a repeating task is marked as done, we update all deadlines and reminders and set it as undone @@ -1107,6 +1117,9 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) { if t.Position == 0 { ot.Position = 0 } + if t.KanbanPosition == 0 { + ot.KanbanPosition = 0 + } // Repeat from current date if t.RepeatMode == TaskRepeatModeDefault { ot.RepeatMode = TaskRepeatModeDefault diff --git a/pkg/models/tasks_test.go b/pkg/models/tasks_test.go index 7a5dc69ffb..49cb590c79 100644 --- a/pkg/models/tasks_test.go +++ b/pkg/models/tasks_test.go @@ -192,12 +192,12 @@ func TestTask_Update(t *testing.T) { defer s.Close() task := &Task{ - ID: 4, - Title: "test10000", - Description: "Lorem Ipsum Dolor", - Position: 10, - ListID: 1, - BucketID: 2, // Bucket 2 already has 3 tasks and a limit of 3 + ID: 4, + Title: "test10000", + Description: "Lorem Ipsum Dolor", + KanbanPosition: 10, + ListID: 1, + BucketID: 2, // Bucket 2 already has 3 tasks and a limit of 3 } err := task.Update(s, u) assert.NoError(t, err) diff --git a/pkg/modules/migration/trello/trello.go b/pkg/modules/migration/trello/trello.go index 316155b101..e637218871 100644 --- a/pkg/modules/migration/trello/trello.go +++ b/pkg/modules/migration/trello/trello.go @@ -196,10 +196,10 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachi // The usual stuff: Title, description, position, bucket id task := &models.Task{ - Title: card.Name, - Description: card.Desc, - Position: card.Pos, - BucketID: bucketID, + Title: card.Name, + Description: card.Desc, + KanbanPosition: card.Pos, + BucketID: bucketID, } if card.Due != nil { diff --git a/pkg/modules/migration/trello/trello_test.go b/pkg/modules/migration/trello/trello_test.go index 5a9d248506..99e2170d1d 100644 --- a/pkg/modules/migration/trello/trello_test.go +++ b/pkg/modules/migration/trello/trello_test.go @@ -209,11 +209,11 @@ func TestConvertTrelloToVikunja(t *testing.T) { }, Tasks: []*models.Task{ { - Title: "Test Card 1", - Description: "Card Description", - BucketID: 1, - Position: 123, - DueDate: time1, + Title: "Test Card 1", + Description: "Card Description", + BucketID: 1, + KanbanPosition: 123, + DueDate: time1, Labels: []*models.Label{ { Title: "Label 1", @@ -248,18 +248,18 @@ func TestConvertTrelloToVikunja(t *testing.T) { * [ ] Pending Task * [ ] Another Pending Task`, - BucketID: 1, - Position: 124, + BucketID: 1, + KanbanPosition: 124, }, { - Title: "Test Card 3", - BucketID: 1, - Position: 126, + Title: "Test Card 3", + BucketID: 1, + KanbanPosition: 126, }, { - Title: "Test Card 4", - BucketID: 1, - Position: 127, + Title: "Test Card 4", + BucketID: 1, + KanbanPosition: 127, Labels: []*models.Label{ { Title: "Label 2", @@ -268,9 +268,9 @@ func TestConvertTrelloToVikunja(t *testing.T) { }, }, { - Title: "Test Card 5", - BucketID: 2, - Position: 111, + Title: "Test Card 5", + BucketID: 2, + KanbanPosition: 111, Labels: []*models.Label{ { Title: "Label 3", @@ -279,20 +279,20 @@ func TestConvertTrelloToVikunja(t *testing.T) { }, }, { - Title: "Test Card 6", - BucketID: 2, - Position: 222, - DueDate: time1, + Title: "Test Card 6", + BucketID: 2, + KanbanPosition: 222, + DueDate: time1, }, { - Title: "Test Card 7", - BucketID: 2, - Position: 333, + Title: "Test Card 7", + BucketID: 2, + KanbanPosition: 333, }, { - Title: "Test Card 8", - BucketID: 2, - Position: 444, + Title: "Test Card 8", + BucketID: 2, + KanbanPosition: 444, }, }, }, @@ -306,9 +306,9 @@ func TestConvertTrelloToVikunja(t *testing.T) { }, Tasks: []*models.Task{ { - Title: "Test Card 634", - BucketID: 3, - Position: 123, + Title: "Test Card 634", + BucketID: 3, + KanbanPosition: 123, }, }, }, @@ -323,9 +323,9 @@ func TestConvertTrelloToVikunja(t *testing.T) { }, Tasks: []*models.Task{ { - Title: "Test Card 63423", - BucketID: 4, - Position: 123, + Title: "Test Card 63423", + BucketID: 4, + KanbanPosition: 123, }, }, }, diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 1743ccd2dd..e0c52082d0 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -7218,6 +7218,10 @@ var doc = `{ "type": "string" } }, + "position": { + "description": "The position this bucket has when querying all buckets. See the tasks.position property on how to use this.", + "type": "number" + }, "sort_by": { "description": "The query parameter to sort by. This is for ex. done, priority, etc.", "type": "array", @@ -7325,6 +7329,10 @@ var doc = `{ "description": "True if a task is a favorite task. Favorite tasks show up in a separate \"Important\" list. This value depends on the user making the call to the api.", "type": "boolean" }, + "kanban_position": { + "description": "The position of tasks in the kanban board. See the docs for the ` + "`" + `position` + "`" + ` property on how to use this.", + "type": "number" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", @@ -7573,6 +7581,10 @@ var doc = `{ "description": "The user who created this list.", "$ref": "#/definitions/user.User" }, + "position": { + "description": "The position this list has when querying all lists. See the tasks.position property on how to use this.", + "type": "number" + }, "subscription": { "description": "The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one list.", "$ref": "#/definitions/models.Subscription" @@ -7901,6 +7913,10 @@ var doc = `{ "description": "True if a task is a favorite task. Favorite tasks show up in a separate \"Important\" list. This value depends on the user making the call to the api.", "type": "boolean" }, + "kanban_position": { + "description": "The position of tasks in the kanban board. See the docs for the ` + "`" + `position` + "`" + ` property on how to use this.", + "type": "number" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index 6514dc4ba6..2bc4b1ef0d 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -7201,6 +7201,10 @@ "type": "string" } }, + "position": { + "description": "The position this bucket has when querying all buckets. See the tasks.position property on how to use this.", + "type": "number" + }, "sort_by": { "description": "The query parameter to sort by. This is for ex. done, priority, etc.", "type": "array", @@ -7308,6 +7312,10 @@ "description": "True if a task is a favorite task. Favorite tasks show up in a separate \"Important\" list. This value depends on the user making the call to the api.", "type": "boolean" }, + "kanban_position": { + "description": "The position of tasks in the kanban board. See the docs for the `position` property on how to use this.", + "type": "number" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", @@ -7556,6 +7564,10 @@ "description": "The user who created this list.", "$ref": "#/definitions/user.User" }, + "position": { + "description": "The position this list has when querying all lists. See the tasks.position property on how to use this.", + "type": "number" + }, "subscription": { "description": "The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one list.", "$ref": "#/definitions/models.Subscription" @@ -7884,6 +7896,10 @@ "description": "True if a task is a favorite task. Favorite tasks show up in a separate \"Important\" list. This value depends on the user making the call to the api.", "type": "boolean" }, + "kanban_position": { + "description": "The position of tasks in the kanban board. See the docs for the `position` property on how to use this.", + "type": "number" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index ec7e0c9abc..3f5aebca17 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -102,6 +102,10 @@ definitions: items: type: string type: array + position: + description: The position this bucket has when querying all buckets. See the + tasks.position property on how to use this. + type: number sort_by: description: The query parameter to sort by. This is for ex. done, priority, etc. @@ -186,6 +190,10 @@ definitions: a separate "Important" list. This value depends on the user making the call to the api. type: boolean + kanban_position: + description: The position of tasks in the kanban board. See the docs for the + `position` property on how to use this. + type: number labels: description: An array of labels which are associated with this task. items: @@ -403,6 +411,10 @@ definitions: owner: $ref: '#/definitions/user.User' description: The user who created this list. + position: + description: The position this list has when querying all lists. See the tasks.position + property on how to use this. + type: number subscription: $ref: '#/definitions/models.Subscription' description: |- @@ -669,6 +681,10 @@ definitions: a separate "Important" list. This value depends on the user making the call to the api. type: boolean + kanban_position: + description: The position of tasks in the kanban board. See the docs for the + `position` property on how to use this. + type: number labels: description: An array of labels which are associated with this task. items: