TripPin parte 2 - Conector de dados para um serviço REST

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

Nesta lição, você vai:

  • Criar uma função base que chama uma API REST usando Web.Contents
  • Saber como definir cabeçalhos de solicitação e processar uma resposta JSON
  • Usar o Power BI Desktop para organizar a resposta em um formato amigável

Esta lição converte o conector baseado em OData para o serviço TripPin (criado na lição anterior) em um conector que se assemelha a algo que você criaria para qualquer API RESTful. O OData é uma API RESTful, mas com um conjunto fixo de convenções. A vantagem do OData é que ele fornece um esquema, um protocolo de recuperação de dados e uma linguagem de consulta padrão. A remoção do uso do OData.Feed exigirá que criemos essas funcionalidades no conector por nossa conta.

Resumo do conector OData

Antes de remover as funções do OData do conector, vamos fazer uma revisão rápida do que ele faz atualmente (principalmente nos bastidores) para recuperar dados do serviço.

Abra o projeto do conector do TripPin da Parte 1 no Visual Studio. Abra o arquivo de consulta e cole na seguinte consulta:

TripPin.Feed("https://services.odata.org/v4/TripPinService/Me")

Abra o Fiddler e selecione o botão Iniciar no Visual Studio.

No Fiddler, você verá três solicitações para o servidor:

Fiddler OData requests.

  • /Me – a URL real que você está solicitando.
  • /$metadata – uma chamada feita automaticamente pela função OData.Feed para determinar informações de esquema e tipo sobre a resposta.
  • /Me/BestFriend – um dos campos que foi (precipitadamente) extraído quando você listou o singleton /Me. Nesse caso, a chamada resultou em um status 204 No Content.

A avaliação da M é essencialmente lenta. Na maioria dos casos, os valores de dados só são recuperados/extraídos quando são necessários. Há cenários (como o caso /Me/BestFriend) em que um valor é obtido precipitadamente. Isso tende a ocorrer quando as informações de tipo são necessárias para um membro e o mecanismo não tem outra maneira de determinar o tipo, a não se recuperar o valor e inspecioná-lo. Fazer as coisas lentamente (ou seja, evitar pulls precipitados) é um dos principais aspectos para que um conector da M seja considerado de bom desempenho.

Observe os cabeçalhos de solicitação que foram enviados junto com as solicitações e o formato JSON da resposta da solicitação /Me.

{
  "@odata.context": "https://services.odata.org/v4/TripPinService/$metadata#Me",
  "UserName": "aprilcline",
  "FirstName": "April",
  "LastName": "Cline",
  "MiddleName": null,
  "Gender": "Female",
  "Age": null,
  "Emails": [ "April@example.com", "April@contoso.com" ],
  "FavoriteFeature": "Feature1",
  "Features": [ ],
  "AddressInfo": [
    {
      "Address": "P.O. Box 555",
      "City": {
        "Name": "Lander",
        "CountryRegion": "United States",
        "Region": "WY"
      }
    }
  ],
  "HomeAddress": null
}

Quando a consulta terminar de avaliar, a janela Saída da Consulta da M deverá mostrar o valor de Registro para o singleton Me.

OData results.

Se você comparar os campos na janela de saída com os campos retornados na resposta JSON bruta, observará que há uma incompatibilidade. O resultado da consulta tem campos adicionais (Friends, Trips, GetFriendsTrips) que não aparecem em nenhum lugar na resposta JSON. A função OData.Feed acrescentou automaticamente esses campos ao registro com base no esquema retornado por $metadata. Este é um bom exemplo de como um conector pode aumentar e/ou reformatar a resposta do serviço para fornecer uma melhor experiência do usuário.

Criando um conector REST básico

Agora você adicionará uma nova função exportada ao conector, a qual vai chamar Web.Contents.

Para fazer solicitações da Web bem-sucedidas para o serviço OData, no entanto, você precisará definir alguns cabeçalhos OData padrão. Você fará isso definindo um conjunto comum de cabeçalhos como uma nova variável em seu conector:

DefaultRequestHeaders = [
    #"Accept" = "application/json;odata.metadata=minimal",  // column name and values only
    #"OData-MaxVersion" = "4.0"                             // we only support v4
];

Você alterará sua implementação da função TripPin.Feed para que, em vez de usar OData.Feed, ela use Web.Contents para fazer uma solicitação da Web, e analise o resultado como um documento JSON.

TripPinImpl = (url as text) =>
    let
        source = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),
        json = Json.Document(source)
    in
        json;

Agora você pode testar isso no Visual Studio usando o arquivo de consulta. O resultado do registro /Me agora se assemelha ao JSON bruto que você viu na solicitação do Fiddler.

Se você observar o Fiddler ao executar a nova função, também verá que agora a avaliação faz uma única solicitação da Web, em vez de três. Parabéns, você conseguiu um aumento de 300% no desempenho! É claro que agora você perdeu todas as informações de tipo e esquema, mas ainda não há necessidade de se concentrar nessa parte.

Atualize sua consulta para acessar algumas das Entidades/Tabelas do TripPin, como:

  • https://services.odata.org/v4/TripPinService/Airlines
  • https://services.odata.org/v4/TripPinService/Airports
  • https://services.odata.org/v4/TripPinService/Me/Trips

Você observará que os caminhos usados para retornar tabelas bem formatadas agora retornam um campo de "valor" de nível superior com uma [Lista] inserida. Você precisará fazer algumas transformações no resultado para torná-lo utilizável para cenários do Power BI.

List results.

Criação de transformações no Power Query

Embora certamente seja possível criar suas transformações da M manualmente, a maioria das pessoas prefere usar o Power Query para moldar os dados. Você abrirá sua extensão no Power BI Desktop e a usará para criar consultas e transformar a saída em um formato mais amigável. Recompile sua solução, copie o novo arquivo de extensão para o diretório de conectores de dados personalizados e reinicie o Power BI Desktop.

Inicie uma nova consulta em branco e cole o seguinte na barra de fórmulas:

= TripPin.Feed("https://services.odata.org/v4/TripPinService/Airlines")

Não se esqueça de incluir o sinal de =.

Manipule a saída até que ela se pareça com o feed original do OData – uma tabela com duas colunas: AirlineCode e Name.

Formatted airlines.

A consulta resultante deve uma aparência semelhante a esta:

let
    Source = TripPin.Feed("https://services.odata.org/v4/TripPinService/Airlines"),
    value = Source[value],
    toTable = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    expand = Table.ExpandRecordColumn(toTable, "Column1", {"AirlineCode", "Name"}, {"AirlineCode", "Name"})
in
    expand

Dê um nome à consulta ("Companhias Aéreas").

Crie uma consulta em branco. Desta vez, use a função TripPin.Feed para acessar a entidade /Airports. Aplique transformações até obter algo semelhante ao compartilhamento mostrado abaixo. A consulta correspondente também pode ser encontrada abaixo – dê a essa consulta um nome ("Aeroportos") também.

Formatted airports.

let
    Source = TripPin.Feed("https://services.odata.org/v4/TripPinService/Airports"),
    value = Source[value],
    #"Converted to Table" = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"Name", "IcaoCode", "IataCode", "Location"}, {"Name", "IcaoCode", "IataCode", "Location"}),
    #"Expanded Location" = Table.ExpandRecordColumn(#"Expanded Column1", "Location", {"Address", "Loc", "City"}, {"Address", "Loc", "City"}),
    #"Expanded City" = Table.ExpandRecordColumn(#"Expanded Location", "City", {"Name", "CountryRegion", "Region"}, {"Name.1", "CountryRegion", "Region"}),
    #"Renamed Columns" = Table.RenameColumns(#"Expanded City",{{"Name.1", "City"}}),
    #"Expanded Loc" = Table.ExpandRecordColumn(#"Renamed Columns", "Loc", {"coordinates"}, {"coordinates"}),
    #"Added Custom" = Table.AddColumn(#"Expanded Loc", "Latitude", each [coordinates]{1}),
    #"Added Custom1" = Table.AddColumn(#"Added Custom", "Longitude", each [coordinates]{0}),
    #"Removed Columns" = Table.RemoveColumns(#"Added Custom1",{"coordinates"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Removed Columns",{{"Name", type text}, {"IcaoCode", type text}, {"IataCode", type text}, {"Address", type text}, {"City", type text}, {"CountryRegion", type text}, {"Region", type text}, {"Latitude", type number}, {"Longitude", type number}})
in
    #"Changed Type"

Você pode repetir esse processo para outros caminhos no serviço. Quando estiver pronto, vá para a próxima etapa de criação de uma tabela de navegação (simulada).

Simulando uma tabela de navegação

Agora você vai criar uma tabela (usando o código da M) que apresenta suas entidades TripPin bem formatadas.

Inicie uma consulta em branco e abra o Editor Avançado.

Cole a seguinte consulta:

let
    source = #table({"Name", "Data"}, {
        { "Airlines", Airlines },
        { "Airports", Airports }
    })
in
    source

Se você não tiver definido a configuração de Níveis de Privacidade como "Sempre ignorar configurações de nível de privacidade" (também conhecida como "Combinação Rápida") você verá um prompt de privacidade.

Firewall.

Os prompts de privacidade aparecem quando você está combinando dados de várias fontes e ainda não especificou um nível de privacidade para as fontes. Selecione o botão Continuar e defina o nível de privacidade da principal fonte como Público.

Privacy.

Selecione Salvar e sua tabela será exibida. Embora esta ainda não seja uma tabela de navegação, ela fornece a funcionalidade básica necessária para transformá-la durante uma lição subsequente.

FakeNav.

As verificações de combinação de dados não ocorrem ao acessar várias fontes de dentro de uma extensão. Como todas as chamadas de fonte de dados feitas de dentro da extensão herdam o mesmo contexto de autorização, supõe-se que elas sejam "seguras" para serem combinadas. Sua extensão sempre será tratada como uma única fonte de dados no que se refere a regras de combinação de dados. Os usuários continuariam recebendo os prompts de privacidade regulares ao combinar sua origem com outras fontes da M.

Se você executar o Fiddler e clicar no botão Atualizar Visualização no Editor de Consultas, observará solicitações da Web separadas para cada item na tabela de navegação. Isso indica que uma avaliação precipitada está ocorrendo, o que não é ideal ao criar tabelas de navegação com muitos elementos. As lições subsequentes mostrarão como criar uma tabela de navegação adequada que dê suporte à avaliação lenta.

Conclusão

Esta lição mostrou como criar um conector simples para um serviço REST. Nesse caso, você transformou uma extensão existente do OData em uma extensão REST padrão (usando Web.Contents), mas os mesmos conceitos se aplicam se você estiver criando uma extensão do zero.

Na próxima lição, você pegará as consultas criadas nesta lição usando o Power BI Desktop e as transformará em uma tabela de navegação verdadeira dentro da extensão.

Próximas etapas

TripPin Parte 3 – Tabelas de Navegação