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

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

Usa el SDK de la extensión de Looker

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

  • Para acceder al SDK desde el proveedor, haz lo siguiente:
  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 actualizar el objeto window.location superior para navegar en otra parte dentro de la instancia de Looker. Es posible navegar con el SDK de la extensión de Looker.

Esta función requiere el derecho navigation.

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

. . .

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

. . .

  extensionSDK.updateLocation('/browse')

Abrir una nueva ventana del navegador

Como la extensión se ejecuta en un iframe de zona de pruebas, no puedes usar la ventana principal para abrir una ventana del navegador nueva. Es posible abrir una ventana del navegador con el SDK de extensión de Looker.

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 { 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 los uses. No intentes crear un BrowserRouter, ya que no funciona en iframes de la zona de pruebas. No intentes crear un HashRouter, ya que no funciona en iframes de la zona de pruebas para la versión del navegador de Microsoft Edge que no se basa 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 se notificará a la extensión cuando se hagan clic en los botones para retroceder y avanzar, 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 saber cómo usar react-router.

Datos de contexto de la extensión

Los datos de contexto del framework de extensiones no deben confundirse con los contextos de React.

Las extensiones permiten compartir datos contextuales entre todos los usuarios de una extensión. Los datos de contexto se pueden usar para los datos que no cambian con frecuencia y que no tienen requisitos de seguridad especiales. Se debe tener cuidado cuando se escriben los datos, ya que no hay bloqueo de datos y cuando termina la última. 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 que permiten que los datos del 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 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 extensión 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. Se le asigna un espacio de nombres a un atributo de usuario específico para la extensión, y este último 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 usa con un ID de extensión de 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 del 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 las llamadas a la API de atributos de los usuarios:

  • userAttributeGetItem: Lee un atributo de usuario. Se puede definir un valor predeterminado, que se usará si no existe un valor de atributo para el usuario.
  • userAttributeSetItem: Guarda un atributo de usuario para el usuario actual. Fallará en el caso de 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á en el caso de los atributos de usuario globales.

Para acceder a los atributos de usuario, debes especificar los nombres de atributo en los derechos 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 la zona de pruebas no permiten el acceso al almacenamiento local del navegador. El SDK de extensión de Looker permite que una extensión lea y escriba en el almacenamiento local de la ventana superior. El almacenamiento local tiene espacio de nombres, por lo que no puede leer el almacenamiento local que crea la ventana superior o cualquier otra extensión.

Para usar el almacenamiento local, se requiere el 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 { 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 la zona de pruebas no permiten el acceso al portapapeles del sistema. El SDK de extensión 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 los derechos 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 framework de extensiones admite la incorporación de paneles, estilos 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 { 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 API externos

El framework de extensiones proporciona dos métodos para acceder a los extremos de la API externa:

  • El proxy de 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.
  • 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 la extensión derecho external_api_urls.

Proxy del servidor

En el siguiente ejemplo, se muestra el uso del proxy del servidor a fin de obtener un token de acceso para que lo use el proxy de recuperación. El ID y secreto de cliente se deben definir 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 { 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 la extensión es my-extension::my-extension, los atributos de usuario que se deben definir para el ejemplo anterior son 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 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 de OAuth

El framework de extensiones admite la integración con proveedores de OAuth. OAuth se puede usar para obtener un token de acceso a fin de 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 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
  • Verificador y desafío de 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 { 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

Desafío y verificador del código 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 se refiere a un método de uso de la instancia de Looker como un entorno para exponer extensiones, y solo extensiones, a un conjunto designado de usuarios. Un usuario espartano que navegue a una instancia de Looker tendrá el flujo de acceso configurado por el administrador de Looker. Una vez que se autentique el usuario, se le presentará 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 puede acceder 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. Existe un método específico del SDK de extensión de Looker para permitir que el usuario salga 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()

Cómo definir usuarios espartanos

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

Una vez que se haya creado el grupo "Extensions Only", navega a la página User Attributes en la sección Admin de Looker y edita el atributo de usuario landing_page. Seleccione la pestaña Valores del grupo y agregue el grupo "Solo extensiones". El valor debe establecerse en /spartan/my_extension::my_extension/, donde my_extension::my_extension es el ID de tu 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 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 del 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

Eliminación de árboles

Los SDK de Looker admiten la eliminación de código no utilizado, pero aún no son perfectos. Modificamos nuestros SDK 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 aprovecharlo, pero cuando esto 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 un módulo de módulo y las funciones que importas 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 la IU de Looker, el SDK de extensión de Looker y el SDK de extensión 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 observarás un aumento en el tamaño del paquete final.

Con el siguiente código, se configura el proveedor de la extensión. Deberás indicarle al proveedor qué SDK quieres:

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

import * as lookerComponents from `@looker/components`

En el ejemplo anterior, se incluye todo desde el 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: Técnica para la carga diferida de JavaScript hasta que sea 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 utiliza para crear y modificar una extensión. Algunos ejemplos son Visual Studio Code, Intellij y WebStorm.
  • Escena: Por lo general, es una página vista en Looker. Las escenas se asignan a las rutas principales. A veces, las escenas tienen 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 de abstracción similar. Un ejemplo es TypeScript a JavaScript.