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 de l'extension Looker
Pour ajouter des fonctions à partir du SDK de l'extension Looker, vous devez d'abord obtenir une référence au SDK, que vous pouvez faire soit auprès du fournisseur, soit dans le monde entier. Vous pouvez ensuite appeler des fonctions du SDK comme vous le feriez dans n'importe quelle application JavaScript.
- Pour accéder au SDK à partir du fournisseur:
import { ExtensionContext2 } from '@looker/extension-sdk-react'
export const Comp1 = () => {
const extensionContext = useContext(
ExtensionContext2
)
const { extensionSDK, coreSDK } = extensionContext
- Pour accéder au SDK de manière globale (l'extension doit être initialisée avant que cela ne soit appelé):
const coreSDK = getCoreSDK2()
Vous pouvez à présent 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
. . .
}
}
Naviguer ailleurs dans l'instance Looker
Comme l'extension s'exécute dans un iFrame en bac à sable, vous ne pouvez pas naviguer ailleurs dans l'instance Looker en mettant à jour l'objet window.location
parent. Il est possible de naviguer à l'aide du SDK de l'extension Looker.
Cette fonction nécessite le droit navigation
.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
const { extensionSDK } = extensionContext
. . .
extensionSDK.updateLocation('/browse')
Ouvrir une nouvelle fenêtre du navigateur
Comme l'extension s'exécute dans un iFrame en bac à sable, vous ne pouvez pas utiliser la fenêtre parent pour ouvrir une nouvelle fenêtre de navigateur. Il est possible d'ouvrir une fenêtre de navigateur à l'aide du SDK de l'extension Looker.
Cette fonction nécessite le droit new_window
d'ouvrir une nouvelle fenêtre vers un emplacement de l'instance Looker actuelle, ou le droit new_window_external_urls
pour ouvrir une nouvelle fenêtre exécutée sur un autre hôte.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
const { extensionSDK } = extensionContext
. . .
extensionSDK.openBrowserWindow('/browse', '_blank')
. . .
extensionSDK.openBrowserWindow('https://docs.looker.com/reference/manifest-params/application#entitlements', '_blank')
Routage et liens profonds
Les informations ci-dessous s'appliquent aux extensions React.
Les composants ExtensionProvider
et ExtensionProvider2
créent automatiquement un routeur React appelé MemoryRouter
. N'essayez pas de créer un BrowserRouter
, car il ne fonctionne pas dans des iFrames en bac à sable. N'essayez pas de créer un HashRouter
, car il ne fonctionne pas dans des cadres iFrame en bac à sable pour la version du navigateur Microsoft Edge qui n'est pas basée sur Chromium.
Si vous utilisez MemoryRouter
et react-router
dans votre extension, le framework de l'extension synchronise automatiquement le routeur de votre extension avec le routeur hôte Looker. Cela signifie que l'extension sera avertie lorsque l'utilisateur cliquera sur le bouton "Précédent" et "Suivant" de l'itinéraire, et que l'itinéraire actuel sera actualisé. Cela signifie également que l'extension doit automatiquement prendre en charge les liens profonds. Consultez les exemples d'extensions pour savoir comment utiliser react-router
.
Données contextuelles de l'extension
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 présentent pas d'exigences de sécurité particulières. Vous devez faire attention lorsque vous écrivez les données, car le verrouillage des données et la dernière écriture l'emportent. Les données de contexte sont accessibles à l'extension dès le démarrage. Le SDK de l'extension Looker fournit des fonctions permettant de mettre à jour et d'actualiser les données contextuelles.
La taille maximale des données contextuelles est d'environ 16 Mo. Les données de contexte seront sérialisées en une chaîne JSON. Vous devez donc également en tenir compte si vous utilisez les données de contexte pour votre extension.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
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 de l'extension Looker fournit une API pour accéder aux attributs utilisateur Looker. Il existe deux types d'accès aux attributs utilisateur:
Portée : associée à l'extension. Un attribut utilisateur restreint s'applique à un espace de noms et l'attribut utilisateur doit être défini dans l'instance Looker avant de pouvoir être utilisé. Pour ajouter un espace de noms à un attribut utilisateur, ajoutez le nom de l'extension en tant que préfixe. Les traits d'union et les caractères ":" sont obligatoires dans le nom de l'extension. En effet, les tirets et les deux-points ne peuvent pas être utilisés dans les noms d'attributs.
Par exemple, un attribut utilisateur restreint nommé
my_value
et utilisé avec l'ID d'extensionmy-extension::my-extension
doit avoir le nom d'attribut utilisateurmy_extension_my_extension_my_value
défini. Une fois défini, l'extension peut lire et mettre à jour l'attribut utilisateur.Global : ces attributs utilisateur généraux sont en lecture seule. Par exemple, l'attribut utilisateur
locale
.
Voici la 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. Seul l'utilisateur actuel peut voir la valeur enregistrée.userAttributeResetItem
: rétablit la valeur par défaut d'un attribut pour l'utilisateur actuel. Échec pour les attributs utilisateur globaux.
Pour accéder aux attributs utilisateur, vous devez spécifier le nom des attributs dans les droits d'accès global_user_attributes
et/ou scoped_user_attributes
. Par exemple, dans le fichier manifeste de projet LookML, ajoutez:
entitlements: {
scoped_user_attributes: ["my_value"]
global_user_attributes: ["locale"]
}
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
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 ne permettent pas l'accès à l'espace de stockage local du navigateur. Le SDK de l'extension Looker permet à une extension de lire et d'écrire dans l'espace de stockage local de la fenêtre parent. L'espace de stockage local est associé à un espace de noms pour l'extension, ce qui signifie qu'il ne peut pas lire le stockage local créé par la fenêtre parente ou d'autres extensions.
L'utilisation du stockage local nécessite le droit local_storage
.
L'API localhost de l'extension est asynchrone plutôt que l'API de stockage en local du navigateur synchrone.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
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 mettre à jour le titre de la page actuelle. Aucun droit d'accès n'est requis pour effectuer cette action.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
const { extensionSDK } = extensionContext
extensionSDK.updateTitle('My Extension Title')
Écrire dans le presse-papiers du système
Les iFrames en bac à sable ne permettent pas l'accès au presse-papiers du système. Le SDK des extensions Looker 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 les données du presse-papiers.
Pour écrire dans le presse-papiers du système, vous devez disposer du droit use_clipboard
.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
const { extensionSDK } = extensionContext
// Write to system clipboard
try {
await extensionSDK.clipboardWrite(
'My interesting information'
)
. . .
} catch (error) {
. . .
}
Intégration de tableaux de bord, de styles et d'explorations
Le framework d'extension permet d'intégrer des tableaux de bord, des styles et des explorations.
Le droit use_embeds
est requis. Nous vous recommandons d'utiliser le SDK Looker Embed Embed pour intégrer du contenu. Pour en savoir plus, consultez la documentation du SDK Embed.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
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'extension utilisent des composants stylisés pour fournir un style simple à l'iFrame généré. Exemple :
import styled from "styled-components"
export const EmbedContainer = styled.div`
width: 100%;
height: 95vh;
& > iframe {
width: 100%;
height: 100%;
}
Accéder aux points de terminaison externes de l'API
Le framework d'extension propose deux méthodes pour accéder aux points de terminaison de l'API externe:
- Le proxy de serveur : accède au point de terminaison via le serveur Looker. Ce mécanisme permet de définir de manière sécurisée les ID client et les clés secrètes par le serveur Looker.
- Proxy de récupération : accède au point de terminaison depuis le 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 le droit external_api_urls
de l'extension.
Proxy de serveur
L'exemple suivant illustre l'utilisation du proxy du serveur afin d'obtenir un jeton d'accès pour le proxy d'extraction. L'ID client et le code secret doivent être définis comme des attributs utilisateur pour l'extension. En règle générale, lorsque l'attribut utilisateur est configuré, la valeur par défaut est l'ID client ou le code secret.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
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 ::
par un seul trait de soulignement.
Par exemple, si le nom de votre extension est my-extension::my-extension
, les attributs utilisateur qui doivent être définis pour l'exemple ci-dessus sont les suivants:
my_extension_my_extension_my_client_id
my_extension_my_extension_'my_client_secret'
Récupérer le proxy
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 { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
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 avec les fournisseurs OAuth. OAuth peut être utilisé pour obtenir un jeton d'accès permettant d'accéder à une ressource particulière, par exemple un document Sheets Goorgle.
Vous devrez spécifier le point de terminaison du serveur OAuth dans le droit extension oauth2_urls
. Vous devrez peut-être également spécifier des URL supplémentaires dans le droit external_api_urls
.
Les frameworks d'extension sont compatibles avec les flux suivants:
- Flux implicite
- Type d'attribution de code d'autorisation avec une clé secrète
- Défi et code du code PKCE
En général, une fenêtre enfant s'ouvre et charge une page de serveur OAuth. Le serveur OAuth authentifie l'utilisateur et le redirige vers le serveur Looker en fournissant des informations supplémentaires qui peuvent être utilisées pour obtenir un jeton d'accès.
Flux implicite:
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
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 de code d'autorisation avec une 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 code du code PKCE:
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
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
Le terme "spartan" fait référence à une méthode utilisant l'instance Looker comme environnement pour exposer des extensions (et des extensions uniquement) à un ensemble d'utilisateurs désigné. Un utilisateur sobre qui accède à une instance Looker affiche le flux de connexion configuré par l'administrateur Looker. Une fois l'utilisateur authentifié, une extension lui est présentée en fonction de son attribut utilisateur landing_page
, comme indiqué ci-dessous. L'utilisateur peut uniquement accéder aux extensions. Il ne peut accéder à aucune autre partie de Looker. Si l'utilisateur a accès à plusieurs extensions, celles-ci contrôlent la possibilité pour l'utilisateur d'accéder aux autres extensions à l'aide de extensionSDK.updateLocation
. Il existe une méthode spécifique pour le SDK de l'extension Looker, qui permet à l'utilisateur de se déconnecter de l'instance Looker.
import { ExtensionContext2 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext2
)
const { extensionSDK } = extensionContext
. . .
// Navigate to another extension
extensionSDK.updateLocation('/spartan/another::extension')
. . .
// Logout
extensionSDK.spartanLogout()
Définir des utilisateurs spartans
Pour définir un utilisateur sobre, 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 Valeurs de groupes, puis ajoutez le groupe "Extensions uniquement". La valeur doit être définie sur "/spartan/my_extension::my_extension/
", où my_extension::my_extension
correspond à l'ID de votre extension. À présent, lorsque cet utilisateur se connecte, il est redirigé vers l'extension désignée.
Diviser le code
La division du code est une technique qui ne requiert que du code lorsque cela est nécessaire. Généralement, les fragments de code sont associés à des routes React, où chaque route reçoit son propre fragment de code. Dans React, les composants Suspense
et React.lazy
sont utilisés. Le composant Suspense
affiche un composant de remplacement pendant le chargement du fragment de code. React.lazy
est responsable du chargement du fragment de code.
Configurer la division 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
Tremblement d'arbres
Les SDK Looker permettent de tree shaking, mais ils ne sont pas encore parfaits. Nous modifions en permanence nos SDK pour améliorer la prise en charge des tree shaking d'arborescence. Certaines de ces modifications peuvent nécessiter une refactorisation du code pour bénéficier, mais lorsque cela est nécessaire, elles sont documentées dans les notes de version.
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 dépourvues d'effets secondaires. C'est le cas, par exemple, du SDK Looker pour TypeScript/Javascript, de la bibliothèque d'exécution de SDK Looker, des composants de l'UI Looker, du SDK des extensions Looker et du SDK des extensions pour React.
Dans une extension, vous devez choisir l'un des SDK 3.1 ou 4.0 de Looker, puis utiliser le composant ExtensionProvider2
de l'extension SDK pour React. Si vous avez besoin des deux SDK, continuez à utiliser le composant ExtensionProvider
, mais sa taille finale va augmenter.
Le code suivant configure le fournisseur d'extension. Vous devez indiquer au fournisseur le SDK souhaité:
import { MyExtension } from './MyExtension'
import { ExtensionProvider2 } 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 ci-dessus intègre tout le contenu du module. Importez plutôt uniquement les composants dont vous avez réellement besoin. Exemple :
import { Paragraph } from `@looker/components`
Glossaire
- Répartition du code : technique de chargement différé de JavaScript jusqu'à ce que cela soit réellement nécessaire. Idéalement, vous devez réduire au maximum la taille du groupe JavaScript initialement chargé. Pour ce faire, utilisez la répartition du code. Les fonctionnalités qui ne sont pas immédiatement requises ne sont pas chargées tant qu'elles ne le sont pas réellement.
- IDE : environnement de développement intégré Éditeur utilisé pour créer et modifier une extension. Visual Studio Code, Intellij et WebStorm sont des exemples.
- Scène : généralement une page vue dans Looker. Les scènes correspondent aux principaux itinéraires. Il arrive qu'une scène comporte des scènes enfants mappées à des sous-itinéraires.
- Transpilation : processus consistant à transformer le code source écrit dans un langage et à le transformer dans un autre langage présentant un niveau d'abstraction similaire. TypeScript est l'exemple de JavaScript.