Partager via


TripPin, partie 2 - Connecteur de données pour un service REST

Ce tutoriel en plusieurs parties traite de la création d’une extension de source de données pour Power Query. Le tutoriel est destiné à être utilisé de manière séquentielle : chaque leçon s’appuie sur le connecteur créé dans les leçons précédentes, ajoutant de nouvelles fonctionnalités de manière incrémentielle.

Dans cette leçon, vous allez :

  • Créer une fonction de base qui appelle une API REST avec Web.Contents
  • Découvrir comment définir des en-têtes de requête et traiter une réponse JSON
  • Utiliser Power BI Desktop pour transformer la réponse dans un format convivial

Cette leçon convertit le connecteur basé sur OData pour le service TripPin (créé dans la leçon précédente) en connecteur qui ressemble à quelque chose que vous créeriez pour n’importe quelle API RESTful. OData est une API RESTful, mais avec un ensemble fixe de conventions. L’avantage d’OData est qu’il fournit un schéma, un protocole d’extraction de données et un langage de requête standard. La suppression d'OData.Feed nous oblige à intégrer nous-mêmes ces fonctionnalités au connecteur.

Récapitulatif du connecteur OData

Avant de supprimer les fonctions OData de votre connecteur, examinons rapidement ce qu’il fait actuellement (principalement en arrière-plan) pour récupérer les données du service.

Ouvrez le projet d’extension TripPin à partir de la partie 1 dans Visual Studio Code. Ouvrez le fichier de requête et collez la requête suivante :

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

Ouvrez Fiddler, puis évaluez le fichier Power Query actuel dans Visual Studio Code.

Dans Fiddler, il existe trois requêtes adressées au serveur :

Demandes OData Fiddler.

  • /Me : l’URL réelle que vous demandez.
  • /$metadata : l’appel effectué automatiquement par la fonction OData.Feed pour déterminer les informations de schéma et de type sur la réponse.
  • /Me/BestFriend : l’un des champs qui a été extrait (de manière hâtive) quand vous avez listé le singleton /Me. Dans ce cas, l’appel a abouti à un état 204 No Content.

L’évaluation M est principalement différée. Dans la plupart des cas, les valeurs de données ne sont récupérées/extraites que lorsqu’elles sont requises. Dans certains scénarios (comme le cas /Me/BestFriend), une valeur est extraite de manière hâtive. Cela a tendance à se produire lorsque des informations de type sont nécessaires pour un membre et que le moteur n’a pas d’autre moyen de déterminer le type que de récupérer la valeur et de l’inspecter. Différer certaines choses (c’est-à-dire éviter les extractions hâtives) est l’un des aspects clés pour rendre un connecteur M performant.

Notez les en-têtes de demande qui ont été envoyés avec les demandes et le format JSON de la réponse de la demande /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
}

Lorsque l’évaluation de la requête est terminée, la fenêtre de résultats de PQTest doit afficher la valeur d’enregistrement pour le singleton Me.

Résultats OData.

Si vous comparez les champs de la fenêtre de sortie avec les champs retournés dans la réponse JSON brute, vous pouvez constater une incohérence. Le résultat de la requête a des d’autres champs (Friends, Trips, GetFriendsTrips) qui n’apparaissent nulle part dans la réponse JSON. La fonction OData.Feed a automatiquement ajouté ces champs à l’enregistrement en fonction du schéma retourné par $metadata. C’est un bon exemple de la façon dont un connecteur peut augmenter et/ou remettre en forme la réponse du service pour offrir une meilleure expérience utilisateur.

Création d’un connecteur REST de base

Vous allez maintenant ajouter une nouvelle fonction exportée à votre connecteur qui appelle Web.Contents.

Toutefois, pour effectuer des demandes web réussies sur le service OData, vous devez définir des en-têtes OData standard. Pour ce faire, définissez un ensemble commun d’en-têtes en tant que nouvelle variable dans votre connecteur :

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

Modifiez l’implémentation de votre fonction TripPin.Feed de manière à ce qu’elle utilise OData.Feed à la place de Web.Contents pour effectuer une requête Web et analyser le résultat en tant que document JSON.

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

N’oubliez pas de générer une version de votre connecteur maintenant que vous avez apporté des modifications au fichier du connecteur. Vous pouvez ensuite évaluer le fichier de requête (TripPin.query.pq). Le résultat de l’enregistrement /Me ressemble maintenant au JSON brut que vous avez vu dans la demande Fiddler.

Si vous examinez Fiddler lors de l’exécution de la nouvelle fonction, vous remarquerez également que l’évaluation effectue désormais une seule demande web au lieu de trois. Félicitations, vous avez augmenté les performances de 300 % ! Toutes les informations relatives au type et au schéma ont été perdues, mais ne nous en préoccupons pas pour le moment.

Mettez à jour votre requête pour accéder à certaines entités/tables TripPin, telles que :

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

Notez que les chemins qui retournaient des tables bien mises en forme retournent désormais un champ « value » de niveau supérieur avec un [List] intégré. Vous devrez effectuer quelques transformations sur le résultat afin de le rendre utilisable pour les scénarios de consommation de l’utilisateur final.

Lister les résultats.

Création de transformations dans Power Query

Bien qu’il soit possible de créer manuellement vos transformations M, la plupart des utilisateurs préfèrent utiliser Power Query pour mettre en forme leurs données. Vous allez ouvrir votre extension dans Power BI Desktop et l’utiliser pour concevoir des requêtes afin de transformer la sortie dans un format plus convivial. Regénérez votre solution, copiez le nouveau fichier d’extension dans votre répertoire Custom Data Connectors et relancez Power BI Desktop.

Démarrez une nouvelle requête vide et collez ce qui suit dans la barre de formule :

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

Veillez à inclure le signe =.

Manipulez la sortie jusqu’à ce qu’elle ressemble au flux OData d’origine, une table à deux colonnes : AirlineCode et Name.

Compagnies aériennes mises en forme.

La requête résultante doit ressembler à ceci :

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

Donnez un nom à la requête (« Airlines »).

Créez une requête vide. Cette fois, utilisez la fonction TripPin.Feed pour accéder à l’entité /Airports. Appliquez des transformations jusqu’à ce que vous obteniez quelque chose de similaire au partage illustré ci-dessous. La requête correspondante est également disponible ci-dessous. Donnez aussi un nom à cette requête (« Airports »).

Aéroports mis en forme.

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"

Vous pouvez répéter ce processus pour plus de chemins sous le service. Quand vous êtes prêt, passez à l’étape suivante de création d’une table de navigation (fictive).

Simulation d’une table de navigation

Vous allez maintenant créer une table (avec du code M) qui présente vos entités TripPin correctement mises en forme.

Démarrez une nouvelle requête vide et ouvrez l’Éditeur avancé.

Collez la requête suivante :

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

Si vous n’avez pas défini votre paramètre Niveaux de confidentialité sur « Toujours ignorer les paramètres de niveau de confidentialité » (également appelé « Regroupement rapide »), vous verrez une invite de confidentialité.

Pare-feu.

Les invites de confidentialité s’affichent lorsque vous combinez des données provenant de plusieurs sources et que vous n’avez pas encore spécifié de niveau de confidentialité pour une ou plusieurs sources. Sélectionnez le bouton Continuer et définissez le niveau de confidentialité de la source supérieure sur Public.

Confidentialité.

Sélectionnez Enregistrer. Votre table s’affiche. Bien qu’il ne s’agisse pas encore d’une table de navigation, elle fournit les fonctionnalités de base dont vous avez besoin pour en faire une dans une leçon ultérieure.

FakeNav.

Les vérifications de combinaison de données ne se produisent pas lors de l’accès à plusieurs sources de données à partir d’une extension. Étant donné que tous les appels de source de données effectués à partir de l’extension héritent du même contexte d’autorisation, il est supposé qu’ils peuvent être combinés « en toute sécurité ». Votre extension sera toujours traitée comme source de données unique en ce qui concerne les règles de combinaison de données. Les utilisateurs reçoivent toujours les invites de confidentialité régulières lors de la combinaison de votre source avec d’autres sources M.

Si vous exécutez Fiddler et cliquez sur le bouton Actualiser l’aperçu dans l’Éditeur de requête, vous pouvez observer des requêtes Web distinctes pour chaque élément de votre table de navigation. Cela indique qu’une évaluation hâtive est en cours, ce qui n’est pas idéal lors de la création de tables de navigation contenant beaucoup d’éléments. Les leçons suivantes montrent comment créer une table de navigation appropriée qui prend en charge l’évaluation différée.

Conclusion

Dans cette leçon, vous avez vu comment créer un connecteur simple pour un service REST. Ici, vous avez transformé une extension OData existante en extension REST standard (avec Web.Contents), mais les mêmes concepts s’appliquent si vous créez une extension à partir de zéro.

Dans la leçon suivante, vous allez prendre les requêtes créées dans cette leçon avec Power BI Desktop et les transformer en véritable table de navigation dans l’extension.

Étapes suivantes

TripPin, partie 3 - Tables de navigation