Exemplos de código JavaScript e do framework React da extensão

Esta página oferece exemplos de código escritos em React e JavaScript para funções comuns que você pode usar nas suas extensões.

Como usar o SDK da extensão Looker

As extensões precisam estabelecer uma conexão com o host do Looker. No React, isso é feito envolvendo a extensão em um componente ExtensionProvider40. Esse componente estabelece uma conexão com o host do Looker e disponibiliza o SDK da extensão do Looker e o SDK do Looker para a extensão.

import React from 'react'
import { ExtensionProvider40 } from '@looker/extension-sdk-react'
import { DemoCoreSDK } from './DemoCoreSDK'


export const App = () => {
 return (
   <ExtensionProvider40 chattyTimeout={-1}>
     <DemoCoreSDK />
   </ExtensionProvider40>
 )
}

Informações gerais sobre provedores de extensão

Os provedores de extensão expõem o SDK da extensão do Looker e a API do SDK para extensões. Várias versões do provedor de extensão foram criadas desde a criação do framework de extensão. Esta seção explica o histórico dos provedores de extensão e por que o ExtensionProvider40 é o recomendado.

O primeiro provedor de extensão foi ExtensionProvider, que expôs os dois SDKs do Looker, versões 3.1 e 4.0. A desvantagem foi que a inclusão dos dois SDKs aumentou o tamanho do pacote de produção final.

ExtensionProvider2 foi criado. Isso foi criado porque não fazia sentido para uma extensão usar os dois SDKs e forçar o desenvolvedor a escolher um ou outro. Infelizmente, isso ainda resultou na inclusão dos dois SDKs no tamanho do pacote de produção final.

Quando o SDK 4.0 foi movido para a GA, a ExtensionProvider40 foi criada. A vantagem de ExtensionProvider40 é que o desenvolvedor não precisa escolher qual SDK usar, porque o SDK 4.0 é a única versão disponível. Como o SDK 3.1 não está incluído no pacote final, isso tem a vantagem de reduzir o tamanho dele.

Para adicionar funções do SDK da extensão do Looker, primeiro você precisa ter uma referência ao SDK, o que pode ser feito pelo provedor ou globalmente. Em seguida, você pode chamar as funções do SDK como faria em qualquer aplicativo JavaScript.

  • Para acessar o SDK do provedor, siga estas etapas:
  import { ExtensionContext40 } from '@looker/extension-sdk-react'

  export const Comp1 = () => {
    const extensionContext = useContext(
      ExtensionContext40
    )
    const { extensionSDK, coreSDK } = extensionContext
  • Para acessar o SDK globalmente (a extensão precisa ser inicializada antes de ser chamada), siga estas etapas:
    const coreSDK = getCoreSDK()

Agora você pode usar o SDK como faria em qualquer aplicativo JavaScript:

  const GetLooks = async () => {
    try {
      const looks = await sdk.ok(sdk.all_looks('id'))
      // process looks
      . . .
    } catch (error) {
      // do error handling
      . . .
    }
}

Como a extensão é executada em um iframe em sandbox, não é possível navegar para outro lugar na instância do Looker atualizando o objeto window.location do pai. É possível navegar usando o SDK da extensão do Looker.

Essa função requer o direito navigation.

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

. . .

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

. . .

  extensionSDK.updateLocation('/browse')

Como abrir uma nova janela do navegador

Como a extensão é executada em um iframe em sandbox, não é possível usar a janela pai para abrir uma nova janela do navegador. É possível abrir uma janela do navegador usando o SDK da extensão do Looker.

Essa função exige o direito de acesso new_window para abrir uma nova janela para um local na instância atual do Looker ou o direito de acesso new_window_external_urls para abrir uma nova janela executada em um 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')

Roteamento e links diretos

O texto a seguir se aplica a extensões baseadas no React.

Os componentes ExtensionProvider, ExtensionProvider2 e ExtensionProvider40 criam automaticamente um React Router chamado MemoryRouter para você usar. Não tente criar um BrowserRouter, porque ele não funciona em iframes em sandbox. Não tente criar um HashRouter, porque ele não funciona em iframes em sandbox para a versão não baseada no Chromium do navegador Microsoft Edge.

Se o MemoryRouter for usado e você usar react-router na extensão, o framework vai sincronizar automaticamente o roteador da extensão com o roteador do host do Looker. Isso significa que a extensão vai ser notificada sobre os cliques nos botões de avançar e voltar do navegador e sobre a rota atual quando a página for recarregada. Isso também significa que a extensão precisa oferecer suporte automático a links diretos. Consulte os exemplos de extensão para saber como usar react-router.

Dados de contexto da extensão

Os dados de contexto do framework de extensão não devem ser confundidos com os contextos do React.

As extensões podem compartilhar dados de contexto entre todos os usuários. Os dados de contexto podem ser usados para dados que não mudam com frequência e que não têm requisitos de segurança especiais. É preciso ter cuidado ao gravar os dados, porque não há bloqueio de dados e a última gravação vence. Os dados de contexto ficam disponíveis para a extensão imediatamente após a inicialização. O SDK da extensão do Looker oferece funções para permitir que os dados de contexto sejam atualizados.

O tamanho máximo dos dados de contexto é de aproximadamente 16 MB. Os dados de contexto serão serializados em uma string JSON. Isso também precisa ser levado em consideração se você usar dados de contexto para sua extensão.

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 do usuário

O SDK da extensão do Looker fornece uma API para acessar os atributos do usuário do Looker. Há dois tipos de acesso a atributos do usuário:

  • Escopo: associado à extensão. Um atributo de usuário com escopo tem um namespace associado à extensão, e ele precisa ser definido na instância do Looker antes de ser usado. Para atribuir um namespace a um atributo do usuário, adicione o nome da extensão ao nome do atributo. Qualquer traço e os caracteres "::" no nome da extensão precisam ser substituídos por um sublinhado, já que traços e dois-pontos não podem ser usados em nomes de atributos do usuário.

    Por exemplo, um atributo de usuário com escopo chamado my_value usado com um ID de extensão de my-extension::my-extension precisa ter um nome de atributo de usuário definido como my_extension_my_extension_my_value. Depois de definido, o atributo do usuário pode ser lido e atualizado pela extensão.

  • Global: são atributos de usuário globais e somente leitura. Um exemplo é o atributo de usuário locale.

Confira a seguir uma lista de chamadas de API de atributos do usuário:

  • userAttributeGetItem: lê um atributo do usuário. Um valor padrão pode ser definido e será usado se um valor de atributo do usuário não existir para o usuário.
  • userAttributeSetItem: salva um atributo do usuário para o usuário atual. Falha em atributos de usuários globais. O valor salvo só fica visível para o usuário atual.
  • userAttributeResetItem: redefine um atributo do usuário atual para o valor padrão. Falha em atributos de usuários globais.

Para acessar os atributos do usuário, especifique os nomes dos atributos nos direitos global_user_attributes e/ou scoped_user_attributes. Por exemplo, no arquivo de manifesto do projeto do LookML, você adicionaria:

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

Armazenamento local

Os iframes em sandbox não permitem acesso ao armazenamento local do navegador. O SDK da extensão do Looker permite que uma extensão leia e grave no armazenamento local da janela pai. O armazenamento local tem um namespace para a extensão, o que significa que ela não pode ler o armazenamento local criado pela janela pai ou outras extensões.

O uso do armazenamento local requer o direito local_storage.

A API localhost da extensão é assíncrona, ao contrário da API de armazenamento local do navegador síncrona.

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

Como atualizar o título da página

As extensões podem atualizar o título da página atual. Os direitos não são necessários para realizar essa ação.

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

. . .

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

  extensionSDK.updateTitle('My Extension Title')

Gravar na área de transferência do sistema

Os iframes em sandbox não permitem acesso à área de transferência do sistema. O SDK da extensão Looker permite que uma extensão grave texto no Clipboard do sistema. Por motivos de segurança, a extensão não pode ler o conteúdo do Clipboard do sistema.

Para gravar na área de transferência do sistema, você precisa do direito 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) {
      . . .
    }

Como incorporar painéis, Looks e análises

O framework de extensão oferece suporte à incorporação de painéis, Looks e Análises.

O direito use_embeds é obrigatório. Recomendamos o uso do SDK de incorporação do Looker em JavaScript para incorporar conteúdo. Consulte a documentação do SDK de incorporação para mais informações.

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

Os exemplos de extensão usam componentes estilizados para fornecer um estilo simples ao iframe gerado. Exemplo:

import styled from "styled-components"

export const EmbedContainer = styled.div`
  width: 100%;
  height: 95vh;
  & > iframe {
    width: 100%;
    height: 100%;
  }

Como acessar endpoints de API externos

O framework de extensão oferece dois métodos para acessar endpoints de API externos:

  • O proxy do servidor: acessa o endpoint pelo servidor do Looker. Esse mecanismo permite que os IDs de cliente e as chaves secretas sejam definidos com segurança pelo servidor do Looker.
  • O proxy de busca: acessa o endpoint do navegador do usuário. O proxy é a interface do Looker.

Em ambos os casos, é necessário especificar o endpoint de API externa no direito de acesso external_api_urls da extensão.

Proxy do servidor

O exemplo a seguir demonstra o uso do proxy do servidor para receber um token de acesso para uso pelo proxy de busca. O ID e o segredo do cliente precisam ser definidos como atributos do usuário para a extensão. Normalmente, quando o atributo do usuário é configurado, o valor padrão é definido como o ID ou a chave secreta do 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
    . . .
  }

O nome do atributo do usuário precisa ser mapeado para a extensão. Os traços precisam ser substituídos por sublinhados, e os caracteres :: precisam ser substituídos por um sublinhado.

Por exemplo, se o nome da sua extensão for my-extension::my-extension, os atributos do usuário que precisam ser definidos para o exemplo anterior serão os seguintes:

my_extension_my_extension_my_client_id
my_extension_my_extension_'my_client_secret'

Buscar proxy

O exemplo a seguir demonstra o uso do proxy de busca. Ele usa o token de acesso do exemplo anterior de proxy do 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

. . .

  }

Integração do OAuth

O framework de extensão oferece suporte à integração com provedores OAuth. O OAuth pode ser usado para receber um token de acesso a um recurso específico, por exemplo, um documento das Planilhas Google.

É necessário especificar o endpoint do servidor OAuth no direito extension oauth2_urls. Talvez seja necessário especificar outros URLs no direito external_api_urls.

Os frameworks de extensão oferecem suporte aos seguintes fluxos:

  • Fluxo implícito
  • Tipo de concessão de código de autorização com chave secreta
  • Desafio e verificador de código PKCE

O fluxo geral é que uma janela filha é aberta e carrega uma página do servidor OAuth. O servidor OAuth autentica o usuário e redireciona de volta ao servidor do Looker com outros detalhes que podem ser usados para receber um token de acesso.

Fluxo 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 concessão de código de autorização com chave 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

Desafio e verificador de 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

Spartan

Spartan se refere a um método de uso da instância do Looker como um ambiente para expor extensões e somente extensões a um conjunto designado de usuários. Um usuário Spartan que acessar uma instância do Looker vai receber o fluxo de login configurado pelo administrador do Looker. Depois que o usuário for autenticado, uma extensão será apresentada de acordo com o atributo do usuário landing_page, conforme mostrado a seguir. O usuário só pode acessar extensões, não qualquer outra parte do Looker. Se o usuário tiver acesso a várias extensões, elas vão controlar a capacidade de navegar para as outras extensões usando extensionSDK.updateLocation. Há um método específico do SDK da extensão do Looker para permitir que o usuário saia da instância do 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()

Como definir usuários Spartan

Para definir um usuário Spartan, é necessário criar um grupo chamado "Somente extensões".

Depois de criar o grupo "Somente extensões", acesse a página Atributos do usuário na seção Administrador do Looker e edite o atributo do usuário landing_page. Selecione a guia Valores do grupo e adicione o grupo "Apenas extensões". O valor precisa ser definido como /spartan/my_extension::my_extension/, em que my_extension::my_extension é o ID da sua extensão. Agora, quando esse usuário fizer login, ele será direcionado para a extensão designada.

Divisão de código

A divisão de código é a técnica em que o código é solicitado apenas quando necessário. Normalmente, os blocos de código são associados a rotas do React, em que cada rota recebe o próprio bloco de código. No React, isso é feito com os componentes Suspense e React.lazy. O componente Suspense mostra um componente substituto enquanto o bloco de código é carregado. O React.lazy é responsável por carregar o bloco de código.

Como configurar a divisão 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>

O componente carregado de forma lenta é implementado da seguinte maneira:

import { lazy } from 'react'

const Comp1 = lazy(
 async () => import(/* webpackChunkName: "comp1" */ './Comp1')
)

export const AsyncComp1 = () => &#60;Home />

O componente é implementado da seguinte maneira. O componente precisa ser exportado como um componente padrão:

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

export default Comp1

Tree shaking

Embora os SDKs do Looker ofereçam suporte ao tree-shaking, essa função ainda precisa de melhorias. Estamos sempre modificando nossos SDKs para melhorar o suporte ao recurso de tree shaking. Algumas dessas mudanças podem exigir que você refatore seu código para aproveitar as vantagens, mas, quando isso for necessário, será documentado nas notas da versão.

Para usar o tree-shaking, o módulo usado precisa ser exportado como um esmodule, e as funções importadas não podem ter efeitos colaterais. O SDK do Looker para TypeScript/Javascript, a biblioteca de execução do SDK do Looker, os componentes da interface do Looker, o SDK da extensão do Looker e o SDK da extensão para React atendem a esses requisitos.

Em uma extensão, use o SDK do Looker 4.0 e o componente ExtensionProvider2 ou ExtensionProvider40 do SDK da extensão para React.

O código a seguir configura o provedor de extensão. Você precisa informar ao provedor qual SDK quer:

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;
  )
})

Não use o seguinte estilo de importação na sua extensão:

import * as lookerComponents from `@looker/components`

O exemplo anterior traz tudo do módulo. Em vez disso, importe apenas os componentes necessários. Exemplo:

import { Paragraph }  from `@looker/components`

Glossário

  • Divisão de código: uma técnica para carregar de maneira lenta o JavaScript até que ele seja realmente necessário. O ideal é manter o pacote JavaScript carregado inicialmente o menor possível. Isso pode ser feito usando a divisão de código. Qualquer funcionalidade que não seja necessária imediatamente não será carregada até que seja realmente necessária.
  • IDE: ambiente de desenvolvimento integrado. Um editor usado para criar e modificar uma extensão. Por exemplo, o Visual Studio Code, o IntelliJ e o WebStorm.
  • Cena: geralmente uma visualização de página no Looker. As cenas são associadas às rotas principais. Às vezes, uma cena tem cenas filhas que são mapeadas para subtrechos dentro do trajeto principal.
  • Transpilação: o processo de tomar o código-fonte escrito em uma linguagem e transformá-lo em outra linguagem com um nível semelhante de abstração. Um exemplo é o TypeScript para JavaScript.