La API de GitHub Enterprise proporciona una gran cantidad de información para el consumo de los desarrolladores. La mayoría de las veces incluso podrías encontrar que estás pidiendo demasiada información y, para mantener felices a nuestros servidores, la API paginará los elementos solicitados automáticamente.
En esta guía haremos algunos llamados a la API de Búsqueda de GitHub Enterprise e iteraremos sobre los resultados utilizando la paginación. Puedes encontrar todo el código fuente de este proyecto en el repositorio platform-samples.
Fundamentos de la Paginación
Para empezar, es importante saber algunos hechos acerca de recibir elementos paginados:
- Las diferentes llamadas a la API responden con predeterminados diferentes también. Por ejemplo, una llamada a Listar repositorios públicos proporciona elementos paginados en conjuntos de 30, mientras que una llamada a la API de Búsqueda de GitHub proporciona elementos en conjuntos de 100
- Puedes especificar cuantos elementos quieres recibir (hasta llegar a 100 como máxmo); pero,
- Por razones técnicas, no todas las terminales se comportan igual. Por ejemplo, los eventos no te dejarán usar un máximo de elementos a recibir. Asegúrate de leer la documentación sobre cómo gestionar los resultados paginados para terminales específicas.
Te proporcionamos la información sobre la paginación en el encabezado de enlace de una llamada a la API. Por ejemplo, vamos a hacer una solicitud de curl a la API de búsqueda para saber cuántas veces se utiliza la frase addClass
en los proyectos de Mozilla:
$ curl -I "http(s)://[hostname]/api/v3/search/code?q=addClass+user:mozilla"
El parámetro -I
indica que solo nos interesan los encabezados y no el contenido en sí. Al examinar el resultado, notarás alguna información en el encabezado de enlace, la cual se ve así:
Link: <http(s)://[hostname]/api/v3/search/code?q=addClass+user%3Amozilla&page=2>; rel="next",
<http(s)://[hostname]/api/v3/search/code?q=addClass+user%3Amozilla&page=34>; rel="last"
Vamos a explicarlo. rel="next"
dice que la siguiente página es la page=2
. Esto tiene sentido ya que, predeterminadamente, todas las consultas paginadas inician en la página 1.
y rel="last"
proporciona más información, lo cual nos dice que la última página de los resultados es la 34
. Por lo tanto, tenemos otras 33 páginas de información que podemos consumir acerca de addClass
. ¡Excelente!
Confía siempre en estas relaciones de enlace que se te proporcionan. No intentes adivinar o construir tu propia URL.
Navegar a través de las páginas
Ahora que sabescuántas páginas hay para recibir, puedes comenzar a navegar a través de ellas para consumir los resultados. Esto se hace pasando un parámetro de page
. Predeterminadamente, la page
siempre comienza en 1
. Vamos a saltar a la página 14 para ver qué pasa:
$ curl -I "http(s)://[hostname]/api/v3/search/code?q=addClass+user:mozilla&page=14"
Aquí está el encabezado de enlace una vez más:
Link: <http(s)://[hostname]/api/v3/search/code?q=addClass+user%3Amozilla&page=15>; rel="next",
<http(s)://[hostname]/api/v3/search/code?q=addClass+user%3Amozilla&page=34>; rel="last",
<http(s)://[hostname]/api/v3/search/code?q=addClass+user%3Amozilla&page=1>; rel="first",
<http(s)://[hostname]/api/v3/search/code?q=addClass+user%3Amozilla&page=13>; rel="prev"
Como era de esperarse, la rel="next"
está en 15, y la rel="last"
es aún 34. Pero ahora tenemos más información: rel="first"
indica la URL de la primera página, y lo que es más importante, rel="prev"
te dice el número de página de la página anterior. Al utilizar esta información, puedes construir alguna IU que le permita a los usuarios saltar entre la lista de resultados principal, previa o siguiente en una llamada a la API.
Cambiar la cantidad de elementos recibidos
Al pasar el parámetro per_page
, puedes especificar cuantos elementos quieres que devuelva cada página, hasta un máximo de 100 de ellos. Vamos a comenzar pidiendo 50 elementos acerca de addClass
:
$ curl -I "http(s)://[hostname]/api/v3/search/code?q=addClass+user:mozilla&per_page=50"
Nota lo que hace en la respuesta del encabezado:
Link: <http(s)://[hostname]/api/v3/search/code?q=addClass+user%3Amozilla&per_page=50&page=2>; rel="next",
<http(s)://[hostname]/api/v3/search/code?q=addClass+user%3Amozilla&per_page=50&page=20>; rel="last"
Como habrás adivinado, la información de la rel="last"
dice que la última página ahora es la 20. Esto es porque estamos pidiendo más información por página acerca de nuestros resultados.
Consumir la información
No debes estar haciendo llamadas de curl de bajo nivel para poder trabajar con la paginación, así que escribamos un script de Ruby sencillo que haga todo lo que acabamos de describir anteriormente.
Como siempre, primero solicitaremos la biblioteca de Ruby Octokit.rb de GitHub y pasaremos nuestro token de acceso personal:
require 'octokit'
# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and test environment variables, like below
client = Octokit::Client.new :access_token => ENV['MY_PERSONAL_TOKEN']
Después, ejecutaremos la búsqueda utilizando el método search_code
de Octokit. A diferencia de cuando se utiliza curl
, también podemos recuperar de inmediato la cantidad de resultados, así que hagámoslo:
results = client.search_code('addClass user:mozilla')
total_count = results.total_count
Ahora tomemos el número de la última página de forma similar a la información de page=34>; rel="last"
en el encabezado de enlace. Octokit.rb es compatible con información de paginación a través de una implementación llamada "Relaciones de enlace de hipermedios." No entraremos en detalles sobre lo que es, pero basta con decir que cada elemento en la variable de results
tiene un hash que se llama rels
, el cual contiene información sobre :next
, :last
, :first
, y :prev
, dependiendo del resultado en el que estés. Estas relaciones también contienen información sobre la URL resultante llamando a rels[:last].href
.
Ahora que sabemos esto, vamos a tomar el número de página del último resultado y a presentar toda esta información al usuario:
last_response = client.last_response
number_of_pages = last_response.rels[:last].href.match(/page=(\d+).*$/)[1]
puts "There are #{total_count} results, on #{number_of_pages} pages!"
Por último, vamos a iterar entre los resultados. Puedes hacerlo con un bucle como for i in 1..number_of_pages.to_i
, pero mejor vamos a seguir los encabezados de rels[:next]
para recuperar la información de cada página. Para mantener la simplicidad, solo vamos a tomar la ruta del archivo del primer resultado de cada página. Para hacerlo, vamos a necesitar un bucle; y al final de cada bucle, vamos a recuperar los datos que se configuraron para la siguiente página siguiendo la información de rels[:next]
. El bucle terminará cuando ya no haya información de rels[:next]
que consumir (es decir, cuando estemos en rels[:last]
). Se verá más o menos así:
puts last_response.data.items.first.path
until last_response.rels[:next].nil?
last_response = last_response.rels[:next].get
puts last_response.data.items.first.path
end
Cambiar la cantidad de elementos por página es extremadamente simple con Octokit.rb. Simplemente pasa un hash de opciones de per_page
a la construcción del cliente inicial. Después de ésto, tu código debería permanecer intacto:
require 'octokit'
# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and test environment variables, like below
client = Octokit::Client.new :access_token => ENV['MY_PERSONAL_TOKEN']
results = client.search_code('addClass user:mozilla', :per_page => 100)
total_count = results.total_count
last_response = client.last_response
number_of_pages = last_response.rels[:last].href.match(/page=(\d+).*$/)[1]
puts last_response.rels[:last].href
puts "There are #{total_count} results, on #{number_of_pages} pages!"
puts "And here's the first path for every set"
puts last_response.data.items.first.path
until last_response.rels[:next].nil?
last_response = last_response.rels[:next].get
puts last_response.data.items.first.path
end
Construir enlaces de paginación
Habitualmente, con la paginación, tu meta no es concentrar todos los resultados posibles, sino más bien producir un conjunto de navegación, como éste:
Vamos a modelar una micro versión de lo que esto podría implicar.
Desde el código anterior, ya sabemos que podemos obtener el number_of_pages
en los resultados paginados desde la primera llamada:
require 'octokit'
# !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!
# Instead, set and test environment variables, like below
client = Octokit::Client.new :access_token => ENV['MY_PERSONAL_TOKEN']
results = client.search_code('addClass user:mozilla')
total_count = results.total_count
last_response = client.last_response
number_of_pages = last_response.rels[:last].href.match(/page=(\d+).*$/)[1]
puts last_response.rels[:last].href
puts "There are #{total_count} results, on #{number_of_pages} pages!"
Desde aquí, podemos construir una hermosa representación en ASCII de las cajas de número:
numbers = ""
for i in 1..number_of_pages.to_i
numbers << "[#{i}] "
end
puts numbers
Vamos a simular que un usuario da clic en alguna de estas cajas mediante la construcción de un número aleatorio:
random_page = Random.new
random_page = random_page.rand(1..number_of_pages.to_i)
puts "A User appeared, and clicked number #{random_page}!"
Ahora que tenemos un número de página, podemos usar el Octokit para recuperar explícitamente dicha página individual si pasamos la opción :page
:
clicked_results = client.search_code('addClass user:mozilla', :page => random_page)
Si quisiéramos ponernos elegantes, podríamos también tomar la página anterior y posterior para generar los enlaces de los elementos anterior (<<
) y posterior (>>
):
prev_page_href = client.last_response.rels[:prev] ? client.last_response.rels[:prev].href : "(none)"
next_page_href = client.last_response.rels[:next] ? client.last_response.rels[:next].href : "(none)"
puts "The prev page link is #{prev_page_href}"
puts "The next page link is #{next_page_href}"