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

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 extensiones 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 las versiones 3.1 y 4.0 de los SDK de Looker. 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 hacía que ambos SDK se incluyeran en el tamaño del paquete de producción final.

Cuando el SDK 4.0 pasó a DG, 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, esto 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 desde el proveedor o de forma global. Luego, podrás llamar a 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 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 otro lugar dentro de la instancia de Looker actualizando el objeto window.location 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 la extensión de Looker.

Esta función requiere el derecho new_window para abrir una ventana nueva en una ubicación en la instancia actual de Looker o el derecho new_window_external_urls para abrir una ventana nueva que se ejecute 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 iframes de zona de pruebas. No intentes crear un HashRouter, ya que no funciona en iframes de zona de pruebas para la versión no basada en Chromium del navegador Microsoft Edge.

Si se usa MemoryRouter y react-router en tu extensión, el framework de extensiones sincronizará automáticamente el router de la extensión con el router host de Looker. Esto significa que la extensión recibirá una notificación de los clics en el botón para retroceder y adelantar del navegador, y 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 la extensión para saber cómo usar react-router.

Datos del contexto de la extensión

Los datos del contexto del framework de la extensión no se deben confundir 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 gana. Los datos de contexto estará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 y 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 string JSON, por lo que también se debe 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 de usuario:

  • Alcance: Se asocia con la extensión. Un atributo de usuario con permiso tiene un espacio de nombres asignado a la extensión, y el atributo de usuario debe definirse en la instancia de Looker antes de poder usarlo. Para asignar el espacio de nombres a un atributo de usuario, debes anteponer el nombre de la extensión al nombre del atributo. 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 usa con un ID de extensión de my-extension::my-extension debe tener definido un nombre de atributo de usuario de my_extension_my_extension_my_value. Una vez definido, el atributo de usuario puede ser leído y actualizado por la extensión.

  • Globales: 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. Se generará un error con los atributos de usuario globales. Solo el usuario actual puede ver el valor guardado.
  • userAttributeResetItem: Restablece un atributo de usuario para el usuario actual al valor predeterminado. Se generará un error con 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, agrega 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 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 asignado a la extensión, lo que significa que no puede leer el almacenamiento local creado por la ventana superior o por otras extensiones.

Para usar el almacenamiento local, se requiere la derecho local_storage.

La API de localhost de la extensión es asíncrona a diferencia de 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 de la página actual. No se requieren autorizaciones para realizar esta acción.

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

. . .

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

  extensionSDK.updateTitle('My Extension Title')

Cómo escribir en el portapapeles del sistema

Los iframes en la zona de pruebas no permiten el acceso al portapapeles del sistema. El SDK de la extensión 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.

Se requiere el derecho use_embeds. 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 (&#60;EmbedContainer ref={embedCtrRef} /&#62;)

Los ejemplos de extensiones usan componentes con diseño 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 los extremos de la 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 de external_api_urls de la extensión.

Proxy de servidor

En el siguiente ejemplo, se muestra cómo usar el proxy de servidor para obtener un token de acceso que puede 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 de usuario, el valor predeterminado se establece en el ID o 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 reemplazarse por guiones bajos, y los caracteres ::, por un solo guion bajo.

Por ejemplo, si el nombre de tu extensión es my-extension::my-extension, los atributos de usuario que se deben definir 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 anterior del proxy del 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 utilizar para obtener un token de acceso para acceder a un recurso en particular, 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 extensiones 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 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 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 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 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

El término espartano se refiere a un método de uso de la instancia de Looker como entorno para exponer extensiones, y extensiones solo, a un conjunto designado de usuarios. Un usuario espartano que navegue a una instancia de Looker verá el flujo de acceso que haya configurado el administrador de Looker. Una vez que se autentique al usuario, se le mostrará una extensión según su atributo de usuario landing_page, como se muestra a continuación. El usuario solo puede acceder a las extensiones, no a ninguna otra parte de Looker. Si el usuario tiene acceso a varias extensiones, estas controlan su capacidad para navegar a las otras extensiones mediante extensionSDK.updateLocation. Hay un método específico del SDK de la extensión de Looker que permite 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 espartano, debe 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 Valores del grupo y agrega el grupo "Solo extensiones". 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, 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 bloques de códigos se asocian con rutas de React en las que 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 un componente predeterminado:

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

export default Comp1

Temblor de árboles

Aunque actualmente los SDK de Looker admiten la eliminación de código no utilizado, esta función aún necesita mejorarse. Modificamos nuestros SDKs de forma continua para mejorar la compatibilidad con la eliminación de código no utilizado. Algunos de estos cambios pueden requerir que refactorices tu código para aprovecharlos, pero cuando esto 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 un 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 la extensión de Looker y el SDK de la extensión para React cumplen con estos requisitos.

En una extensión, usa el SDK de Looker 4.0 y utiliza 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 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 (
    &#60;ExtensionProvider2 type={Looker40SDK}&#62;
      &#60;MyExtension /&#62;
    &#60;/ExtensionProvider2&#62;
  )
})

No use el siguiente estilo de importación en su extensión:

import * as lookerComponents from `@looker/components`

En el ejemplo anterior, se incluye todo lo que se vio en el módulo. En cambio, solo importa los componentes que realmente necesites. Por ejemplo:

import { Paragraph }  from `@looker/components`

Glosario

  • División de código: Es una técnica de carga diferida de JavaScript hasta que se la necesita. Lo ideal sería que el paquete de JavaScript cargado inicialmente sea lo más pequeño posible. Esto se puede lograr usando la división de código. Las funcionalidades que no se necesitan de inmediato no se cargan hasta que se necesitan realmente.
  • 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, es 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 subrutas de la ruta principal.
  • Transpilar: es el proceso de tomar código fuente escrito en un lenguaje y transformarlo en otro con un nivel de abstracción similar. Un ejemplo es TypeScript a JavaScript.