Cette page fournit des exemples de code écrits en React et en JavaScript pour les fonctions courantes que vous pouvez utiliser dans vos extensions.
Utiliser le SDK d'extension Looker
Les extensions doivent établir une connexion avec l'hôte Looker. Dans React, cela se fait en encapsulant l'extension dans un composant ExtensionProvider40
. Ce composant établit une connexion avec l'hôte Looker et met à la disposition de l'extension le SDK d'extension Looker et le SDK Looker.
import React from 'react'
import { ExtensionProvider40 } from '@looker/extension-sdk-react'
import { DemoCoreSDK } from './DemoCoreSDK'
export const App = () => {
return (
<ExtensionProvider40 chattyTimeout={-1}>
<DemoCoreSDK />
</ExtensionProvider40>
)
}
Informations sur les fournisseurs d'extensions
Les fournisseurs d'extensions exposent le SDK et l'API de l'extension Looker aux extensions. Différentes versions du fournisseur d'extensions ont été créées depuis la création du framework d'extensions. Cette section explique l'historique des fournisseurs d'extensions et pourquoi ExtensionProvider40 est le fournisseur recommandé.
Le premier fournisseur d'extensions était ExtensionProvider
, qui exposait les deux SDK Looker, les versions 3.1 et 4.0. L'inconvénient était que l'inclusion des deux SDK augmentait la taille du bundle de production final.
ExtensionProvider2
a ensuite été créé. Cette fonctionnalité a été créée, car il n'était pas logique qu'une extension utilise les deux SDK et force le développeur à choisir l'un ou l'autre. Malheureusement, les deux SDK étaient toujours inclus dans la taille du bundle de production final.
Lorsque le SDK 4.0 est passé en version GA, ExtensionProvider40
a été créé. L'avantage de ExtensionProvider40
est que le développeur n'a pas à choisir le SDK à utiliser, car le SDK 4.0 est la seule version disponible. Étant donné que le SDK 3.1 n'est pas inclus dans le bundle final, cela présente l'avantage de réduire la taille du bundle.
Pour ajouter des fonctions à partir du SDK Looker Extension, vous devez d'abord obtenir une référence au SDK, ce qui peut se faire à partir du fournisseur ou de manière globale. Vous pouvez ensuite appeler les fonctions du SDK comme dans n'importe quelle application JavaScript.
- Pour accéder au SDK à partir du fournisseur, procédez comme suit:
import { ExtensionContext40 } from '@looker/extension-sdk-react'
export const Comp1 = () => {
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK, coreSDK } = extensionContext
- Pour accéder au SDK de manière globale (l'extension doit être initialisée avant cette opération), procédez comme suit:
const coreSDK = getCoreSDK()
Vous pouvez désormais utiliser le SDK comme vous le feriez dans n'importe quelle application JavaScript:
const GetLooks = async () => {
try {
const looks = await sdk.ok(sdk.all_looks('id'))
// process looks
. . .
} catch (error) {
// do error handling
. . .
}
}
Parcourir d'autres éléments de l'instance Looker
Étant donné que l'extension s'exécute dans une iframe en bac à sable, vous ne pouvez pas accéder à d'autres endroits de l'instance Looker en mettant à jour l'objet window.location
du parent. Vous pouvez naviguer à l'aide du SDK d'extension Looker.
Cette fonction nécessite l'droit d'accès navigation
.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
extensionSDK.updateLocation('/browse')
Ouvrir une nouvelle fenêtre de navigateur
Étant donné que l'extension s'exécute dans un iframe en bac à sable, vous ne pouvez pas utiliser la fenêtre parente pour ouvrir une nouvelle fenêtre de navigateur. Vous pouvez ouvrir une fenêtre de navigateur à l'aide du SDK Looker Extension.
Cette fonction nécessite l'autorisation new_window
pour ouvrir une nouvelle fenêtre vers un emplacement dans l'instance Looker actuelle, ou l'autorisation new_window_external_urls
pour ouvrir une nouvelle fenêtre qui s'exécute sur un autre hôte.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
extensionSDK.openBrowserWindow('/browse', '_blank')
. . .
extensionSDK.openBrowserWindow('https://docs.looker.com/reference/manifest-params/application#entitlements', '_blank')
Routage et liens profonds
Les éléments suivants s'appliquent aux extensions basées sur React.
Les composants ExtensionProvider
, ExtensionProvider2
et ExtensionProvider40
créent automatiquement un routeur React appelé MemoryRouter
que vous pouvez utiliser. N'essayez pas de créer un BrowserRouter
, car il ne fonctionne pas dans les iFrames en bac à sable. N'essayez pas de créer un HashRouter
, car il ne fonctionne pas dans les iFrames en bac à sable pour la version non Chromium du navigateur Microsoft Edge.
Si le MemoryRouter
est utilisé et que vous utilisez react-router
dans votre extension, le framework d'extension synchronise automatiquement le routeur de votre extension avec le routeur hôte Looker. Cela signifie que l'extension sera avertie des clics sur les boutons "Précédent" et "Suivant" du navigateur, ainsi que du chemin d'accès actuel lorsque la page sera actualisée. Cela signifie également que l'extension doit être compatible automatiquement avec les liens profonds. Consultez les exemples d'extension pour savoir comment utiliser react-router
.
Données contextuelles des extensions
Les données de contexte du framework d'extension ne doivent pas être confondues avec les contextes React.
Les extensions peuvent partager des données contextuelles entre tous les utilisateurs d'une extension. Les données de contexte peuvent être utilisées pour les données qui ne changent pas fréquemment et qui ne nécessitent pas de conditions de sécurité particulières. Vous devez faire preuve de prudence lorsque vous écrivez les données, car il n'y a pas de verrouillage des données et la dernière écriture l'emporte. Les données de contexte sont disponibles pour l'extension immédiatement après le démarrage. Le SDK d'extension Looker fournit des fonctions permettant de mettre à jour et d'actualiser les données de contexte.
La taille maximale des données contextuelles est d'environ 16 Mo. Les données de contexte seront sérialisées en chaîne JSON. Vous devez donc également en tenir compte si vous utilisez des données de contexte pour votre extension.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
// Get loaded context data. This will reflect any updates that have
// been made by saveContextData.
let context = await extensionSDK.getContextData()
. . .
// Save context data to Looker server.
context = await extensionSDK.saveContextData(context)
. . .
// Refresh context data from Looker server.
context = await extensionSDK.refreshContextData()
Attributs utilisateur
Le SDK d'extension Looker fournit une API permettant d'accéder aux attributs utilisateur de Looker. Il existe deux types d'accès aux attributs utilisateur:
Étendue : associé à l'extension. Un attribut utilisateur à portée limitée est associé à un espace de noms de l'extension. L'attribut utilisateur doit être défini dans l'instance Looker avant de pouvoir être utilisé. Pour définir un espace de noms pour un attribut utilisateur, ajoutez le nom de l'extension au nom de l'attribut. Tous les traits de soulignement et les caractères '::' du nom de l'extension doivent être remplacés par un trait de soulignement, car les traits de soulignement et les deux-points ne peuvent pas être utilisés dans les noms d'attributs utilisateur.
Par exemple, un attribut utilisateur à portée limitée nommé
my_value
utilisé avec un ID d'extension demy-extension::my-extension
doit avoir un nom d'attribut utilisateur demy_extension_my_extension_my_value
défini. Une fois défini, l'attribut utilisateur peut être lu et mis à jour par l'extension.Global : il s'agit d'attributs utilisateur globaux en lecture seule. L'attribut utilisateur
locale
en est un exemple.
Voici une liste des appels d'API des attributs utilisateur:
userAttributeGetItem
: lit un attribut utilisateur. Une valeur par défaut peut être définie et sera utilisée si aucune valeur d'attribut utilisateur n'existe pour l'utilisateur.userAttributeSetItem
: enregistre un attribut utilisateur pour l'utilisateur actuel. Échec pour les attributs utilisateur globaux. La valeur enregistrée n'est visible que par l'utilisateur actuel.userAttributeResetItem
: réinitialise un attribut utilisateur pour l'utilisateur actuel sur la valeur par défaut. Échec pour les attributs utilisateur globaux.
Pour accéder aux attributs utilisateur, vous devez spécifier les noms des attributs dans les autorisations global_user_attributes
et/ou scoped_user_attributes
. Par exemple, dans le fichier manifeste du projet LookML, vous devez ajouter:
entitlements: {
scoped_user_attributes: ["my_value"]
global_user_attributes: ["locale"]
}
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
// Read global user attribute
const locale = await extensionSDK.userAttributeGetItem('locale')
// Read scoped user attribute
const value = await extensionSDK.userAttributeGetItem('my_value')
// Update scoped user attribute
const value = await extensionSDK.userAttributeSetItem('my_value', 'abcd1234')
// Reset scoped user attribute
const value = await extensionSDK.userAttributeResetItem('my_value')
Stockage en local
Les iFrames en bac à sable n'autorisent pas l'accès au stockage local du navigateur. Le SDK Looker Extension permet à une extension de lire et d'écrire dans l'espace de stockage local de la fenêtre parente. Le stockage local est associé à l'espace de noms de l'extension. Il ne peut donc pas lire le stockage local créé par la fenêtre parente ou d'autres extensions.
L'utilisation du stockage local nécessite l'autorisation local_storage
.
L'API localhost de l'extension est asynchrone, contrairement à l'API de stockage local du navigateur synchrone.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
// Read from local storage
const value = await extensionSDK.localStorageGetItem('my_storage')
// Write to local storage
await extensionSDK.localStorageSetItem('my_storage', 'abcedefh')
// Delete item from local storage
await extensionSDK.localStorageRemoveItem('my_storage')
Modifier le titre de la page
Les extensions peuvent modifier le titre de la page actuelle. Aucune autorisation n'est requise pour effectuer cette action.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
extensionSDK.updateTitle('My Extension Title')
Écriture dans le presse-papiers du système
Les iFrames en bac à sable n'autorisent pas l'accès au presse-papiers du système. Le SDK Looker Extension permet à une extension d'écrire du texte dans le presse-papiers du système. Pour des raisons de sécurité, l'extension n'est pas autorisée à lire dans le presse-papiers du système.
Pour écrire dans le presse-papiers du système, vous devez disposer du droit use_clipboard
.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
// Write to system clipboard
try {
await extensionSDK.clipboardWrite(
'My interesting information'
)
. . .
} catch (error) {
. . .
}
Intégrer des tableaux de bord, des présentations et des explorations
Le framework d'extension permet d'intégrer des tableaux de bord, des présentations et des explorations.
Le droit d'accès use_embeds
est obligatoire. Nous vous recommandons d'utiliser le SDK d'ingestion JavaScript Looker pour intégrer du contenu. Pour en savoir plus, consultez la documentation du SDK Embed.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
const canceller = (event: any) => {
return { cancel: !event.modal }
}
const updateRunButton = (running: boolean) => {
setRunning(running)
}
const setupDashboard = (dashboard: LookerEmbedDashboard) => {
setDashboard(dashboard)
}
const embedCtrRef = useCallback(
(el) => {
const hostUrl = extensionContext?.extensionSDK?.lookerHostData?.hostUrl
if (el && hostUrl) {
el.innerHTML = ''
LookerEmbedSDK.init(hostUrl)
const db = LookerEmbedSDK.createDashboardWithId(id as number)
.withNext()
.appendTo(el)
.on('dashboard:loaded', updateRunButton.bind(null, false))
.on('dashboard:run:start', updateRunButton.bind(null, true))
.on('dashboard:run:complete', updateRunButton.bind(null, false))
.on('drillmenu:click', canceller)
.on('drillmodal:explore', canceller)
.on('dashboard:tile:explore', canceller)
.on('dashboard:tile:view', canceller)
.build()
.connect()
.then(setupDashboard)
.catch((error: Error) => {
console.error('Connection error', error)
})
}
},
[]
)
return (<EmbedContainer ref={embedCtrRef} />)
Les exemples d'extensions utilisent des composants stylisés pour fournir un style simple à l'iFrame générée. Exemple :
import styled from "styled-components"
export const EmbedContainer = styled.div`
width: 100%;
height: 95vh;
& > iframe {
width: 100%;
height: 100%;
}
Accéder à des points de terminaison d'API externes
Le framework d'extension fournit deux méthodes pour accéder aux points de terminaison d'API externes:
- Le proxy du serveur : accède au point de terminaison via le serveur Looker. Ce mécanisme permet au serveur Looker de définir de manière sécurisée les ID client et les clés secrètes.
- Le proxy de récupération : accède au point de terminaison à partir du navigateur de l'utilisateur. Le proxy est l'interface utilisateur de Looker.
Dans les deux cas, vous devez spécifier le point de terminaison de l'API externe dans l'autorisation external_api_urls
de l'extension.
Proxy de serveur
L'exemple suivant montre comment utiliser le proxy du serveur pour obtenir un jeton d'accès à utiliser par le proxy de récupération. L'ID client et le secret doivent être définis comme attributs utilisateur pour l'extension. En règle générale, lorsque l'attribut utilisateur est configuré, la valeur par défaut est définie sur l'ID client ou le secret.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
const requestBody = {
client_id: extensionSDK.createSecretKeyTag('my_client_id'),
client_secret: extensionSDK.createSecretKeyTag('my_client_secret'),
},
try {
const response = await extensionSDK.serverProxy(
'https://myaccesstokenserver.com/access_token',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
}
)
const { access_token, expiry_date } = response.body
. . .
} catch (error) {
// Error handling
. . .
}
Le nom de l'attribut utilisateur doit être mappé à l'extension. Les tirets doivent être remplacés par des traits de soulignement, et les caractères ::
doivent être remplacés par un seul trait de soulignement.
Par exemple, si le nom de votre extension est my-extension::my-extension
, les attributs utilisateur à définir pour l'exemple précédent sont les suivants:
my_extension_my_extension_my_client_id
my_extension_my_extension_'my_client_secret'
Proxy de récupération
L'exemple suivant illustre l'utilisation du proxy de récupération. Il utilise le jeton d'accès de l'exemple de proxy de serveur précédent.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
try {
const response = await extensionSDK.fetchProxy(
'https://myaccesstokenserver.com/myendpoint',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
some_value: someValue,
another_value: anotherValue,
}),
}
)
// Handle success
. . .
} catch (error) {
// Handle failure
. . .
}
Intégration OAuth
Le framework d'extension est compatible avec l'intégration de fournisseurs OAuth. OAuth permet d'obtenir un jeton d'accès pour accéder à une ressource spécifique, par exemple un document Google Sheets.
Vous devez spécifier le point de terminaison du serveur OAuth dans le droit d'accès extension oauth2_urls
. Vous devrez peut-être également spécifier des URL supplémentaires dans le droit d'accès external_api_urls
.
Les frameworks d'extension sont compatibles avec les flux suivants:
- Flux implicite
- Type d'attribution du code d'autorisation avec clé secrète
- Défi et vérificateur de code PKCE
Le parcours général consiste à ouvrir une fenêtre enfant qui charge une page de serveur OAuth. Le serveur OAuth authentifie l'utilisateur et le redirige vers le serveur Looker avec des informations supplémentaires permettant d'obtenir un jeton d'accès.
Flux implicite:
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
const response = await extensionSDK.oauth2Authenticate(
'https://accounts.google.com/o/oauth2/v2/auth',
{
client_id: GOOGLE_CLIENT_ID!,
scope: GOOGLE_SCOPES,
response_type: 'token',
}
)
const { access_token, expires_in } = response
Type d'attribution du code d'autorisation avec clé secrète:
const authenticateParameters: Record<string, string> = {
client_id: GITHUB_CLIENT_ID!,
response_type: 'code',
}
const response = await extensionSDK.oauth2Authenticate(
'https://github.com/login/oauth/authorize',
authenticateParameters,
'GET'
)
const exchangeParameters: Record<string, string> = {
client_id: GITHUB_CLIENT_ID!,
code: response.code,
client_secret: extensionSDK.createSecretKeyTag('github_secret_key'),
}
const codeExchangeResponse = await extensionSDK.oauth2ExchangeCodeForToken(
'https://github.com/login/oauth/access_token',
exchangeParameters
)
const { access_token, error_description } = codeExchangeResponse
Défi et vérificateur de code PKCE:
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
const authRequest: Record<string, string> = {
client_id: AUTH0_CLIENT_ID!,
response_type: 'code',
scope: AUTH0_SCOPES,
code_challenge_method: 'S256',
}
const response = await extensionSDK.oauth2Authenticate(
'https://sampleoauthserver.com/authorize',
authRequest,
'GET'
)
const exchangeRequest: Record<string, string> = {
grant_type: 'authorization_code',
client_id: AUTH0_CLIENT_ID!,
code: response.code,
}
const codeExchangeResponse = await extensionSDK.oauth2ExchangeCodeForToken(
'https://sampleoauthserver.com/login/oauth/token',
exchangeRequest
)
const { access_token, expires_in } = codeExchangeResponse
Spartan
Spartan fait référence à une méthode d'utilisation de l'instance Looker comme environnement pour exposer des extensions, et uniquement des extensions, à un ensemble d'utilisateurs désignés. Lorsqu'un utilisateur Spartan accède à une instance Looker, le flux de connexion configuré par l'administrateur Looker s'affiche. Une fois l'utilisateur authentifié, une extension lui est présentée en fonction de son attribut utilisateur landing_page
, comme illustré ci-dessous. L'utilisateur ne peut accéder qu'aux extensions. Il ne peut accéder à aucune autre partie de Looker. Si l'utilisateur a accès à plusieurs extensions, celles-ci contrôlent sa capacité à accéder aux autres extensions à l'aide de extensionSDK.updateLocation
. Il existe une méthode spécifique du SDK Looker Extension permettant à l'utilisateur de se déconnecter de l'instance Looker.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
// Navigate to another extension
extensionSDK.updateLocation('/spartan/another::extension')
. . .
// Logout
extensionSDK.spartanLogout()
Définir les utilisateurs spartiates
Pour définir un utilisateur spartan, vous devez créer un groupe appelé "Extensions uniquement".
Une fois le groupe "Extensions uniquement" créé, accédez à la page Attributs utilisateur dans la section Administration de Looker, puis modifiez l'attribut utilisateur landing_page
. Sélectionnez l'onglet Grouper les valeurs, puis ajoutez le groupe "Extensions uniquement". La valeur doit être définie sur /spartan/my_extension::my_extension/
, où my_extension::my_extension
est l'ID de votre extension. Désormais, lorsque cet utilisateur se connectera, il sera redirigé vers l'extension désignée.
Fractionnement du code
La technique de fractionnement du code consiste à ne demander le code que lorsqu'il est nécessaire. En règle générale, les blocs de code sont associés aux routes React, où chaque route reçoit son propre bloc de code. Dans React, cela se fait avec les composants Suspense
et React.lazy
. Le composant Suspense
affiche un composant de remplacement pendant le chargement du bloc de code. React.lazy
est chargé de charger le bloc de code.
Configurer le fractionnement du code:
import { AsyncComp1 as Comp1 } from './Comp1.async'
import { AsyncComp1 as Comp2 } from './Comp2.async'
. . .
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/comp1">
<Comp1 />
</Route>
<Route path="/comp2">
<Comp2 />
</Route>
</Switch>
<Suspense>
Le composant à chargement différé est implémenté comme suit:
import { lazy } from 'react'
const Comp1 = lazy(
async () => import(/* webpackChunkName: "comp1" */ './Comp1')
)
export const AsyncComp1 = () => <Home />
Le composant est implémenté comme suit. Le composant doit être exporté en tant que composant par défaut:
const Comp1 = () => {
return (
<div>Hello World</div>
)
}
export default Comp1
Agiter un arbre
Bien que les SDK Looker soient actuellement compatibles avec le tree-shaking, cette fonctionnalité doit encore être améliorée. Nous modifions en permanence nos SDK pour améliorer la prise en charge du tree shaking. Certaines de ces modifications peuvent nécessiter que vous refactoriez votre code pour en profiter, mais cela sera indiqué dans les notes de version si nécessaire.
Pour utiliser le tree-shaking, le module que vous utilisez doit être exporté en tant qu'esmodule, et les fonctions que vous importez doivent être exemptes d'effets secondaires. Le SDK Looker pour TypeScript/Javascript, la bibliothèque Looker SDK Runtime, les composants d'interface utilisateur Looker, le SDK Looker Extension et le SDK Extension pour React répondent tous à ces exigences.
Dans une extension, utilisez le SDK Looker 4.0 et le composant ExtensionProvider2
ou ExtensionProvider40
du SDK d'extension pour React.
Le code suivant configure le fournisseur d'extensions. Vous devrez indiquer au fournisseur le SDK souhaité:
import { MyExtension } from './MyExtension'
import { ExtensionProvider40 } from '@looker/extension-sdk-react'
import { Looker40SDK } from '@looker/sdk/lib/4.0/methods'
import { hot } from 'react-hot-loader/root'
export const App = hot(() => {
return (
<ExtensionProvider2 type={Looker40SDK}>
<MyExtension />
</ExtensionProvider2>
)
})
N'utilisez pas le style d'importation suivant dans votre extension:
import * as lookerComponents from `@looker/components`
L'exemple précédent importe tout le contenu du module. Importez plutôt uniquement les composants dont vous avez réellement besoin. Exemple :
import { Paragraph } from `@looker/components`
Glossaire
- Division du code : technique permettant de charger JavaScript de manière différée jusqu'à ce qu'il soit réellement nécessaire. Idéalement, vous devez réduire au maximum le bundle JavaScript chargé initialement. Pour ce faire, vous pouvez utiliser le fractionnement du code. Toute fonctionnalité qui n'est pas immédiatement requise n'est pas chargée tant qu'elle n'est pas réellement nécessaire.
- IDE (environnement de développement intégré) Éditeur permettant de créer et de modifier une extension. Par exemple, Visual Studio Code, IntelliJ et WebStorm.
- Scène : généralement une page vue dans Looker. Les scènes sont mappées sur les principales routes. Il peut arriver qu'une scène comporte des scènes enfants qui correspondent à des sous-itinéraires dans l'itinéraire principal.
- Transcompilation : processus consistant à transformer un code source écrit dans un langage en un autre langage ayant un niveau d'abstraction similaire. Par exemple, TypeScript vers JavaScript.