En esta página, se proporcionan ejemplos de código escritos en React y JavaScript para funciones comunes que podrías querer usar en tus extensiones.
Usa el SDK de extensión de Looker
Las extensiones deben establecer una conexión con el host de Looker. En React, esto se hace uniendo la extensión en un componente ExtensionProvider40
. Este componente establece una conexión con el host de Looker y pone a disposición el SDK de la extensión de Looker y el SDK de Looker para la extensión.
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 y la API de la extensión de Looker a las extensiones. Se crearon diferentes versiones del proveedor de extensiones desde que se creó el framework de extensiones. En esta sección, se explica la historia de los proveedores de extensiones y por qué ExtensionProvider40 es el proveedor recomendado.
El primer proveedor de extensiones fue ExtensionProvider
, que expuso los SDKs de Looker, las versiones 3.1 y 4.0. La desventaja era que incluir ambos SDK aumentaba el tamaño del paquete de producción final.
Luego, se creó ExtensionProvider2
. Se creó porque no tenía sentido que una extensión usara ambos SDK y obligara al desarrollador a elegir uno o el otro. Lamentablemente, esto provocaba que ambos SDKs se incluyeran en el tamaño del paquete de producción final.
Cuando se transfirió el SDK 4.0 a Google Analytics, se creó ExtensionProvider40
. La ventaja de ExtensionProvider40
es que el desarrollador no tiene que elegir qué SDK usar, ya que el SDK 4.0 es la única versión disponible. Como el SDK 3.1 no se incluye en el paquete final, esto tiene la ventaja de reducir el tamaño del paquete.
Para agregar funciones del SDK de la extensión de Looker, primero debes obtener una referencia al SDK, lo que se puede hacer desde el proveedor o de forma global. Luego, puedes llamar a las funciones del SDK como lo harías en cualquier aplicación de JavaScript.
- Para acceder al SDK desde el 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 esta), sigue estos pasos:
const coreSDK = getCoreSDK()
Ahora puedes usar el SDK como lo harías en cualquier aplicación de JavaScript:
const GetLooks = async () => {
try {
const looks = await sdk.ok(sdk.all_looks('id'))
// process looks
. . .
} catch (error) {
// do error handling
. . .
}
}
Navegar a otro lugar de la instancia de Looker
Dado que la extensión se ejecuta en un iframe de zona de pruebas, no puedes navegar a ningún otro lugar dentro de la instancia de Looker actualizando el objeto window.location
del elemento superior. Es posible navegar 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')
Cómo abrir una ventana nueva del navegador
Dado que la extensión se ejecuta en un iframe con zona de pruebas, no puedes usar la ventana superior para abrir una nueva ventana del navegador. Es posible abrir una ventana del navegador con el SDK de Looker Extension.
Esta función requiere el derecho new_window
para abrir una ventana nueva a una ubicación en la instancia actual de Looker o el derecho new_window_external_urls
para abrir una ventana nueva que se ejecuta en un host diferente.
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 vinculación directa
Lo siguiente se aplica a las extensiones basadas en React.
Los componentes ExtensionProvider
, ExtensionProvider2
y ExtensionProvider40
crean automáticamente un router de React llamado MemoryRouter
para que lo uses. No intentes crear un BrowserRouter
, ya que no funciona en los iframes de la zona de pruebas. No intentes crear un HashRouter
, ya que no funciona en iframes de zona de pruebas para la versión del navegador Microsoft Edge que no está basada en Chromium.
Si se usa 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 host de Looker. Esto significa que la extensión recibirá notificaciones sobre los clics en los botones Atrás y Adelante del navegador y sobre 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 vínculos directos. Consulta los ejemplos de extensión para saber cómo usar react-router
.
Datos de contexto de la extensión
Los datos del contexto del framework de la extensión no deben confundirse con los contextos de React.
Las extensiones tienen la capacidad de 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 la última escritura prevalece. Los datos de contexto están disponibles para la extensión de inmediato después del inicio. El SDK de la extensión de Looker proporciona funciones para permitir que los datos de contexto se actualicen.
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 se deberá tener en cuenta si usas datos de contexto para 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 la extensión de Looker proporciona una API para acceder a los atributos del usuario de Looker. Existen dos tipos de acceso a los atributos del usuario:
Con alcance: Se asocia con la extensión. Un atributo de usuario con permiso se asigna a un espacio de nombres de la extensión y se debe definir en la instancia de Looker antes de poder usarlo. Para asignar un espacio de nombres a un atributo de usuario, antepón el nombre de la extensión al nombre del atributo. Cualquier guion y el signo '::' Los caracteres en el nombre de la extensión deben reemplazarse por un guion bajo, ya que estos guiones y dos puntos no se pueden usar en los nombres de los atributos de usuario.
Por ejemplo, un atributo de usuario específico llamado
my_value
que se use con el ID de extensiónmy-extension::my-extension
debe tener definido el nombre de atributo de usuariomy_extension_my_extension_my_value
. Una vez definido, la extensión puede leer y actualizar el atributo de usuario.Globales: Son atributos globales del usuario y son de solo lectura. Un ejemplo es el atributo de usuario
locale
.
La siguiente es 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 un valor de atributo de usuario para el usuario.userAttributeSetItem
: Guarda un atributo de usuario para el usuario actual. Fallará en los atributos de usuario globales. Solo el usuario actual puede ver el valor guardado.userAttributeResetItem
: Restablece un atributo del usuario actual al valor predeterminado. Fallará para los atributos de usuario globales.
Para acceder a los atributos del 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, debes agregar 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 de la zona de pruebas no permiten el acceso al almacenamiento local del navegador. El SDK de la extensión de Looker permite que una extensión lea y escriba en el almacenamiento local de la ventana superior. El almacenamiento local tiene un espacio de nombres para la extensión, lo que significa que no puede leer el almacenamiento local creado por la ventana superior ni otras extensiones.
Para usar el almacenamiento local, se requiere la autorización local_storage
.
La API de localhost de la extensión es asíncrona, a diferencia de la API de almacenamiento local síncrona del navegador.
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')
Cómo actualizar el título de la página
Es posible que las extensiones actualicen el título actual de la página. No se requieren 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')
Escribe en el portapapeles del sistema
Los iframes de zona de pruebas no permiten el acceso 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 desde 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) {
. . .
}
Incorporación de paneles, vistas y exploraciones
El framework de extensiones admite la incorporación de paneles, looks y exploraciones.
El derecho use_embeds
es obligatorio. Te recomendamos que uses el SDK de incorporación de JavaScript de Looker para incorporar contenido. Consulta la documentación del SDK de Embed para obtener más informació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} />)
Los ejemplos de extensión usan componentes con estilo para proporcionar un estilo simple al iframe generado. Por ejemplo:
import styled from "styled-components"
export const EmbedContainer = styled.div`
width: 100%;
height: 95vh;
& > iframe {
width: 100%;
height: 100%;
}
Accede a extremos de API externos
El framework de extensiones proporciona dos métodos para acceder a extremos de API externos:
- El proxy del servidor: Accede al extremo a través del servidor de Looker. Este mecanismo permite que el servidor de Looker configure los IDs de cliente y las claves secretas de forma segura.
- El proxy de recuperación: Accede al extremo desde el navegador del usuario. El proxy es la IU de Looker.
En ambos casos, debes especificar el extremo de API externo en el derecho external_api_urls
de la extensión.
Proxy del servidor
En el siguiente ejemplo, se muestra el uso del proxy del servidor para obtener un token de acceso que usará el proxy de recuperación. El ID de cliente y el secreto deben definirse como atributos de usuario para la extensión. Por lo general, cuando se configura el atributo del usuario, el valor predeterminado se establece en el ID o secreto del 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 del usuario debe asignarse a la extensión. Los guiones deben reemplazarse por guiones bajos, y los caracteres ::
deben reemplazarse por un solo guion bajo.
Por ejemplo, si el nombre de tu extensión es my-extension::my-extension
, los atributos de usuario que deben definirse para el ejemplo anterior serían los siguientes:
my_extension_my_extension_my_client_id
my_extension_my_extension_'my_client_secret'
Recuperar proxy
En el siguiente ejemplo, se muestra el uso del proxy de recuperació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 con 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 específico, por ejemplo, un documento de Hojas de cálculo de Google.
Deberás especificar el extremo del servidor de OAuth en el derecho extension oauth2_urls
. Es posible que también debas especificar URLs adicionales en el derecho external_api_urls
.
Los frameworks de extensión admiten los siguientes flujos:
- Flujo implícito
- Tipo de otorgamiento de código de autorización con clave secreta
- Verificador y desafío de código de PKCE
El flujo general es que se abre una ventana secundaria que carga una página del servidor de OAuth. El servidor de OAuth autentica al usuario y lo redirecciona de vuelta al servidor de Looker con detalles adicionales que pueden usarse 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 otorgamiento 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
Desafío y verificador de códigos 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 hace referencia a un método en el que se usa la instancia de Looker como entorno para exponer extensiones, y solo las extensiones, a un conjunto designado de usuarios. A un usuario Spartan que navegue a una instancia de Looker se le presentará cualquier flujo de acceso que haya configurado el administrador de Looker. Una vez que se autentique el usuario, se le presentará una extensión de acuerdo con su atributo de usuario landing_page
, 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 la capacidad del usuario para navegar a las otras extensiones con extensionSDK.updateLocation
. Hay un método específico del SDK de la extensión de Looker para permitir que el usuario salga de 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()
Definición de usuarios espartanos
Para definir un usuario Spartan, debes crear un grupo llamado "Solo extensiones".
Una vez que se haya creado el grupo "Solo extensiones", navega a la página Atributos del usuario en la sección Administrador de Looker y edita el atributo del usuario landing_page
. Selecciona la pestaña Valores del grupo y agrega el grupo "Solo extensiones". El valor se debe establecer en /spartan/my_extension::my_extension/
, donde my_extension::my_extension
es el ID de tu extensión. Ahora, cuando ese usuario acceda, se lo redireccionará a la extensión designada.
División de código
La división de código es la técnica en la que se solicita código solo cuando es necesario. Por lo general, los fragmentos de código se asocian con rutas de React, en las que cada ruta obtiene 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 resguardo mientras se carga el fragmento de código. React.lazy
es responsable de cargar el fragmento de código.
Configuración de la división de código:
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 de carga 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
Eliminación de código no utilizado
Aunque los SDK de Looker actualmente admiten el desprendimiento de árboles, esta función aún necesita mejoras. Modificamos continuamente nuestros SDK 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 aprovecharlo, pero cuando sea necesario, se documentarán en las notas de la versión.
Para utilizar la eliminación de código no utilizado, el módulo que uses debe exportarse como esmodule y las funciones que importes no deben tener efectos secundarios. El SDK de Looker para TypeScript/JavaScript, la biblioteca del entorno de ejecución del SDK de Looker, los componentes de la IU de Looker, el SDK de extensiones de Looker y el SDK de extensiones para React cumplen con 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 extensiones. Deberás indicarle al proveedor qué SDK deseas:
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 utilices el siguiente estilo de importación en tu extensión:
import * as lookerComponents from `@looker/components`
El ejemplo anterior incluye todo del módulo. En su lugar, importa solo los componentes que realmente necesitas. Por ejemplo:
import { Paragraph } from `@looker/components`
Glosario
- División de código: Es una técnica para la carga diferida de JavaScript hasta que sea realmente necesaria. Lo ideal es que el paquete de JavaScript cargado inicialmente sea lo más pequeño posible. Esto se puede lograr con la división de código. Cualquier funcionalidad que no se requiera de inmediato no se carga hasta que se la necesita.
- IDE: Entorno de desarrollo integrado. Es un editor que se usa para crear y modificar una extensión. Algunos ejemplos son Visual Studio Code, IntelliJ y WebStorm.
- Escena: Por lo general, una vista de página en Looker. Las escenas se asignan a las rutas principales. A veces, una escena incluye escenas secundarias que se asignan a rutas secundarias dentro de la ruta principal.
- Transpilar: Es el proceso de tomar el código fuente escrito en un lenguaje y transformarlo en otro que tenga un nivel similar de abstracción. Un ejemplo es TypeScript a JavaScript.