Ejemplos de código de React y JavaScript del framework de extensiones

El framework de la extensión de Looker pronto usará un nuevo mecanismo de carga. El nuevo cargador puede provocar errores cuando se cargan las extensiones existentes. Para obtener instrucciones sobre cómo probar las extensiones con el cargador nuevo antes de que se active oficialmente en los entornos de Looker, consulte el artículo Cómo probar el nuevo cargador de marco de trabajo de extensiones en el Centro de ayuda de Looker.

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

Para agregar funciones desde el SDK de extensiones de Looker, primero debe obtener una referencia al SDK, que se puede hacer desde el proveedor o de forma global. Luego, puedes llamar a las funciones del SDK como lo harías con cualquier aplicación de JavaScript.

  • Para acceder al SDK desde el proveedor:
  import { ExtensionContext2 } from '@looker/extension-sdk-react'

  export const Comp1 = () => {
    const extensionContext = useContext(
      ExtensionContext2
    )
    const { extensionSDK, coreSDK } = extensionContext
  • Para acceder al SDK de forma global (la extensión debe inicializarse antes de que se llame a ella):
    const coreSDK = getCoreSDK2()

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 si actualizas el objeto window.location principal. Es posible navegar con el SDK de extensiones de Looker.

Esta función requiere la autorización navigation.

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

. . .

  const extensionContext = useContext(
    ExtensionContext2
  )
  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 principal para abrir una nueva ventana del navegador. Es posible abrir una ventana del navegador con el SDK de extensiones de Looker.

Esta función requiere la autorización new_window para abrir una nueva ventana a una ubicación en la instancia actual de Looker o la autorización new_window_external_urls para abrir una nueva ventana que se ejecuta en un host diferente.

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

. . .

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

. . .
  extensionSDK.openBrowserWindow('/browse', '_blank')
. . .
  extensionSDK.openBrowserWindow('https://docs.looker.com/reference/manifest-params/application#entitlements', '_blank')

Enrutamiento y vínculos directos

Lo siguiente se aplica a las extensiones basadas en React.

Los componentes ExtensionProvider y ExtensionProvider2 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 una HashRouter, ya que no funciona en los iframes de la zona de pruebas para la versión del navegador Microsoft Edge que no se basa en Chromium.

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 se notificará a la extensión sobre los clics en los botones hacia atrás y hacia adelante del navegador y sobre la ruta actual cuando se vuelva a cargar la página. Esto también significa que la extensión debe admitir automáticamente los vínculos directos. Consulta los ejemplos de extensiones para usar react-router.

Datos contextuales de la extensión

Los datos de 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 contextuales 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 operación es exitosa. Los datos contextuales están disponibles para la extensión inmediatamente después del inicio. El SDK de extensiones de Looker proporciona funciones que permiten actualizar y actualizar los datos contextuales.

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 la extensión.

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

. . .

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

. . .

  // Get loaded context data. This will reflect any updates that have
  // been made by saveContextData.
  let context = await extensionSDK.getContextData()

. . .

  // Save context data to Looker server.
  context = await extensionSDK.saveContextData(context)

. . .

  // Refresh context data from Looker server.
  context = await extensionSDK.refreshContextData()

Atributos de usuario

El SDK de extensiones de Looker proporciona una API para acceder a los atributos de usuario de Looker. Existen dos tipos de acceso a los atributos de usuario:

  • Alcance: asociado con la extensión. Un atributo de usuario específico tiene un espacio de nombres para la extensión y el atributo de usuario debe definirse en la instancia de Looker antes de poder usarlo. Para asignar un espacio de nombres a un atributo de usuario, agrega el nombre del atributo como prefijo. Cualquier guion y los caracteres & &33;;

    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 un nombre de atributo de usuario de my_extension_my_extension_my_value definido. Una vez definida, la extensión puede leer y actualizar el atributo de usuario.

  • Global: Son atributos de usuario globales y son de solo lectura. Un ejemplo es 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 el usuario no tiene un valor de atributo.
  • userAttributeSetItem: Guarda un atributo de usuario para el usuario actual. Fallará para 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. Fallará para los atributos de usuario globales.

Para acceder a los atributos de usuario, debes especificar los nombres de los atributos en las autorizaciones global_user_attributes o scoped_user_attributes. Por ejemplo, en el archivo de manifiesto del proyecto de LookML, agregarías lo siguiente:

  entitlements: {
    scoped_user_attributes: ["my_value"]
    global_user_attributes: ["locale"]
  }
import { ExtensionContext2 } from '@looker/extension-sdk-react'

. . .

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

  // Read global user attribute
  const locale = await extensionSDK.userAttributeGetItem('locale')

  // Read scoped user attribute
  const value = await extensionSDK.userAttributeGetItem('my_value')

  // Update scoped user attribute
  const value = await extensionSDK.userAttributeSetItem('my_value', 'abcd1234')

  // Reset scoped user attribute
  const value = await extensionSDK.userAttributeResetItem('my_value')

Almacenamiento local

Los iframes de 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 principal. 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 por otras extensiones.

Para usar el almacenamiento local, se requiere la autorización local_storage.

La API de localhost de extensión es asíncrona en comparación con la API de almacenamiento local del navegador síncrono.

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

. . .

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

  // Read from local storage
  const value = await extensionSDK.localStorageGetItem('my_storage')

  // Write to local storage
  await extensionSDK.localStorageSetItem('my_storage', 'abcedefh')

  // Delete item from local storage
  await extensionSDK.localStorageRemoveItem('my_storage')

Actualizando el título de la página

Las extensiones pueden actualizar el título actual de la página. No se requieren autorizaciones para realizar esta acción.

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

. . .

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

  extensionSDK.updateTitle('My Extension Title')

Cómo escribir 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 razones de seguridad, no se permite leer la extensión desde el portapapeles del sistema.

Para escribir en el portapapeles del sistema, necesitas la autorización de use_clipboard.

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

. . .

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

    // Write to system clipboard
    try {
      await extensionSDK.clipboardWrite(
        'My interesting information'
      )
      . . .
    } catch (error) {
      . . .
    }

Incorporación de paneles, apariencias y exploraciones

El marco de trabajo de extensiones admite la incorporación de paneles, apariencias y exploraciones. Los paneles normales y los heredados se pueden incorporar.

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 sobre el SDK de incorporación para obtener más información.

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

. . .

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

. . .

  const canceller = (event: any) => {
    return { cancel: !event.modal }
  }

  const updateRunButton = (running: boolean) => {
    setRunning(running)
  }

  const setupDashboard = (dashboard: LookerEmbedDashboard) => {
    setDashboard(dashboard)
  }

  const embedCtrRef = useCallback(
    (el) => {
      const hostUrl = extensionContext?.extensionSDK?.lookerHostData?.hostUrl
      if (el && hostUrl) {
        el.innerHTML = ''
        LookerEmbedSDK.init(hostUrl)
        const db = LookerEmbedSDK.createDashboardWithId(id as number)
          .withNext()
          .appendTo(el)
          .on('dashboard:loaded', updateRunButton.bind(null, false))
          .on('dashboard:run:start', updateRunButton.bind(null, true))
          .on('dashboard:run:complete', updateRunButton.bind(null, false))
          .on('drillmenu:click', canceller)
          .on('drillmodal:explore', canceller)
          .on('dashboard:tile:explore', canceller)
          .on('dashboard:tile:view', canceller)
          .build()
          .connect()
          .then(setupDashboard)
          .catch((error: Error) => {
            console.error('Connection error', error)
          })
      }
    },
    []
  )

  return (<EmbedContainer ref={embedCtrRef} />)

Los ejemplos de extensiones usan componentes con estilo para proporcionar un estilo sencillo 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 la API externa

El framework de la extensión proporciona dos métodos para acceder a 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 ID de cliente y claves secretas de forma segura.
  • 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 externa en la extensión external_api_urls derecho.

Proxy del servidor

En el siguiente ejemplo, se demuestra el uso del proxy del servidor para obtener un token de acceso que debe usar el proxy de recuperación. El ID de cliente y el secreto deben definirse como atributos del 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 { ExtensionContext2 } from '@looker/extension-sdk-react'

. . .

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

. . .
  const requestBody = {
    client_id: extensionSDK.createSecretKeyTag('my_client_id'),
    client_secret: extensionSDK.createSecretKeyTag('my_client_secret'),
  },
  try {
    const response = await extensionSDK.serverProxy(
      'https://myaccesstokenserver.com/access_token',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(requestBody),
      }
    )
    const { access_token, expiry_date } = response.body
. . .
  } catch (error) {
    // Error handling
    . . .
  }

El nombre del atributo de usuario se debe asignar 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 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'

Obtener proxy

En el siguiente ejemplo, se muestra el uso del proxy de recuperación. Usa el token de acceso del ejemplo de proxy del servidor anterior.

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

. . .

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

. . .

  try {
    const response = await extensionSDK.fetchProxy(
      'https://myaccesstokenserver.com/myendpoint',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${accessToken}`,
        },
        body: JSON.stringify({
          some_value: someValue,
          another_value: anotherValue,
        }),
      }
    )
    // Handle success

. . .

  } catch (error) {
    // Handle failure

. . .

  }

Integración con OAuth

El marco de trabajo 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 Goorgle.

Deberás especificar el extremo del servidor de OAuth en la autorización de extension oauth2_urls. Es posible que también debas especificar URL adicionales en la autorización de 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
  • 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 OAuth. El servidor 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 { ExtensionContext2 } from '@looker/extension-sdk-react'

. . .

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

. . .

    const response = await extensionSDK.oauth2Authenticate(
      'https://accounts.google.com/o/oauth2/v2/auth',
      {
        client_id: GOOGLE_CLIENT_ID!,
        scope: GOOGLE_SCOPES,
        response_type: 'token',
      }
    )
    const { access_token, expires_in } = response

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

Verificador y desafío del código de PKCE:

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

. . .

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

. . .

  const authRequest: Record<string, string> = {
    client_id: AUTH0_CLIENT_ID!,
    response_type: 'code',
    scope: AUTH0_SCOPES,
    code_challenge_method:  'S256',
  }
  const response = await extensionSDK.oauth2Authenticate(
    'https://sampleoauthserver.com/authorize',
    authRequest,
    'GET'
  )
  const exchangeRequest: Record<string, string> = {
    grant_type: 'authorization_code',
    client_id: AUTH0_CLIENT_ID!,
    code: response.code,
  }
  const codeExchangeResponse = await extensionSDK.oauth2ExchangeCodeForToken(
    'https://sampleoauthserver.com/login/oauth/token',
    exchangeRequest
  )
  const { access_token, expires_in } = codeExchangeResponse

Espartano

Spartan hace referencia a un método para usar la instancia de Looker como entorno para exponer extensiones, y solo extensiones, a un conjunto designado de usuarios. Un usuario espartano que navegue a una instancia de Looker se mostrará con el flujo de acceso que haya configurado el administrador de Looker. Una vez que se autentique el 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 controlarán la capacidad del usuario de navegar a las otras extensiones mediante extensionSDK.updateLocation. Hay un método específico del SDK de extensiones de Looker que permite al usuario salir de la instancia de Looker.

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

. . .

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

. . .
  // Navigate to another extension
  extensionSDK.updateLocation('/spartan/another::extension')

. . .
  // Logout
  extensionSDK.spartanLogout()

Define usuarios espartanos

Para definir un usuario espartano, debes crear un grupo denominado "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&quot. El valor se debe establecer en /spartan/my_extension::my_extension/, donde my_extension::my_extension es el ID de la extensión. Ahora, cuando el usuario acceda, se lo dirigirá a la extensión designada.

División de código

La división del código es la técnica mediante la cual se solicita el código solo cuando es necesario. Por lo general, los fragmentos de código están asociados 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 para dividir el 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

Batido de árboles

Los SDK de Looker admiten la eliminación de código no utilizado, pero aún no son perfectos. Modificamos nuestros SDK continuamente 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 aprovecharlo, pero cuando sea necesario, se documentará en las notas de la versión.

Para usar la eliminación de código no utilizado, el módulo que estás utilizando debe exportarse como esmódulo y las funciones que importes no deben tener efectos secundarios. El SDK de Looker para TypeScript/Javascript, la Biblioteca de entorno de ejecución del SDK de Looker, los Componentes de IU de Looker, el SDK de extensiones de Looker y el SDK de extensiones para React hacen esto.

En una extensión, debes elegir uno de los SDK de Looker (3.1 o 4.0) y usar el componente ExtensionProvider2 del SDK de extensiones para React. Si necesitas ambos SDK, sigue usando el componente ExtensionProvider, pero verás un aumento en el tamaño del paquete final.

El siguiente código configura el proveedor de la extensión. Deberá indicarle al proveedor qué SDK desea:

import { MyExtension } from './MyExtension'
import { ExtensionProvider2 } from '@looker/extension-sdk-react'
import { Looker40SDK } from '@looker/sdk/lib/4.0/methods'
import { hot } from 'react-hot-loader/root'

export const App = hot(() => {

  return (
    &#60;ExtensionProvider2 type={Looker40SDK}&#62;
      &#60;MyExtension /&#62;
    &#60;/ExtensionProvider2&#62;
  )
})

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

import * as lookerComponents from `@looker/components`

En el ejemplo anterior, se incluye toda la información del módulo. En su lugar, solo importe los componentes que realmente necesita. Por ejemplo:

import { Paragraph }  from `@looker/components`

Glosario

  • División de código: 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 mediante la división del código. Las funcionalidades que no se requieran de inmediato no se cargarán hasta que sean realmente necesarias.
  • 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 página vista en Looker. Las escenas se asignan a rutas principales. A veces, una escena tiene escenas secundarias que se asignan a subrutas dentro de la ruta principal.
  • Transpilar: el proceso de tomar el código fuente escrito en un idioma y transformarlo en otro que tenga un nivel similar de abstracción. Un ejemplo es TypeScript a JavaScript.