En esta página se proporcionan ejemplos de código escritos en React y JavaScript para funciones habituales que puede utilizar en sus extensiones.
Usar el SDK de extensiones de Looker
Las extensiones deben establecer una conexión con el host de Looker. En React, esto se hace envolviendo la extensión en un componente ExtensionProvider40
. Este componente establece una conexión con el host de Looker y pone a disposición de la extensión el SDK de extensiones de Looker y el SDK de 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>
)
}
Información general sobre los proveedores de extensiones
Los proveedores de extensiones exponen el SDK de extensiones de Looker y la API del SDK a las extensiones. Se han creado diferentes versiones del proveedor de la extensión desde que se creó el framework de la extensión. En esta sección se explica el historial de los proveedores de extensiones y por qué ExtensionProvider40 es el proveedor recomendado.
El primer proveedor de extensiones fue ExtensionProvider
, que exponía los SDKs de Looker, versiones 3.1 y 4.0. El inconveniente era que, al incluir ambos SDKs, aumentaba el tamaño del paquete de producción final.
Después, se creó ExtensionProvider2
. Se ha creado porque no tenía sentido que una extensión usara ambos SDKs y obligara al desarrollador a elegir uno u otro. Desafortunadamente, esto seguía provocando que ambos SDKs se incluyeran en el tamaño del paquete de producción final.
Cuando el SDK 4.0 pasó a estar disponible de forma general, se creó ExtensionProvider40
. La ventaja de ExtensionProvider40
es que el desarrollador no tiene que elegir qué SDK usar, ya que la versión 4.0 es la única disponible. Como el SDK 3.1 no se incluye en el paquete final, se reduce el tamaño del paquete.
Para añadir funciones del SDK de extensiones de Looker, primero debes obtener una referencia al SDK, lo que puedes hacer desde el proveedor o de forma global. Después, puedes llamar a las funciones del SDK como lo harías en cualquier aplicación JavaScript.
- Para acceder al SDK del proveedor, sigue estos pasos:
import { ExtensionContext40 } from '@looker/extension-sdk-react'
export const Comp1 = () => {
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK, coreSDK } = extensionContext
- Para acceder al SDK de forma global (la extensión debe inicializarse antes de llamar a este método), sigue estos pasos:
const coreSDK = getCoreSDK()
Ahora puedes usar el SDK como lo harías en cualquier aplicación JavaScript:
const GetLooks = async () => {
try {
const looks = await sdk.ok(sdk.all_looks('id'))
// process looks
. . .
} catch (error) {
// do error handling
. . .
}
}
Desplazarse a otra parte de la instancia de Looker
Como la extensión se ejecuta en un iframe aislado, no puedes desplazarte a otra parte de la instancia de Looker actualizando el objeto window.location
del elemento superior. Es posible desplazarse con el SDK de extensiones de Looker.
Esta función requiere el derecho navigation
.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
extensionSDK.updateLocation('/browse')
Abrir una nueva ventana del navegador
Como la extensión se ejecuta en un iframe aislado, no puedes usar la ventana principal para abrir una nueva ventana del navegador. Puedes abrir una ventana del navegador con el SDK de extensiones de Looker.
Esta función requiere el derecho new_window
para abrir una ventana nueva en una ubicación de la instancia de Looker actual o el derecho new_window_external_urls
para abrir una ventana nueva que se ejecute en otro host.
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')
Enrutamiento y enlaces profundos
Esto se aplica a las extensiones basadas en React.
Los componentes ExtensionProvider
, ExtensionProvider2
y ExtensionProvider40
crean automáticamente un enrutador de React llamado MemoryRouter
para que lo uses. No intentes crear un BrowserRouter
, ya que no funciona en iframes en entornos aislados. No intentes crear un HashRouter
, ya que no funciona en iframes en entornos aislados para la versión no basada en Chromium del navegador Microsoft Edge.
Si se utiliza MemoryRouter
y usas react-router
en tu extensión, el framework de la extensión sincronizará automáticamente el router de tu extensión con el router del host de Looker. Esto significa que la extensión recibirá notificaciones cuando se haga clic en los botones para ir hacia atrás y hacia adelante del navegador, así como de la ruta actual cuando se vuelva a cargar la página. Esto también significa que la extensión debería admitir automáticamente los enlaces profundos. Consulta los ejemplos de extensiones para saber cómo usar react-router
.
Datos de contexto de la extensión
Los datos de contexto del framework de extensiones no deben confundirse con los contextos de React.
Las extensiones pueden compartir datos de contexto entre todos los usuarios de una extensión. Los datos de contexto se pueden usar para datos que no cambian con frecuencia y que no tienen requisitos de seguridad especiales. Se debe tener cuidado al escribir los datos, ya que no hay bloqueo de datos y se aplica la última escritura. La extensión puede acceder a los datos de contexto inmediatamente al iniciarse. El SDK de extensiones de Looker proporciona funciones que permiten actualizar y volver a cargar los datos de contexto.
El tamaño máximo de los datos de contexto es de aproximadamente 16 MB. Los datos de contexto se serializarán en una cadena JSON, por lo que también debes tenerlo en cuenta si utilizas datos de contexto en tu extensión.
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()
Atributos de usuario
El SDK de extensiones de Looker proporciona una API para acceder a los atributos de usuario de Looker. Hay dos tipos de acceso a atributos de usuario:
Limitado: asociado a la extensión. Un atributo de usuario con ámbito tiene un espacio de nombres en la extensión y debe definirse en la instancia de Looker antes de poder usarse. Para asignar un espacio de nombres a un atributo de usuario, añade el nombre de la extensión al principio del nombre del atributo. Los guiones y los caracteres "::" del nombre de la extensión deben sustituirse por un guion bajo, ya que no se pueden usar guiones ni dos puntos en los nombres de los atributos de usuario.
Por ejemplo, un atributo de usuario con ámbito llamado
my_value
que se usa con un ID de extensiónmy-extension::my-extension
debe tener definido un nombre de atributo de usuariomy_extension_my_extension_my_value
. Una vez definido, la extensión puede leer y actualizar el atributo de usuario.Global: son atributos de usuario globales y son de solo lectura. Por ejemplo, el atributo de usuario
locale
.
A continuación, se muestra una lista de llamadas a la API de atributos de usuario:
userAttributeGetItem
: lee un atributo de usuario. Se puede definir un valor predeterminado que se usará si no existe ningún valor de atributo de usuario para el usuario.userAttributeSetItem
: guarda un atributo de usuario del usuario actual. Se producirá un error en los atributos de usuario globales. El valor guardado solo lo puede ver el usuario actual.userAttributeResetItem
: restablece el valor predeterminado de un atributo de usuario del usuario actual. Se producirá un error en los atributos de usuario globales.
Para acceder a los atributos de usuario, debes especificar los nombres de los atributos en los derechos global_user_attributes
o scoped_user_attributes
. Por ejemplo, en el archivo de manifiesto del proyecto de LookML, añadirías lo siguiente:
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')
Almacenamiento local
Los iframes en entornos aislados no permiten el acceso al almacenamiento local del navegador. El SDK de extensiones de Looker permite que una extensión lea y escriba en el almacenamiento local de la ventana principal. El almacenamiento local está en un espacio de nombres de la extensión, lo que significa que no puede leer el almacenamiento local creado por la ventana principal u otras extensiones.
Para usar el almacenamiento local, se necesita el derecho local_storage
.
La API localhost de la extensión es asíncrona, a diferencia de la API de almacenamiento local del navegador, que es síncrona.
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')
Actualizar el título de la página
Las extensiones pueden actualizar el título de la página actual. No se necesitan derechos para realizar esta acción.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
extensionSDK.updateTitle('My Extension Title')
Escribir en el portapapeles del sistema
Los iframes en entorno aislado no permiten acceder al portapapeles del sistema. El SDK de extensiones de Looker permite que una extensión escriba texto en el portapapeles del sistema. Por motivos de seguridad, la extensión no puede leer el portapapeles del sistema.
Para escribir en el portapapeles del sistema, necesitas el derecho 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) {
. . .
}
Insertar paneles de control, Looks y Exploraciones
El framework de extensiones permite insertar paneles, Looks y Exploraciones.
Se necesita el derecho use_embeds
. Te recomendamos que uses el SDK de JavaScript de Looker para insertar contenido. Para obtener más información, consulta la documentación del SDK de inserción.
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} />)
En los ejemplos de extensiones se usan componentes con estilos para proporcionar estilos sencillos al iframe generado. Por ejemplo:
import styled from "styled-components"
export const EmbedContainer = styled.div`
width: 100%;
height: 95vh;
& > iframe {
width: 100%;
height: 100%;
}
Acceder a endpoints de APIs externas
El framework de extensiones proporciona dos métodos para acceder a endpoints de APIs externas:
- Proxy del servidor: accede al endpoint a través del servidor de Looker. Este mecanismo permite que el servidor de Looker defina de forma segura los IDs de cliente y las claves secretas.
- Proxy de obtención: accede al endpoint desde el navegador del usuario. El proxy es la interfaz de usuario de Looker.
En ambos casos, debes especificar el endpoint de la API externa en el derecho external_api_urls
de la extensión.
Proxy de servidor
En el siguiente ejemplo se muestra el uso del proxy del servidor para obtener un token de acceso que pueda usar el proxy de obtención. El ID y el secreto de cliente deben definirse como atributos de usuario de la extensión. Normalmente, cuando se configura el atributo de usuario, el valor predeterminado es el ID o el secreto de cliente.
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
. . .
}
El nombre del atributo de usuario debe asignarse a la extensión. Los guiones deben sustituirse por guiones bajos y los caracteres ::
deben sustituirse por un solo guion bajo.
Por ejemplo, si el nombre de su extensión es my-extension::my-extension
, los atributos de usuario que deben definirse en el ejemplo anterior serían los siguientes:
my_extension_my_extension_my_client_id
my_extension_my_extension_'my_client_secret'
Obtener proxy
En el siguiente ejemplo se muestra el uso del proxy de obtención. Usa el token de acceso del ejemplo de proxy de servidor anterior.
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
. . .
}
Integración de OAuth
El framework de extensiones admite la integración con proveedores de OAuth. OAuth se puede usar para obtener un token de acceso a un recurso concreto, como un documento de Hojas de cálculo de Google.
Deberás especificar el endpoint del servidor OAuth en el derecho extension oauth2_urls
. También es posible que tengas que especificar URLs adicionales en el external_api_urls
derecho.
Los frameworks de extensiones admiten los siguientes flujos:
- Flujo implícito
- Tipo de autorización de código de autorización con clave secreta
- Challenge y verificador de código PKCE
El flujo general consiste en abrir una ventana secundaria que carga una página del servidor OAuth. El servidor de OAuth autentica al usuario y lo redirige al servidor de Looker con detalles adicionales que se pueden usar para obtener un token de acceso.
Flujo implícito:
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
Tipo de autorización de código de autorización con clave secreta:
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
Verificador y reto de código 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
Espartano hace referencia a un método para usar la instancia de Looker como un entorno para exponer extensiones (y solo extensiones) a un conjunto de usuarios concreto. Un usuario espartano que acceda a una instancia de Looker verá el flujo de inicio de sesión que haya configurado el administrador de Looker. Una vez que se haya autenticado el usuario, se le mostrará una extensión según su landing_page
atributo de usuario, como se muestra a continuación. El usuario solo puede acceder a las extensiones; no puede acceder a ninguna otra parte de Looker. Si el usuario tiene acceso a varias extensiones, estas controlan su capacidad para desplazarse a las demás extensiones mediante extensionSDK.updateLocation
. Hay un método específico del SDK de extensiones de Looker que permite al usuario cerrar sesión en la instancia de 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()
Definir usuarios espartanos
Para definir un usuario espartano, debes crear un grupo llamado "Extensions Only" (Solo extensiones).
Una vez que se haya creado el grupo "Solo extensiones", ve a la página Atributos de usuario de la sección Administrar de Looker y edita el atributo de usuario landing_page
. Seleccione la pestaña Valores de grupo y añada el grupo "Solo extensiones". El valor debe ser /spartan/my_extension::my_extension/
, donde my_extension::my_extension
es el ID de tu extensión. Ahora, cuando ese usuario inicie sesión, se le dirigirá a la extensión designada.
División del código
La división del código es una técnica que consiste en solicitar el código solo cuando es necesario. Normalmente, los fragmentos de código se asocian a rutas de React, donde cada ruta tiene su propio fragmento de código. En React, esto se hace con los componentes Suspense
y React.lazy
. El componente Suspense
muestra un componente de respaldo mientras se carga el fragmento de código. React.lazy
se encarga de cargar el fragmento de código.
Para configurar la división del código, sigue estos pasos:
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>
El componente cargado de forma diferida se implementa de la siguiente manera:
import { lazy } from 'react'
const Comp1 = lazy(
async () => import(/* webpackChunkName: "comp1" */ './Comp1')
)
export const AsyncComp1 = () => <Home />
El componente se implementa de la siguiente manera. El componente debe exportarse como componente predeterminado:
const Comp1 = () => {
return (
<div>Hello World</div>
)
}
export default Comp1
Sacudida del árbol
Aunque los SDKs de Looker admiten la eliminación de código innecesario, esta función aún necesita mejoras. Modificamos continuamente nuestros SDKs para mejorar la compatibilidad con la eliminación de código no utilizado. Es posible que algunos de estos cambios requieran que refactorices tu código para aprovecharlos, pero, si es necesario, se indicará en las notas de la versión.
Para utilizar la eliminación de código innecesario, el módulo que utilices debe exportarse como un módulo ES y las funciones que importes no deben tener efectos secundarios. El SDK de Looker para TypeScript/JavaScript, la biblioteca de tiempo de ejecución del SDK de Looker, los componentes de la interfaz de usuario de Looker, el SDK de extensiones de Looker y el SDK de extensiones para React cumplen estos requisitos.
En una extensión, usa el SDK de Looker 4.0 y el componente ExtensionProvider2
o ExtensionProvider40
del SDK de extensiones para React.
El siguiente código configura el proveedor de la extensión. Tendrás que indicar al proveedor qué SDK quieres:
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>
)
})
No utilice el siguiente estilo de importación en su extensión:
import * as lookerComponents from `@looker/components`
En el ejemplo anterior, se importa todo el contenido del módulo. En su lugar, importe solo los componentes que necesite. Por ejemplo:
import { Paragraph } from `@looker/components`
Glosario
- División del código: técnica para cargar JavaScript de forma diferida hasta que sea necesario. Lo ideal es que el paquete de JavaScript cargado inicialmente sea lo más pequeño posible. Para ello, se puede usar la división de código. Las funciones que no se necesiten de inmediato no se cargarán hasta que sean necesarias.
- IDE: entorno de desarrollo integrado. Editor que se usa para crear y modificar una extensión. Por ejemplo, Visual Studio Code, IntelliJ y WebStorm.
- Escena: por lo general, una vista de página en Looker. Las escenas se corresponden con las rutas principales. A veces, una escena tendrá escenas secundarias que se correspondan con subrutas dentro de la ruta principal.
- Transpilación: proceso que consiste en tomar el código fuente escrito en un lenguaje y transformarlo en otro lenguaje que tenga un nivel de abstracción similar. Por ejemplo, TypeScript a JavaScript.