Skip to content

Instantly share code, notes, and snippets.

@hakunin
Last active January 3, 2022 00:51
Show Gist options
  • Save hakunin/3c25a4c4ae30a2c2c299 to your computer and use it in GitHub Desktop.
Save hakunin/3c25a4c4ae30a2c2c299 to your computer and use it in GitHub Desktop.

So since the API really desn't work as you'd expect it to, I am documenting this here at least.

I created a testing board with cards A, B, C, ... , M so I can test on it.

First, grabbing all cards works as expected.

api.find(:boards, '56af532ae7a74a5fcac32e66').cards.map { |c| [c.name, c.id] }

=> [
 ["A", "56af53367f15b163c0e345fd"],
 ["B", "56af533783d4fccc1cce3941"],
 ["C", "56af533831727db409451c9e"],
 ["D", "56af53397bf672bb56c7c536"],
 ["E", "56af5339f5e67cf6bcc7323b"],
 ["F", "56af533ac0aa02de54ab881a"],
 ["G", "56af533b212b095266ebb506"],
 ["H", "56af533e11992f20f375fca1"],
 ["I", "56af533fc1775129aaa7028b"],
 ["J", "56af5345f81227a7a1cc3a35"],
 ["K", "56af5345e36b3d3c45b16967"],
 ["L", "56af534d964b6c4aa93d4aca"],
 ["M", "56af534e768610ff67c781ce"]
]

Note: the order of the cards isn't by create time, its by their appearance in the board from left column to right column and from top to bottom.

Limit

Okay, time to start paginating. Let's get only the first card, shall we?

board.cards(limit: 1).map { |c| [c.name, c.id] }

=> [["M", "56af534e768610ff67c781ce"]]

Ooops, API returns the last element. Definitely unexpected. Lets play with the after/before next.

Before

Let's grab card before the last one.

board.cards(limit: 1, before: '56af534e768610ff67c781ce').map { |c| [c.name, c.id] }

=> [["L", "56af534d964b6c4aa93d4aca"]]

That works well, L is before M.

After

What happens if we grab a card after the last one, response should be empty, right?

board.cards(limit: 1, after: '56af534e768610ff67c781ce').map { |c| [c.name, c.id] }

=> [["M", "56af534e768610ff67c781ce"]]

Mmmm, what? What happens if I grab two card then?

board.cards(limit: 2, after: '56af534e768610ff67c781ce').map { |c| [c.name, c.id] }

=> [
  ["L", "56af534d964b6c4aa93d4aca"], 
  ["M", "56af534e768610ff67c781ce"]
]

This defies any logic. Definitely can't go through the list from start to finish then, because there is no end to the card list.

Back to pagination

We'll have to go through the cards in reverse order in that case. Just specifying limit works, since by default we're going in reverse order. (which makes total sense, right?)

api.find(:boards, '56af532ae7a74a5fcac32e66').cards(limit: 5).map { |c| [c.name, c.id] }

=> [
 ["I", "56af533fc1775129aaa7028b"], 
 ["J", "56af5345f81227a7a1cc3a35"], 
 ["K", "56af5345e36b3d3c45b16967"], 
 ["L", "56af534d964b6c4aa93d4aca"], 
 ["M", "56af534e768610ff67c781ce"]
]

Don't let the order of these items fool you! Its still the order how they appear in the board, they're not sorted by the time of creation. If I move M card in its column so that it is right over L, the API will return them in reverse order.

The question is now, how to grab the older card from the list? As the other answer says, the IDs are timestamp, so we just sort by the ID and pick the lowest one and continue paginating.

Note: If you're fast, you can create multiple cards during the same second, so the IDs may not in the right order as they just use the number of seconds, not microseconds. That doesn't prevent us from paginating though.

board.cards(limit: 5, before: '56af533fc1775129aaa7028b').map { |c| [c.name, c.id] }

=> [
  ["D", "56af53397bf672bb56c7c536"], 
  ["E", "56af5339f5e67cf6bcc7323b"], 
  ["F", "56af533ac0aa02de54ab881a"], 
  ["G", "56af533b212b095266ebb506"], 
  ["H", "56af533e11992f20f375fca1"]
]

Awesome, we're getting the next page, let's get the last one.

board.cards(limit: 5, before: '56af53397bf672bb56c7c536').map { |c| [c.name, c.id] }

=> [
  ["A", "56af53367f15b163c0e345fd"],
  ["B", "56af533783d4fccc1cce3941"],
  ["C", "56af533831727db409451c9e"]
]

Yesss, so the last page contains less cards, than the limit, so we know its the last page. Horray!

@priyadarshy
Copy link

This is really helpful. I was struggling with proper pagination and the API is poorly documented for this case.

I think there's one mistake in your algorithm that didn't show up because your card's visual order probably corresponds to your creation order. I also created a board called A, B, C, ... and this was working but when I reversed the order with the existing cards I saw it was all out of whack.

Here's how I did it.

  1. Get cards by board with a limit specified.
  2. Make sure to sort by -id
  3. If the request returns limit number of results, call our function recursively and pass the id of the last card of the response (since it's supported).
  4. Concat results
  5. return when the number of results does not equal limit (e.g. last page).
  6. Sort all of them by card.pos
  7. Later (not seen here) break it down into an list of lists (by grouping by card.idList), then you should have them in order assuming you have the list ids in order.

    getCardsByBoard({ token, boardId, before}) {
        const limit = 10;
        const qs = { token, members: 'true', limit, sort:'-id' };
        if (before) {
            qs.before = before;
        }
        return _rp({ url: `${trelloApiUrl}/boards/${boardId}/cards`, qs})
            .then(res => {
                if (res.length === limit) {
                    return this.getCardsByBoard({ token, boardId, before: res[res.length -1].id })
                        .then(nextPage => [...nextPage, ...res]);
                } else {
                    return res;
                }
            })
            .then(cards => _.sortBy(cards, $ => $.pos))
    }, 

@favila
Copy link

favila commented Mar 25, 2020

If a page of cards doesn't display a card newer than the oldest card on that page, won't that newer card never be retrieved? The fact that cards are never in chronological order seems fatal to this approach.

Example:
Suppose this is the timestamp of all your cards in board order:
6 7 8 9 1 2 3 4 5

Now fetch cards with limit=3:
6 7 8

Now fetch cards with before=6 and limit=3:
1 2 3

Now fetch cards with before=1 and limit 3:

What happened to 9, 4 and 5?

@hakunin
Copy link
Author

hakunin commented Mar 26, 2020

You can study my Stack overflow question for further info:
https://stackoverflow.com/questions/34612933/how-to-paginate-trello-api-response

I don't have this mess of an API in my head anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment