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

Este tutorial com várias partes aborda a criação de uma nova extensão de fonte de dados para o 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 seu conector.

Nesta lição, você irá:

  • Criar uma função base que chame uma API REST usando Web.Contents
  • Saiba como definir cabeçalhos de solicitação e processar uma resposta JSON
  • Usar o Power BI Desktop para transformar 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 semelhante a algo que você criaria para qualquer API RESTful. OData é uma API RESTful, mas com um conjunto fixo de convenções. A vantagem do OData é que ele fornece um esquema, protocolo de recuperação de dados e linguagem de consulta padrão. Tirar o uso do OData.Feed exigirá que nós mesmos construamos esses recursos no conector.

Recapitulação do conector OData

Antes de remover as funções OData do seu 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 de extensão TripPin da Parte 1 no Visual Studio Code. Abra o arquivo de consulta e cole na seguinte consulta:

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

Abra o Fiddler e avalie o arquivo atual do Power Query no Visual Studio Code.

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

Solicitações do Fiddler OData.

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

A avaliação M é maioritariamente preguiçosa. 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 é puxado ansiosamente. 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 ser recuperar o valor e inspecioná-lo. Tornar as coisas preguiçosas (ou seja, evitar puxadas ansiosas) é um dos aspectos-chave para tornar um conector M eficiente.

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 de resultados PQTest deverá mostrar o valor Record para o singleton Me.

Resultados OData.

Se você comparar os campos na janela de saída com os campos retornados na resposta JSON bruta, notará uma incompatibilidade. O resultado da consulta tem outros campos (Friends, Trips, GetFriendsTrips) que não aparecem em nenhum lugar na resposta JSON. A função OData.Feed anexou 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 ao usuário.

Criando um conector REST básico

Agora você adicionará uma nova função exportada ao seu conector que chama Web.Contents.

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

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

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

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

Lembre-se de criar seu conector agora que você fez alterações no arquivo do conector. Em seguida, você pode avaliar o arquivo de consulta (TripPin.query.pq). O resultado do registro /Me agora se assemelha ao JSON bruto que você viu na solicitação do Fiddler.

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

Atualize a sua consulta para aceder a algumas das Entidades/Tabelas do TripPin, tais como:

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

Você notará que os caminhos que costumavam retornar tabelas bem formatadas agora retornam um campo "valor" de nível superior com uma [Lista] incorporada. Você precisará fazer algumas transformações no resultado para torná-lo utilizável para cenários de consumo do usuário final.

Listar resultados.

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

Embora seja possível criar as suas transformações M manualmente, a maioria das pessoas prefere utilizar o Power Query para dar forma aos seus dados. Você abrirá sua extensão no Power BI Desktop e a usará para projetar consultas para transformar a saída em um formato mais amigável. Reconstrua sua solução, copie o novo arquivo de extensão para o diretório 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")

Certifique-se de incluir o sinal =.

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

Companhias aéreas formatadas.

A consulta resultante deve ter esta aparência:

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 nova consulta em branco. Desta vez, utilize a TripPin.Feed função para aceder à entidade /Airports. Aplique transformações até obter algo semelhante ao compartilhamento mostrado abaixo. A consulta correspondente também pode ser encontrada abaixo—dê a esta consulta um nome ("Aeroportos") também.

Aeroportos formatados.

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 obter mais caminhos no serviço. Quando estiver pronto, passe para a próxima etapa de criação de uma tabela de navegação (simulada).

Simular uma tabela de navegação

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

Inicie uma nova Consulta em Branco e abra o Editor Avançado.

Cole na seguinte consulta:

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

Se você não tiver definido sua 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á uma solicitação de privacidade.

Firewall.

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

Privacidade.

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

FakeNav.

As verificações de combinação de dados não ocorrem ao acessar várias fontes de dados 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, presume-se que elas sejam "seguras" para combinar. Sua extensão sempre será tratada como uma única fonte de dados quando se trata de regras de combinação de dados. Os usuários ainda receberiam os avisos de privacidade regulares ao combinar sua fonte com outras fontes M.

Se você executar o Fiddler e selecionar o botão Atualizar visualização no Editor de Consultas, observe as solicitações da Web separadas para cada item na tabela de navegação. Isso indica que está ocorrendo uma avaliação ansiosa, o que não é ideal ao construir tabelas de navegação com muitos elementos. As lições subsequentes mostram como construir uma tabela de navegação adequada que suporte uma avaliação preguiçosa.

Conclusão

Esta lição mostrou como criar um conector simples para um serviço REST. Nesse caso, você transformou uma extensão OData existente em uma extensão REST padrão (usando Web.Contents), mas os mesmos conceitos se aplicam se você estiver criando uma nova 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 verdadeira tabela de navegação dentro da extensão.

Próximos passos

TripPin Parte 3 - Tabelas de navegação