Skip to content
Support formation Rapports Power BI
  • Pages
    • Présentation du support
    • Organisation des formations Power BI
    • Ressources pédagogiques
      • Kit pédagogique
      • Références
      • Mise en pratique
        • Exo 1
        • Exo 2
        • Exo 3
        • Exo 4 Parc Auto
        • Exo 5 Vente data
        • Exo 6 EuroDesk
      • Aide à la décision d'une formation courte Power BI
    • Généralités
      • Carte & schéma Power BI
      • Guide de survie
      • Architecture des données [ToDo]
      • Optimisations & bonnes pratiques
      • Présentation de la solution Power BI
        • Le marché des solutions BI
        • A quoi sert Power BI ?
        • Versions & licences
        • Installation
      • Organiser son projet Power BI
      • Microsoft Fabric [WIP]
      • Planification de l’implémentation de Power BI
      • Planification de la migration et du déploiement Power BI
    • 1 | Se connecter aux données
      • Modes de connexion aux données (résumé)
      • Obtenir les données d’un fichier Excel (SharePoint)
      • Obtenir les données d’un fichier CSV/Texte
      • Obtenir les données d'un classeur Google Sheet
      • Créer un Dataflow (Query dans BI Service)
      • Base de données (Azure SQL Server)
      • Récupérer le contenu d'un fichier Zip
      • Récupérer les fichiers d'un site SharePoint
      • OData
      • Tips - retours d'expériences [WIP]
    • 2 | Transformer ses données (Power Query)
      • Pourquoi transformer ses données ? (dépivoter)
      • Transformations
        • Fusionner des requêtes ("RECHERCHEV")
          • Les 6 types de jointure
        • Ajouter des requêtes
        • Transformations complexes
        • Combiner les feuilles d'un classeur
        • Combiner automatiquement les fichiers d’un dossier
        • Équivalent d'un RECHERCHEV / VRAI
      • Paramètres de la source de données
      • Langage M
        • 1 | Introduction
          • Ressources
        • 2 | Travailler avec Power Query
          • Options [WiP]
        • 3 | Accès et combinaison des données
        • 4 | Valeurs et expressions
        • Fonctions personnalisées
        • Recherche de la valeur précédente
        • Contourner l'erreur Firewall
        • Tips
          • let ... in imbriqués
          • Closures (fermetures) [WiP]
          • Récursivité [ToDo]
      • Transformer avec R [WIP]
      • Transformer avec Python [WIP]
      • Query folding
      • Tips
    • 3 | Modéliser (+ DAX)
      • Propriétés du modèle
      • Modèle de données
        • Relations
        • Schéma en étoile
          • Concepts liés au schéma en étoile
        • Slow Changing Dimension (SCD) [WiP]
      • Composants du modèles
        • Groupes & hiérarchies
        • Groupe de calculs
        • Relations
        • Mesures d'expression de détail des lignes
        • Agrégations manuelles et automatiques
      • DAX (le langage) et Power BI
        • Mesures explicites dans Power BI
        • Exploiter le volet Vue de requête DAX
        • Calculs visuels
        • User-defined functions (UDF)
      • Table de dates
      • Level Security
        • RLS (Row Level Security)
          • RLS partiel [WiP]
        • "Page Level Security" [WiP]
        • OLS (Object Level Security) [WiP]
        • Visuel Level Security
      • Tables recommandées (Type de données dans Excel)
      • Optimisation du modèle
        • Roche's Maxim
        • Réduire la taille du modèle
    • 4 | Présenter et analyser
      • Filtres & tris
        • Paramètres dynamiques
        • Focus Segments
        • L'exploration ("drill")
      • Objets visuels
        • Axes dynamiques
        • Couleurs fixes selon les catégories
        • KPI
        • Jauge
        • Sparkline [WiP]
        • Valeur dynamique des zones de texte
        • Icônes personnalisées
        • Cartes (Map)
        • Mises en forme conditionnelle
        • Jolies tables [WiP]
        • Nouvelles cartes [WiP]
      • Animer le rapport
        • Infos bulles personnalisées
        • Extractions
        • Signets
      • Étendre le rapport
        • Ajouter des visuels
        • Présenter avec R [WIP]
        • Présenter avec Python [WIP]
        • Visuels personnalisés avec Deneb
        • Infographic Designer [WiP]
        • Power Automate dans Power BI
        • Rapports multilangue
      • Styles & thèmes
        • Styles prédéfinis
    • 5 | Partager et diffuser
      • Diffuser et partager un rapport Power BI
        • Modèle sémantique comme source de rapport
        • Partager un espace de travail
        • Intégrer Power BI [WiP]
        • bouton_publier
          "Publier" avec Power BI Desktop dans OneDrive/SharePoint
      • Tableaux de bord
      • Créer l'application d'un Espace de travail Power BI
      • Espaces de travail
        • [Admin] Créer un Espace de travail
        • Approbation
        • Application dans un espace de travail
        • Rôles dans un espace de travail
        • Sécurité [ToDo]
        • Alertes dans Power BI
          • Fabric Activator pour Power BI
      • Power BI Server
        • Analysis Services (SSAS)
      • Datamart [beta]
      • Date de dernière actualisation
      • Requête dans Excel vers un modèle Power BI
      • Incorporer des rapports (Embed - API)
    • Développeurs
      • Tabular Editor
      • Mode développeur (Projet Power BI)
      • [Dev] PowerOps : analyser un rapport avec
      • ModeOp express
      • Semantic Labs
      • API REST
        • icon picker
          Connecteur personnalisé
      • Flux de tâches translyticaux [mai 2025]
    • Echange documents formation
    • logo_copilot
      Copilot
      • ChatGPT
    • Préparation PL-300

Connecteur personnalisé

sample35_ConnectorAPI.pbix
20 kB

Mode opératoire

Installer Visual Studio Code
Installer l’extension Power Query SDK
Créer un nouveau projet d’extension (template “Data Connector”)
Remplacer le contenu du fichier *.pq par le squelette ci-dessous
Build → génération du fichier .mez
Déposer le .mez dans Documents\Power BI Desktop\Custom Connectors (puis autoriser les extensions dans Options > Sécurité, selon politique interne)

Fichier pq

section ClientVentes;

// ===========================
// PUBLISH (entrée dans "Obtenir des données")
// ===========================
[DataSource.Kind="ClientVentes", Publish="ClientVentes.Publish"]
shared ClientVentes.Contents =
Value.ReplaceType(ClientVentesImpl,
type function (
ApiBaseUrl as (type text meta [Documentation.FieldCaption="API Base URL, par ex. https://akbbuehslmcrjsgupcon.supabase.co/functions/v1"]),
ApiPathClients as (type text meta [Documentation.FieldCaption="Chemin Clients par ex. /api/clients", Publish.Preview.Default="/api/clients"]),
ApiPathVentes as (type text meta [Documentation.FieldCaption="Chemin Ventes par ex. /api/ventes", Publish.Preview.Default="/api/ventes"]),
AuthBaseUrl as (type text meta [Documentation.FieldCaption="Auth Base URL, par ex. https://akbbuehslmcrjsgupcon.supabase.co", Publish.Preview.Default="https://akbbuehslmcrjsgupcon.supabase.co"]),
optional LoginPath as (type text meta [Documentation.FieldCaption="Login/Token Path", Publish.Preview.Default="/api/auth/token-from-key"]),
optional APIKey as (type text meta [Documentation.FieldCaption="API Key (prioritaire si non vide)"]),
optional ClientId as (type text meta [Documentation.FieldCaption="Client ID"]),
optional ClientSecret as (type text meta [Documentation.FieldCaption="Client Secret"]),
optional Scope as (type text meta [Documentation.FieldCaption="Scope"]),
optional Email as (type text meta [Documentation.FieldCaption="Email"]),
optional Password as (type text meta [Documentation.FieldCaption="Password"])
) as table
);

// ===========================
// IMPL
// ===========================
ClientVentesImpl =
(
ApiBaseUrl as text,
ApiPathClients as text,
ApiPathVentes as text,
AuthBaseUrl as text,
optional LoginPath as nullable text,
optional APIKey as nullable text,
optional ClientId as nullable text,
optional ClientSecret as nullable text,
optional Scope as nullable text,
optional Email as nullable text,
optional Password as nullable text
) as table =>
let
// ---- Defaults (adapter) ----
_ApiBaseUrl = ApiBaseUrl,
_ApiPathClients = ApiPathClients,
_ApiPathVentes = ApiPathVentes,
_AuthBaseUrl = AuthBaseUrl,
_LoginPath = if IsNonEmpty(LoginPath) then LoginPath else "/api/auth/token-from-key",

// ---- Auth precedence ----
Headers = GetAuthHeaders(_AuthBaseUrl, _LoginPath, APIKey, ClientId, ClientSecret, Scope, Email, Password),

// ---- Calls ----
ClientsJson = CallApiJson(_ApiBaseUrl, _ApiPathClients, Headers),
VentesJson = CallApiJson(_ApiBaseUrl, _ApiPathVentes, Headers),

ClientsData = GetDataArray(ClientsJson),
VentesData = GetDataArray(VentesJson),

TblClients = Table.FromList(ClientsData, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
TblClients2 =
if Table.RowCount(TblClients) > 0 and Value.Is(TblClients{0}[Column1], type record)
then Table.ExpandRecordColumn(TblClients, "Column1", Record.FieldNames(TblClients{0}[Column1]))
else TblClients,

TblVentes = Table.FromList(VentesData, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
TblVentes2 =
if Table.RowCount(TblVentes) > 0 and Value.Is(TblVentes{0}[Column1], type record)
then Table.ExpandRecordColumn(TblVentes, "Column1", Record.FieldNames(TblVentes{0}[Column1]))
else TblVentes,

// ---- Navigation table (Clients & Ventes) ----
Nav = #table(
type table [Name = text, Data = any, ItemKind = text, ItemName = text, IsLeaf = logical],
{
{"Clients", TblClients2, "Table", "Table", true},
{"Ventes", TblVentes2, "Table", "Table", true}
}
)
//Nav2 = Table.ToNavigationTable(Nav, {"Name"}, "Name", "Data", "ItemKind", "ItemName", "IsLeaf")
in
Nav;

// ===========================
// AUTH HELPERS
// ===========================
GetAuthHeaders =
(
AuthBaseUrl as text,
LoginPath as text,
APIKey as nullable text,
ClientId as nullable text,
ClientSecret as nullable text,
Scope as nullable text,
Email as nullable text,
Password as nullable text
) as record =>
let
HasApiKey = IsNonEmpty(APIKey),
HasClientCred = IsNonEmpty(ClientId) and IsNonEmpty(ClientSecret),
HasUserPass = IsNonEmpty(Email) and IsNonEmpty(Password),

Result =
if HasApiKey then
// Mode 1 : APIKey uniquement (prioritaire)
[Accept="application/json", #"X-API-Key"=Text.From(APIKey)]
else if HasClientCred then
// Mode 2 : Client credentials -> token -> Bearer
let
Token = GetToken_ClientCredentials(AuthBaseUrl, LoginPath, Text.From(ClientId), Text.From(ClientSecret), Scope),
TokenType = "Bearer"
in
[Accept="application/json", Authorization = TokenType & " " & Token]
else if HasUserPass then
// Mode 3 : Email/Password -> token -> Bearer
let
Token = GetToken_EmailPassword(AuthBaseUrl, LoginPath, Text.From(Email), Text.From(Password)),
TokenType = "Bearer"
in
[Accept="application/json", Authorization = TokenType & " " & Token]
else
error "Aucune auth valide : renseigner APIKey, ou ClientId+ClientSecret, ou Email+Password."
in
Result;

GetToken_ClientCredentials =
(AuthBaseUrl as text, LoginPath as text, ClientId as text, ClientSecret as text, Scope as nullable text) as text =>
let
BodyText =
Uri.BuildQueryString([
grant_type = "client_credentials",
client_id = ClientId,
client_secret = ClientSecret,
scope = if Scope = null then "" else Text.From(Scope)
]),
Resp =
Json.Document(
Web.Contents(
AuthBaseUrl,
[
RelativePath = LoginPath,
Headers = [
#"Content-Type" = "application/x-www-form-urlencoded",
Accept = "application/json"
],
Content = Text.ToBinary(BodyText),
ManualStatusHandling = {400,401,403,500}
]
)
),
Tok =
if Record.HasFields(Resp, "access_token") then Text.From(Resp[access_token])
else if Record.HasFields(Resp, "token") then Text.From(Resp[token])
else error "Token introuvable (client_credentials)."
in
Tok;

GetToken_EmailPassword =
(AuthBaseUrl as text, LoginPath as text, Email as text, Password as text) as text =>
let
LoginBody = Json.FromValue([ email = Email, password = Password ]),
Resp =
Json.Document(
Web.Contents(
AuthBaseUrl,
[
RelativePath = LoginPath,
Headers = [
#"Content-Type" = "application/json",
Accept = "application/json"
],
Content = LoginBody,
ManualStatusHandling = {400,401,403,500}
]
)
),
Tok =
if Record.HasFields(Resp, "token") then Text.From(Resp[token])
else if Record.HasFields(Resp, "access_token") then Text.From(Resp[access_token])
else if Record.HasFields(Resp, "data") and Record.HasFields(Resp[data], "token") then Text.From(Resp[data][token])
else error "Token introuvable (email/password)."
in
Tok;

// ===========================
// API HELPERS
// ===========================
CallApiJson =
(ApiBaseUrl as text, ApiPath as text, Headers as record) as any =>
let
Bin =
Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.