• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rwjdk / TrelloDotNet / 9191886850

22 May 2024 01:06PM UTC coverage: 61.525% (-0.7%) from 62.198%
9191886850

push

github

rwjdk
Version 1.10.2

890 of 1809 branches covered (49.2%)

Branch coverage included in aggregate %.

0 of 32 new or added lines in 2 files covered. (0.0%)

1 existing line in 1 file now uncovered.

2329 of 3423 relevant lines covered (68.04%)

51.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.8
/TrelloDotNet/TrelloDotNet/TrelloClient.Cards.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Text.Json;
5
using System.Threading;
6
using System.Threading.Tasks;
7
using TrelloDotNet.Control;
8
using TrelloDotNet.Model;
9
using TrelloDotNet.Model.Options;
10
using TrelloDotNet.Model.Options.GetCardOptions;
11
using TrelloDotNet.Model.Options.MoveCardToBoardOptions;
12
using TrelloDotNet.Model.Options.MoveCardToListOptions;
13

14
namespace TrelloDotNet
15
{
16
    public partial class TrelloClient
17
    {
18
        /// <summary>
19
        /// Add a Card
20
        /// </summary>
21
        /// <param name="card">The Card to Add</param>
22
        /// <param name="cancellationToken">Cancellation Token</param>
23
        /// <returns>The Added Card</returns>
24
        public async Task<Card> AddCardAsync(Card card, CancellationToken cancellationToken = default)
25
        {
26
            QueryParameter[] parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(card);
59✔
27
            _queryParametersBuilder.AdjustForNamedPosition(parameters, card.NamedPosition);
59✔
28
            var result = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
59✔
29
            if (card.Cover != null)
59✔
30
            {
31
                return await AddCoverToCardAsync(result.Id, card.Cover, cancellationToken);
1✔
32
            }
33

34
            return result;
58✔
35
        }
59✔
36

37
        /// <summary>
38
        /// Archive (Close) a Card
39
        /// </summary>
40
        /// <param name="cardId">The id of card that should be archived</param>
41
        /// <param name="cancellationToken">Cancellation Token</param>
42
        /// <returns>The Archived Card</returns>
43
        public async Task<Card> ArchiveCardAsync(string cardId, CancellationToken cancellationToken = default)
44
        {
45
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", true));
1✔
46
        }
1✔
47

48
        /// <summary>
49
        /// ReOpen (Send back to board) a Card
50
        /// </summary>
51
        /// <param name="cardId">The id of card that should be reopened</param>
52
        /// <param name="cancellationToken">Cancellation Token</param>
53
        /// <returns>The ReOpened Card</returns>
54
        public async Task<Card> ReOpenCardAsync(string cardId, CancellationToken cancellationToken = default)
55
        {
56
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", false));
1✔
57
        }
1✔
58

59
        /// <summary>
60
        /// Update a Card
61
        /// </summary>
62
        /// <param name="cardWithChanges">The card with the changes</param>
63
        /// <param name="cancellationToken">Cancellation Token</param>
64
        /// <returns>The Updated Card</returns>
65
        public async Task<Card> UpdateCardAsync(Card cardWithChanges, CancellationToken cancellationToken = default)
66
        {
67
            var parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(cardWithChanges).ToList();
9✔
68
            CardCover cardCover = cardWithChanges.Cover;
9✔
69
            _queryParametersBuilder.AdjustForNamedPosition(parameters, cardWithChanges.NamedPosition);
9✔
70
            var payload = GeneratePayloadForCoverUpdate(cardCover, parameters);
9✔
71
            return await _apiRequestController.PutWithJsonPayload<Card>($"{UrlPaths.Cards}/{cardWithChanges.Id}", cancellationToken, payload, parameters.ToArray());
9✔
72
        }
9✔
73

74
        private static string GeneratePayloadForCoverUpdate(CardCover cardCover, List<QueryParameter> parameters)
75
        {
76
            //Special code for Cover
77
            string payload = string.Empty;
10✔
78
            if (cardCover == null)
10✔
79
            {
80
                //Remove cover
81
                parameters.Add(new QueryParameter("cover", ""));
1✔
82
            }
83
            else
84
            {
85
                cardCover.PrepareForAddUpdate();
9✔
86
                if (cardCover.Color != null || cardCover.BackgroundImageId != null)
9✔
87
                {
88
                    QueryParameter queryParameter = parameters.FirstOrDefault(x => x.Name == "idAttachmentCover");
29✔
89
                    if (queryParameter != null)
3✔
90
                    {
91
                        parameters.Remove(queryParameter); //This parameter can't be there while a cover is added
2✔
92
                    }
93
                }
94

95
                payload = $"{{\"cover\":{JsonSerializer.Serialize(cardCover)}}}";
9✔
96
            }
97

98
            return payload;
10✔
99
        }
100

101
        /// <summary>
102
        /// Archive all cards on in a List
103
        /// </summary>
104
        /// <param name="listId">The id of the List that should have its cards archived</param>
105
        /// <param name="cancellationToken">Cancellation Token</param>
106
        public async Task ArchiveAllCardsInListAsync(string listId, CancellationToken cancellationToken = default)
107
        {
108
            await _apiRequestController.Post<List>($"{UrlPaths.Lists}/{listId}/archiveAllCards", cancellationToken);
1✔
109
        }
1✔
110

111
        /// <summary>
112
        /// Move all cards of a list to another list
113
        /// </summary>
114
        /// <param name="currentListId">The id of the List that should have its cards moved</param>
115
        /// <param name="newListId">The id of the new List that should receive the cards</param>
116
        /// <param name="cancellationToken">Cancellation Token</param>
117
        public async Task MoveAllCardsInListAsync(string currentListId, string newListId, CancellationToken cancellationToken = default)
118
        {
119
            var newList = await GetListAsync(newListId, cancellationToken); //Get the new list's BoardId so the user do not need to provide it.
1✔
120
            await _apiRequestController.Post($"{UrlPaths.Lists}/{currentListId}/moveAllCards", cancellationToken,
1✔
121
                0,
1✔
122
                new QueryParameter("idBoard", newList.BoardId),
1✔
123
                new QueryParameter("idList", newListId)
1✔
124
            );
1✔
125
        }
1✔
126

127
        /// <summary>
128
        /// Delete a Card (WARNING: THERE IS NO WAY GOING BACK!!!). Alternative use ArchiveCardAsync() for non-permanency
129
        /// </summary>
130
        /// <param name="cardId">The id of the Card to Delete</param>
131
        /// <param name="cancellationToken">Cancellation Token</param>
132
        public async Task DeleteCardAsync(string cardId, CancellationToken cancellationToken = default)
133
        {
134
            await _apiRequestController.Delete($"{UrlPaths.Cards}/{cardId}", cancellationToken, 0);
1✔
135
        }
1✔
136

137
        /// <summary>
138
        /// Get a Card by its Id
139
        /// </summary>
140
        /// <param name="cardId">Id of the Card</param>
141
        /// <param name="cancellationToken">Cancellation Token</param>
142
        /// <returns>The Card</returns>
143
        public async Task<Card> GetCardAsync(string cardId, CancellationToken cancellationToken = default)
144
        {
145
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken,
51✔
146
                new QueryParameter("customFieldItems", Options.IncludeCustomFieldsInCardGetMethods),
51✔
147
                new QueryParameter("attachments", Options.IncludeAttachmentsInCardGetMethods)
51✔
148
            );
51✔
149
        }
51✔
150

151
        /// <summary>
152
        /// Get a Card by its Id
153
        /// </summary>
154
        /// <param name="cardId">Id of the Card</param>
155
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param>
156
        /// <param name="cancellationToken">Cancellation Token</param>
157
        /// <returns>The Card</returns>
158
        public async Task<Card> GetCardAsync(string cardId, GetCardOptions options, CancellationToken cancellationToken = default)
159
        {
160
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken, options.GetParameters());
58✔
161
        }
58✔
162

163
        /// <summary>
164
        /// Get all open cards on un-archived lists on a board
165
        /// </summary>
166
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
167
        /// <param name="cancellationToken">CancellationToken</param>
168
        /// <returns>List of Cards</returns>
169
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, CancellationToken cancellationToken = default)
170
        {
171
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken,
6✔
172
                new QueryParameter("customFieldItems", Options.IncludeCustomFieldsInCardGetMethods),
6✔
173
                new QueryParameter("attachments", Options.IncludeAttachmentsInCardGetMethods)
6✔
174
            );
6✔
175
        }
6✔
176

177
        /// <summary>
178
        /// Get all open cards on un-archived lists on a board
179
        /// </summary>
180
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
181
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param>
182
        /// <param name="cancellationToken">CancellationToken</param>
183
        /// <returns>List of Cards</returns>
184
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, GetCardOptions options, CancellationToken cancellationToken = default)
185
        {
186
            if (options.IncludeList)
×
187
            {
188
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
×
189
                {
190
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
191
                    {
×
192
                        "idList"
×
193
                    }).ToArray();
×
194
                }
195
            }
196

NEW
197
            if (options.IncludeBoard)
×
198
            {
NEW
199
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
200
                {
NEW
201
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
NEW
202
                    {
×
NEW
203
                        "idBoard"
×
NEW
204
                    }).ToArray();
×
205
                }
206
            }
207

208
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken, options.GetParameters());
×
209
            if (options.IncludeList)
×
210
            {
211
                var lists = await GetListsOnBoardAsync(boardId, cancellationToken);
×
212
                foreach (Card card in cards)
×
213
                {
214
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
×
215
                }
216
            }
217

NEW
218
            if (options.IncludeBoard)
×
219
            {
NEW
220
                var board = await GetBoardAsync(boardId, cancellationToken);
×
NEW
221
                foreach (Card card in cards)
×
222
                {
NEW
223
                    card.Board = board;
×
224
                }
225
            }
226

227
            return cards;
×
228
        }
×
229

230
        /// <summary>
231
        /// Get all open cards on a specific list
232
        /// </summary>
233
        /// <param name="listId">Id of the List</param>
234
        /// <param name="cancellationToken">Cancellation Token</param>
235
        /// <returns>List of Cards</returns>
236
        public async Task<List<Card>> GetCardsInListAsync(string listId, CancellationToken cancellationToken = default)
237
        {
238
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken,
9✔
239
                new QueryParameter("customFieldItems", Options.IncludeCustomFieldsInCardGetMethods),
9✔
240
                new QueryParameter("attachments", Options.IncludeAttachmentsInCardGetMethods));
9✔
241
        }
9✔
242

243
        /// <summary>
244
        /// Get all open cards on a specific list
245
        /// </summary>
246
        /// <param name="listId">Id of the List</param>
247
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param>
248
        /// <param name="cancellationToken">Cancellation Token</param>
249
        /// <returns>List of Cards</returns>
250
        public async Task<List<Card>> GetCardsInListAsync(string listId, GetCardOptions options, CancellationToken cancellationToken = default)
251
        {
NEW
252
            if (options.IncludeBoard)
×
253
            {
NEW
254
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
255
                {
NEW
256
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
NEW
257
                    {
×
NEW
258
                        "idBoard"
×
NEW
259
                    }).ToArray();
×
260
                }
261
            }
262

263
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken, options.GetParameters());
×
264
            if (options.IncludeList)
×
265
            {
266
                var list = await GetListAsync(listId, cancellationToken);
×
267
                foreach (Card card in cards)
×
268
                {
269
                    card.ListId = listId;
×
270
                    card.List = list;
×
271
                }
272
            }
273

NEW
274
            if (options.IncludeBoard && cards.Count > 0)
×
275
            {
NEW
276
                var board = await GetBoardAsync(cards[0].BoardId, cancellationToken);
×
NEW
277
                foreach (Card card in cards)
×
278
                {
NEW
279
                    card.Board = board;
×
280
                }
281
            }
282

283

284
            return cards;
×
285
        }
×
286

287
        /// <summary>
288
        /// Get the cards on board based on their status regardless if they are on archived lists
289
        /// </summary>
290
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
291
        /// <param name="filter">The Selected Filter</param>
292
        /// <param name="cancellationToken">Cancellation Token</param>
293
        /// <returns>List of Cards</returns>
294
        public async Task<List<Card>> GetCardsOnBoardFilteredAsync(string boardId, CardsFilter filter, CancellationToken cancellationToken = default)
295
        {
296
            return await _apiRequestController.Get<List<Card>>($"{GetUrlBuilder.GetCardsOnBoard(boardId)}/{filter.GetJsonPropertyName()}", cancellationToken,
1✔
297
                new QueryParameter("customFieldItems", Options.IncludeCustomFieldsInCardGetMethods),
1✔
298
                new QueryParameter("attachments", Options.IncludeAttachmentsInCardGetMethods));
1✔
299
        }
1✔
300

301
        /// <summary>
302
        /// Get the cards on board based on their status regardless if they are on archived lists
303
        /// </summary>
304
        /// <param name="boardId">Id of the Board (in its long or short version)</param>
305
        /// <param name="filter">The Selected Filter</param>
306
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param> 
307
        /// <param name="cancellationToken">Cancellation Token</param>
308
        /// <returns>List of Cards</returns>
309
        public async Task<List<Card>> GetCardsOnBoardFilteredAsync(string boardId, CardsFilter filter, GetCardOptions options, CancellationToken cancellationToken = default)
310
        {
311
            if (options.IncludeList)
×
312
            {
313
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
×
314
                {
315
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
316
                    {
×
317
                        "idList"
×
318
                    }).ToArray();
×
319
                }
320
            }
321

322
            var cards = await _apiRequestController.Get<List<Card>>($"{GetUrlBuilder.GetCardsOnBoard(boardId)}/{filter.GetJsonPropertyName()}", cancellationToken, options.GetParameters());
×
323
            if (options.IncludeList)
×
324
            {
325
                var lists = await GetListsOnBoardAsync(boardId, cancellationToken);
×
326
                foreach (Card card in cards)
×
327
                {
328
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
×
329
                }
330
            }
331

332
            return cards;
×
333
        }
×
334

335
        /// <summary>
336
        /// Get all Cards a Member is on (across multiple boards)
337
        /// </summary>
338
        /// <param name="memberId">Id of Member</param>
339
        /// <param name="cancellationToken">Cancellation Token</param>
340
        /// <returns></returns>
341
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, CancellationToken cancellationToken = default)
342
        {
343
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken);
1✔
344
        }
1✔
345

346
        /// <summary>
347
        /// Get all Cards a Member is on (across multiple boards)
348
        /// </summary>
349
        /// <param name="memberId">Id of Member</param>
350
        /// <param name="options">Options on how and what should be included on the cards (Example only a few fields to increase performance or more nested data to avoid more API calls)</param>
351
        /// <param name="cancellationToken">Cancellation Token</param>
352
        /// <returns></returns>
353
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, GetCardOptions options, CancellationToken cancellationToken = default)
354
        {
355
            if (options.IncludeList)
×
356
            {
357
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
×
358
                {
359
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
360
                    {
×
361
                        "idList"
×
362
                    }).ToArray();
×
363
                }
364

365
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
366
                {
367
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
368
                    {
×
369
                        "idBoard"
×
370
                    }).ToArray();
×
371
                }
372
            }
373

NEW
374
            if (options.IncludeBoard)
×
375
            {
NEW
376
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
×
377
                {
NEW
378
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
NEW
379
                    {
×
NEW
380
                        "idBoard"
×
NEW
381
                    }).ToArray();
×
382
                }
383
            }
384

385
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken, options.GetParameters());
×
386
            if (options.IncludeList)
×
387
            {
388
                var boardsToGetListsFor = cards.Select(x => x.BoardId).Distinct().ToArray();
×
389
                List<List> lists = new List<List>();
×
390
                foreach (var boardId in boardsToGetListsFor)
×
391
                {
392
                    lists.AddRange(await GetListsOnBoardAsync(boardId, cancellationToken));
×
393
                }
394

395
                foreach (Card card in cards)
×
396
                {
397
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
×
398
                }
399
            }
×
400

NEW
401
            if (options.IncludeBoard)
×
402
            {
NEW
403
                var boardIds = cards.Select(x => x.BoardId).Distinct().ToList();
×
NEW
404
                var boards = await GetBoardsAsync(boardIds, cancellationToken);
×
NEW
405
                foreach (Card card in cards)
×
406
                {
NEW
407
                    card.Board = boards.FirstOrDefault(x => x.Id == card.BoardId);
×
408
                }
409
            }
410

411
            return cards;
×
412
        }
×
413

414
        /// <summary>
415
        /// Set Due Date on a card
416
        /// </summary>
417
        /// <param name="cardId">Id of the Card</param>
418
        /// <param name="dueDate">The Due Date (In UTC Time)</param>
419
        /// <param name="dueComplete">If Due is complete</param>
420
        /// <param name="cancellationToken">Cancellation Token</param>
421
        public async Task<Card> SetDueDateOnCardAsync(string cardId, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
422
        {
423
            return await UpdateCardAsync(cardId, new List<QueryParameter>
1✔
424
            {
1✔
425
                new QueryParameter(CardFieldsType.Due.GetJsonPropertyName(), dueDate),
1✔
426
                new QueryParameter(CardFieldsType.DueComplete.GetJsonPropertyName(), dueComplete)
1✔
427
            }, cancellationToken);
1✔
428
        }
1✔
429

430
        /// <summary>
431
        /// Set Due Date on a card
432
        /// </summary>
433
        /// <param name="cardId">Id of the Card</param>
434
        /// <param name="startDate">The Start Date (In UTC Time)</param>
435
        /// <param name="cancellationToken">Cancellation Token</param>
436
        public async Task<Card> SetStartDateOnCardAsync(string cardId, DateTimeOffset startDate, CancellationToken cancellationToken = default)
437
        {
438
            return await UpdateCardAsync(cardId, new List<QueryParameter>
1✔
439
            {
1✔
440
                new QueryParameter(CardFieldsType.Start.GetJsonPropertyName(), startDate)
1✔
441
            }, cancellationToken);
1✔
442
        }
1✔
443

444
        /// <summary>
445
        /// Set Start and Due Date on a card
446
        /// </summary>
447
        /// <param name="cardId">Id of the Card</param>
448
        /// <param name="startDate">The Start Date (In UTC Time)</param>
449
        /// <param name="dueDate">The Due Date (In UTC Time)</param>
450
        /// <param name="dueComplete">If Due is complete</param>
451
        /// <param name="cancellationToken">Cancellation Token</param> 
452
        public async Task<Card> SetStartDateAndDueDateOnCardAsync(string cardId, DateTimeOffset startDate, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
453
        {
454
            return await UpdateCardAsync(cardId, new List<QueryParameter>
1✔
455
            {
1✔
456
                new QueryParameter(CardFieldsType.Start.GetJsonPropertyName(), startDate),
1✔
457
                new QueryParameter(CardFieldsType.Due.GetJsonPropertyName(), dueDate),
1✔
458
                new QueryParameter(CardFieldsType.DueComplete.GetJsonPropertyName(), dueComplete),
1✔
459
            }, cancellationToken);
1✔
460
        }
1✔
461

462
        /// <summary>
463
        /// Move a Card to a new list on the same board
464
        /// </summary>
465
        /// <param name="cardId">Id of the Card</param>
466
        /// <param name="newListId">Id of the List you wish to move it to</param>
467
        /// <param name="cancellationToken">Cancellation Token</param>
468
        /// <returns></returns>
469
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, CancellationToken cancellationToken = default)
470
        {
471
            return await UpdateCardAsync(cardId, new List<QueryParameter>
1✔
472
            {
1✔
473
                new QueryParameter(CardFieldsType.ListId.GetJsonPropertyName(), newListId)
1✔
474
            }, cancellationToken);
1✔
475
        }
1✔
476

477
        /// <summary>
478
        /// Move a Card to a new list on the same board
479
        /// </summary>
480
        /// <param name="cardId">Id of the Card</param>
481
        /// <param name="newListId">Id of the List you wish to move it to</param>
482
        /// <param name="options">Additional optional Options for the Move</param>
483
        /// <param name="cancellationToken">Cancellation Token</param>
484
        /// <returns></returns>
485
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, MoveCardToListOptions options, CancellationToken cancellationToken = default)
486
        {
487
            var parameters = new List<QueryParameter> { new QueryParameter(CardFieldsType.ListId.GetJsonPropertyName(), newListId) };
×
488
            if (options.NamedPositionOnNewList.HasValue)
×
489
            {
490
                switch (options.NamedPositionOnNewList.Value)
×
491
                {
492
                    case NamedPosition.Top:
493
                        parameters.Add(new QueryParameter("pos", "top"));
×
494
                        break;
×
495
                    case NamedPosition.Bottom:
496
                        parameters.Add(new QueryParameter("pos", "bottom"));
×
497
                        break;
×
498
                }
499
            }
500
            else if (options.PositionOnNewList.HasValue)
×
501
            {
502
                parameters.Add(new QueryParameter(CardFieldsType.Position.GetJsonPropertyName(), options.PositionOnNewList.Value));
×
503
            }
504

505
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
×
506
        }
×
507

508
        /// <summary>
509
        /// Update one or more specific fields on a card (compared to a full update of all fields with UpdateCard)
510
        /// </summary>
511
        /// <param name="cardId">Id of the Card</param>
512
        /// <param name="parameters">The Specific Parameters to set</param>
513
        /// <param name="cancellationToken">CancellationToken</param>
514
        public async Task<Card> UpdateCardAsync(string cardId, List<QueryParameter> parameters, CancellationToken cancellationToken = default)
515
        {
516
            QueryParameter coverParameter = parameters.FirstOrDefault(x => x.Name == "cover");
78✔
517
            if (coverParameter != null && !string.IsNullOrWhiteSpace(coverParameter.GetRawStringValue()))
33✔
518
            {
519
                parameters.Remove(coverParameter);
1✔
520
                CardCover cover = JsonSerializer.Deserialize<CardCover>(coverParameter.GetRawStringValue());
1✔
521
                var payload = GeneratePayloadForCoverUpdate(cover, parameters);
1✔
522
                return await _apiRequestController.PutWithJsonPayload<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, payload, parameters.ToArray());
1✔
523
            }
524

525
            //Special Cover Card
526
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, parameters.ToArray());
32✔
527
        }
33✔
528

529
        /// <summary>
530
        /// Move the Card to the top of its current list
531
        /// </summary>
532
        /// <param name="cardId">Id of the Card</param>
533
        /// <param name="cancellationToken">Cancellation Token</param>
534
        /// <returns>The Card</returns>
535
        public async Task<Card> MoveCardToTopOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
536
        {
537
            return await UpdateCardAsync(cardId, new List<QueryParameter>
×
538
            {
×
539
                new QueryParameter(CardFieldsType.Position.GetJsonPropertyName(), "top")
×
540
            }, cancellationToken);
×
541
        }
×
542

543
        /// <summary>
544
        /// Move the Card to the bottom of its current list
545
        /// </summary>
546
        /// <param name="cardId">Id of the Card</param>
547
        /// <param name="cancellationToken">Cancellation Token</param>
548
        /// <returns>The Card</returns>
549
        public async Task<Card> MoveCardToBottomOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
550
        {
551
            return await UpdateCardAsync(cardId, new List<QueryParameter>
×
552
            {
×
553
                new QueryParameter(CardFieldsType.Position.GetJsonPropertyName(), "bottom")
×
554
            }, cancellationToken);
×
555
        }
×
556

557
        /// <summary>
558
        /// Move a Card to another board
559
        /// </summary>
560
        /// <param name="cardId">The Id of the Card to Move</param>
561
        /// <param name="newBoardId">The ID of the New Board that the card should be moved to</param>
562
        /// <param name="options">Additional Options for the move like what list the card should end up on the new board and what happens to labels and members</param>
563
        /// <param name="cancellationToken">Cancellation Token</param>
564
        /// <returns></returns>
565
        public async Task<Card> MoveCardToBoard(string cardId, string newBoardId, MoveCardToBoardOptions options, CancellationToken cancellationToken = default)
566
        {
567
            if (options == null)
×
568
            {
569
                throw new ArgumentNullException(nameof(options), "You need to pass an options object to confirm the various options that are invovled with moving a card between boards");
×
570
            }
571

572
            List<QueryParameter> parameters = new List<QueryParameter> { new QueryParameter(CardFieldsType.BoardId.GetJsonPropertyName(), newBoardId) };
×
573
            var newListId = options.NewListId;
×
574
            if (string.IsNullOrWhiteSpace(newListId))
×
575
            {
576
                //No list specified, so we need to find the first list on the board
577
                newListId = (await GetListsOnBoardAsync(newBoardId, cancellationToken)).OrderBy(x => x.Position).FirstOrDefault()?.Id;
×
578
            }
579

580
            parameters.Add(new QueryParameter(CardFieldsType.ListId.GetJsonPropertyName(), newListId));
×
581

582
            if (options.NamedPositionOnNewList.HasValue)
×
583
            {
584
                switch (options.NamedPositionOnNewList.Value)
×
585
                {
586
                    case NamedPosition.Top:
587
                        parameters.Add(new QueryParameter("pos", "top"));
×
588
                        break;
×
589
                    case NamedPosition.Bottom:
590
                        parameters.Add(new QueryParameter("pos", "bottom"));
×
591
                        break;
×
592
                }
593
            }
594
            else if (options.PositionOnNewList.HasValue)
×
595
            {
596
                parameters.Add(new QueryParameter(CardFieldsType.Position.GetJsonPropertyName(), options.PositionOnNewList.Value));
×
597
            }
598

599
            Card card = await GetCardAsync(cardId, new GetCardOptions
×
600
            {
×
601
                CardFields = new CardFields(CardFieldsType.MemberIds, CardFieldsType.LabelIds, CardFieldsType.Labels)
×
602
            }, cancellationToken);
×
603

604
            switch (options.MemberOptions)
×
605
            {
606
                case MoveCardToBoardOptionsMemberOptions.KeepMembersAlsoOnNewBoardAndRemoveRest:
607
                    var existingMemberIdsOnNewBoard = (await GetMembersOfBoardAsync(newBoardId, cancellationToken)).Select(x => x.Id);
×
608
                    card.MemberIds = card.MemberIds.Intersect(existingMemberIdsOnNewBoard).ToList();
×
609
                    break;
×
610
                case MoveCardToBoardOptionsMemberOptions.RemoveAllMembersOnCard:
611
                    card.MemberIds.Clear();
×
612
                    break;
×
613
                default:
614
                    throw new ArgumentOutOfRangeException();
×
615
            }
616

617
            if (card.LabelIds.Any())
×
618
            {
619
                card.LabelIds.Clear();
×
620
                switch (options.LabelOptions)
×
621
                {
622
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndCreateMissing:
623
                    {
624
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
625
                        foreach (Label cardLabel in card.Labels)
×
626
                        {
627
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
×
628
                            if (existingLabel != null)
×
629
                            {
630
                                card.LabelIds.Add(existingLabel.Id);
×
631
                            }
632
                            else
633
                            {
634
                                //Label need to be added
635
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
×
636
                                card.LabelIds.Add(newLabel.Id);
×
637
                            }
638
                        }
639

640
                        break;
×
641
                    }
642
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndRemoveMissing:
643
                    {
644
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
645
                        foreach (Label cardLabel in card.Labels)
×
646
                        {
647
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
×
648
                            if (existingLabel != null)
×
649
                            {
650
                                card.LabelIds.Add(existingLabel.Id);
×
651
                            }
652
                        }
653

654
                        break;
655
                    }
656
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndCreateMissing:
657
                    {
658
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
659
                        foreach (Label cardLabel in card.Labels)
×
660
                        {
661
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
×
662
                            if (existingLabel != null)
×
663
                            {
664
                                card.LabelIds.Add(existingLabel.Id);
×
665
                            }
666
                            else
667
                            {
668
                                //Label need to be added
669
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
×
670
                                card.LabelIds.Add(newLabel.Id);
×
671
                            }
672
                        }
673

674
                        break;
×
675
                    }
676
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndRemoveMissing:
677
                    {
678
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
679
                        foreach (Label cardLabel in card.Labels)
×
680
                        {
681
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
×
682
                            if (existingLabel != null)
×
683
                            {
684
                                card.LabelIds.Add(existingLabel.Id);
×
685
                            }
686
                        }
687

688
                        break;
689
                    }
690
                    case MoveCardToBoardOptionsLabelOptions.RemoveAllLabelsOnCard:
691
                        //No more Work needed
692
                        break;
693
                    default:
694
                        throw new ArgumentOutOfRangeException();
×
695
                }
696
            }
697

698
            parameters.Add(new QueryParameter(CardFieldsType.LabelIds.GetJsonPropertyName(), card.LabelIds));
×
699
            parameters.Add(new QueryParameter(CardFieldsType.MemberIds.GetJsonPropertyName(), card.MemberIds));
×
700

701
            if (options.RemoveDueDate)
×
702
            {
703
                parameters.Add(new QueryParameter(CardFieldsType.Due.GetJsonPropertyName(), (DateTimeOffset?)null));
×
704
            }
705

706
            if (options.RemoveStartDate)
×
707
            {
708
                parameters.Add(new QueryParameter(CardFieldsType.Start.GetJsonPropertyName(), (DateTimeOffset?)null));
×
709
            }
710

711
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
×
712
        }
×
713
    }
714
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc