TripPin Parte 5 – Paging

Este tutorial de várias partes aborda a criação de uma nova extensão de fonte de dados para Power Query. O tutorial deve ser feito sequencialmente, cada lição se baseia no conector criado nas lições anteriores, adicionando incrementalmente novos recursos — ao conector.

Nesta lição, você vai:

  • Adicionar suporte de paging ao conector

Muitas APIs Rest retornarão dados em "páginas", exigindo que os clientes façam várias solicitações para unir os resultados. Embora haja algumas convenções comuns para paginação (como RFC 5988),geralmente varia de API para API. Felizmente, o TripPin é um serviço OData e o padrão OData define uma maneira de fazer paginação usando valores odata.nextLink retornados no corpo da resposta.

Para simplificar as ierções anteriores do conector, a TripPin.Feed função não estava ciente de página. Ele simplesmente analisado qualquer JSON retornado da solicitação e formatado como uma tabela. Aqueles familiarizados com o protocolo OData podem ter notado que várias suposições incorretas foram feitas no formato da resposta (por exemplo, supondo que haja um campo que contém uma matriz value de registros).

Nesta lição, você melhorará sua lógica de tratamento de resposta tornando-a ciente da página. Tutoriais futuros tornarão a lógica de manipulação de página mais robusta e capaz de lidar com vários formatos de resposta (incluindo erros do serviço).

Observação

Você não precisa implementar sua própria lógica de paging com conectores baseados em OData.Feed,pois ele lida com tudo isso automaticamente.

Lista de verificação de pajamento

Ao implementar o suporte à paging, você precisará saber o seguinte sobre sua API:

  • Como solicitar a próxima página de dados?
  • O mecanismo de paging envolve o cálculo de valores ou você extrai a URL para a próxima página da resposta?
  • Como você sabe quando parar a pa paging?
  • Há parâmetros relacionados à paging que você deve estar ciente? (como "tamanho da página")

A resposta a essas perguntas afetará a maneira como você implementa sua lógica de paging. Embora haja alguma quantidade de reutilização de código em implementações de paginação (como o uso de Table.GenerateByPage),a maioria dos conectores acabará exigindo lógica personalizada.

Observação

Esta lição contém a lógica de paging para um serviço OData, que segue um formato específico. Verifique a documentação da API para determinar as alterações que você precisará fazer em seu conector para dar suporte ao formato de paging.

Visão geral da paging do OData

A paging do OData é controlada por anotações nextLink contidas no conteúdo da resposta. O valor nextLink contém a URL para a próxima página de dados. Você saberá se há outra página de dados procurando um odata.nextLink campo no objeto mais externo na resposta. Se não houver nenhum odata.nextLink campo, você leu todos os seus dados.

{
  "odata.context": "...",
  "odata.count": 37,
  "value": [
    { },
    { },
    { }
  ],
  "odata.nextLink": "...?$skiptoken=342r89"
}

Alguns serviços OData permitem que os clientes fornecem uma preferência de tamanho máximo de página,mas é com o serviço que a deve ou não. Power Query ser capaz de lidar com respostas de qualquer tamanho, portanto, você não precisa se preocupar com a especificação de uma preferência de tamanho de página, você pode dar suporte a qualquer que o serviço lançar para — você.

Mais informações sobre Paging controlada por servidor podem ser encontradas na especificação OData.

Testando TripPin

Antes de corrigir sua implementação de paging, confirme o comportamento atual da extensão do tutorial anterior. A consulta de teste a seguir recuperará a tabela People e adicionará uma coluna de índice para mostrar a contagem de linhas atual.

let
    source = TripPin.Contents(),
    data = source{[Name="People"]}[Data],
    withRowCount = Table.AddIndexColumn(data, "Index")
in
    withRowCount

A ligue o Fiddler e execute a consulta Visual Studio. Você observará que a consulta retorna uma tabela com 8 linhas (índice de 0 a 7).

QueryWithoutPaging.

Se você olhar para o corpo da resposta do Fiddler, verá que, na verdade, ela contém um campo, indicando que há mais páginas de dados @odata.nextLink disponíveis.

{
  "@odata.context": "https://services.odata.org/V4/TripPinService/$metadata#People",
  "@odata.nextLink": "https://services.odata.org/v4/TripPinService/People?%24skiptoken=8",
  "value": [
    { },
    { },
    { }
  ]
}

Implementando paging para TripPin

Agora você fará as seguintes alterações em sua extensão:

  1. Importar a função Table.GenerateByPage comum
  2. Adicionar uma GetAllPagesByNextLink função que usa para unir todas as Table.GenerateByPage páginas
  3. Adicionar uma GetPage função que pode ler uma única página de dados
  4. Adicionar uma GetNextLink função para extrair a próxima URL da resposta
  5. Atualizar TripPin.Feed para usar as novas funções de leitor de página

Observação

Conforme indicado anteriormente neste tutorial, a lógica de paging variará entre fontes de dados. A implementação aqui tenta separar a lógica em funções que devem ser reutilizáveis para fontes que usam os próximos links retornados na resposta.

Table.GenerateByPage

A Table.GenerateByPage função pode ser usada para combinar com eficiência várias 'páginas' de dados em uma única tabela. Ele faz isso chamando repetidamente a função passada como o getNextPage parâmetro , até receber um null . O parâmetro de função deve pegar um único argumento e retornar um nullable table .

getNextPage = (lastPage) as nullable table => ...

Cada chamada para getNextPage recebe a saída da chamada anterior.

// The getNextPage function takes a single argument and is expected to return a nullable table
Table.GenerateByPage = (getNextPage as function) as table =>
    let        
        listOfPages = List.Generate(
            () => getNextPage(null),            // get the first page of data
            (lastPage) => lastPage <> null,     // stop when the function returns null
            (lastPage) => getNextPage(lastPage) // pass the previous page to the next function call
        ),
        // concatenate the pages together
        tableOfPages = Table.FromList(listOfPages, Splitter.SplitByNothing(), {"Column1"}),
        firstRow = tableOfPages{0}?
    in
        // if we didn't get back any pages of data, return an empty table
        // otherwise set the table type based on the columns of the first page
        if (firstRow = null) then
            Table.FromRows({})
        else        
            Value.ReplaceType(
                Table.ExpandTableColumn(tableOfPages, "Column1", Table.ColumnNames(firstRow[Column1])),
                Value.Type(firstRow[Column1])
            );

Algumas observações sobre Table.GenerateByPage :

  • A função precisará recuperar a URL da próxima página (ou o número da página ou quaisquer outros valores usados getNextPage para implementar a lógica de paging). Isso geralmente é feito adicionando meta valores à página antes de reavolvê-la.
  • As colunas e o tipo de tabela da tabela combinada (ou seja, todas as páginas juntas) são derivados da primeira página de dados. A getNextPage função deve normalizar cada página de dados.
  • A primeira chamada para getNextPage recebe um parâmetro nulo.
  • getNextPage deve retornar nulo quando não houver mais páginas.

O corpo da função GetAllPagesByNextLink implementa o argumento de função para getNextPage Table.GenerateByPage . Ele chamará a função e recuperará a URL para a próxima página de dados do campo do registro GetPage NextLink da chamada meta anterior.

// Read all pages of data.
// After every page, we check the "NextLink" record on the metadata of the previous request.
// Table.GenerateByPage will keep asking for more pages until we return null.
GetAllPagesByNextLink = (url as text) as table =>
    Table.GenerateByPage((previous) => 
        let
            // if previous is null, then this is our first page of data
            nextLink = if (previous = null) then url else Value.Metadata(previous)[NextLink]?,
            // if NextLink was set to null by the previous call, we know we have no more data
            page = if (nextLink <> null) then GetPage(nextLink) else null
        in
            page
    );

Implementando GetPage

Sua GetPage função usará Web.Contents para recuperar uma única página de dados do serviço TripPin e converter a resposta em uma tabela. Ele passa a resposta de Web.Contents para a função para extrair a URL da próxima página e a define no registro da tabela retornada GetNextLink meta (página de dados).

Essa implementação é uma versão ligeiramente modificada TripPin.Feed da chamada dos tutoriais anteriores.

GetPage = (url as text) as table =>
    let
        response = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),        
        body = Json.Document(response),
        nextLink = GetNextLink(body),
        data = Table.FromRecords(body[value])
    in
        data meta [NextLink = nextLink];

Sua GetNextLink função simplesmente verifica o corpo da resposta para um campo e retorna seu @odata.nextLink valor.

// In this implementation, 'response' will be the parsed body of the response after the call to Json.Document.
// Look for the '@odata.nextLink' field and simply return null if it doesn't exist.
GetNextLink = (response) as nullable text => Record.FieldOrDefault(response, "@odata.nextLink");

Juntando tudo

A etapa final para implementar sua lógica de paging é atualizar TripPin.Feed para usar as novas funções. Por enquanto, você está simplesmente chamando para , mas nos tutoriais subsequentes, você adicionará novas funcionalidades (como a imposição de um esquema e a lógica do parâmetro GetAllPagesByNextLink de consulta).

TripPin.Feed = (url as text) as table => GetAllPagesByNextLink(url);

Se você executar a mesma consulta de teste anteriormente no tutorial, deverá ver o leitor de página em ação. Você também deve ver que tem 20 linhas na resposta em vez de 8.

QueryWithPaging.

Se você olhar para as solicitações no Fiddler, agora deverá ver solicitações separadas para cada página de dados.

Fiddler.

Observação

Você observará solicitações duplicadas para a primeira página de dados do serviço, o que não é ideal. A solicitação extra é resultado do comportamento de verificação de esquema do mecanismo M. Ignore esse problema por enquanto e resolva-o no próximo tutorial,em que você aplicará um esquema de explict.

Conclusão

Esta lição mostrou como implementar o suporte à paginação para uma API Rest. Embora a lógica provavelmente varie entre as APIs, o padrão estabelecido aqui deve ser reutilizável com pequenas modificações.

Na próxima lição, você verá como aplicar um esquema explícito aos seus dados, indo além dos tipos simples e de dados que você text number obter do Json.Document .

Próximas etapas

TripPin Parte 6 – Esquema