Ejemplos de código JavaScript y de React del framework de extensión

En esta página, se proporcionan ejemplos de código escritos en React y JavaScript para funciones comunes que puedes usar en tus extensiones.

Usa el SDK de la 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 de la extensión de Looker y la API del SDK a las extensiones. Desde que se creó el framework de extensiones, se crearon diferentes versiones del proveedor de extensiones. 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 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. Esto se creó porque no tenía sentido que una extensión usara ambos SDKs y obligara al desarrollador a elegir uno u 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 SDK 4.0 es la única versión disponible. Como el SDK 3.1 no está incluido en el paquete final, tiene la ventaja de reducir el tamaño del paquete.

Para agregar funciones desde el SDK de la extensión de Looker, primero debes obtener una referencia al SDK, lo que se puede hacer a través del proveedor o de manera global. Luego, podrás llamar a las funciones del SDK como lo harías con 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 manera global (la extensión debe inicializarse antes de que se llame), 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
      . . .
    }
}

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')

Abrir una nueva ventana del navegador

Dado que la extensión se ejecuta en un iframe de 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 los iframes de la zona de pruebas para la versión no basada en Chromium del navegador Microsoft Edge.

Si usas MemoryRouter y react-router en tu extensión, el framework de extensiones sincronizará automáticamente el router de tu extensión con el router del host de Looker. Esto significa que la extensión recibirá una notificación de los clics en los botones hacia atrás y 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 vínculos directos. Consulta los ejemplos de extensión para usar react-router.

Datos del 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 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 la última escritura prevalece. Los datos de contexto están disponibles para la extensión inmediatamente 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. Hay dos tipos de acceso a atributos de usuario:

  • Con alcance: Se asocia con la extensión. Un atributo de usuario con permiso tiene un espacio de nombres para la extensión, y el atributo de usuario se debe definir en la instancia de Looker antes de poder usarlo. Para asignar un espacio de nombres a un atributo de usuario, prefija el nombre del atributo con el nombre de la extensión. Los guiones y los caracteres '::' del nombre de la extensión deben reemplazarse 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 específico llamado my_value que se use con el ID de extensión my-extension::my-extension debe tener definido el nombre de atributo de usuario my_extension_my_extension_my_value. Una vez definido, la extensión puede leer y actualizar el atributo de usuario.

  • Global: Estos son atributos de usuario globales 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á para los atributos de usuario globales. El valor guardado solo es visible para el usuario actual.
  • userAttributeResetItem: Restablece un atributo de usuario para el 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 de 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 extensiones 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 en comparación con la API de almacenamiento local del navegador síncrono.

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 la 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, vistas 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 incorporación 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 (&#60;EmbedContainer ref={embedCtrRef} /&#62;)

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 demuestra el uso del proxy de servidor para obtener un token de acceso para que lo use 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 de 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 de usuario se debe asignar a la extensión. Los guiones se deben reemplazar por guiones bajos y los caracteres :: se deben reemplazar 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. Utiliza el token de acceso del ejemplo anterior del proxy de servidor.

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 y acceder a un recurso en particular, como 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
  • Desafío y verificador del código de la PKCE

El flujo general consiste en 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&#60;string, string&#62; = {
    client_id: GITHUB_CLIENT_ID!,
    response_type: 'code',
  }
  const response = await extensionSDK.oauth2Authenticate(
    'https://github.com/login/oauth/authorize',
    authenticateParameters,
   'GET'
  )
  const exchangeParameters: Record&#60;string, string&#62; = {
    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 del código de la PKCE:

import { ExtensionContext40 } from '@looker/extension-sdk-react'

. . .

  const extensionContext = useContext(
    ExtensionContext40
  )
  const { extensionSDK } = extensionContext

. . .

  const authRequest: Record&#60;string, string&#62; = {
    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&#60;string, string&#62; = {
    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

Espartano

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 espartano que navega a una instancia de Looker verá el 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()

Define usuarios espartanos

Para definir un usuario espartano, debes crear un grupo llamado “Solo extensiones”.

Una vez que se haya creado el grupo “Solo extensiones”, navega a la página Atributos de usuario en la sección Administrador de Looker y edita el atributo de usuario landing_page. Selecciona la pestaña Group Values y agrega el grupo “Extensions Only”. El valor se debe establecer en /spartan/my_extension::my_extension/, en el que my_extension::my_extension es el ID de la extensión. Ahora, cuando el usuario acceda, será enrutado 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 se necesita. Normalmente, los bloques de códigos se asocian con rutas de React donde cada ruta obtiene su propio bloque 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 bloque de código. React.lazy es responsable de cargar el bloque 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 = () => &#60;Home />

El componente se implementa de la siguiente manera: El componente debe exportarse como componente predeterminado:

const Comp1 = () => {
  return (
    &#60;div&#62;Hello World&#60;/div&#62;
  )
}

export default Comp1

Movimiento de árboles

Aunque los SDK de Looker actualmente admiten la eliminación de código no utilizado, esta función aún debe mejorarse. 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 la extensión 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 (
    &#60;ExtensionProvider2 type={Looker40SDK}&#62;
      &#60;MyExtension /&#62;
    &#60;/ExtensionProvider2&#62;
  )
})

No utilices el siguiente estilo de importación en tu extensión:

import * as lookerComponents from `@looker/components`

El ejemplo anterior incluye todo lo del módulo. En su lugar, solo importa 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. 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.