Exemplos de códigos do React e do JavaScript do framework de extensões

Em breve, o framework de extensão do Looker usará um novo mecanismo de carregamento. O novo carregador pode causar erros quando as extensões existentes forem carregadas. Para ver instruções sobre como testar suas extensões com o novo carregador antes da ativação oficial em ambientes do Looker, consulte o artigo Como testar o novo carregador de framework de extensão na Central de Ajuda do Looker.

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

Como usar o SDK de extensão do Looker

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

  • Para acessar o SDK no provedor:
  import { ExtensionContext2 } from '@looker/extension-sdk-react'

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

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 Looker.

Esta função requer o direito navigation.

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

. . .

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

. . .

  extensionSDK.updateLocation('/browse')

Abrir uma nova janela do navegador

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

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

Roteamento e links diretos

As informações a seguir se aplicam às extensões baseadas no React.

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

Se o MemoryRouter for utilizado e você usar o react-router na sua extensão, o framework de extensões sincronizará automaticamente o roteador da sua extensão com o roteador do host do Looker. Isso significa que a extensão será notificada sobre os cliques no botão "Avançar" e "Avançar" do navegador e sobre o trajeto atual quando a página for recarregada. Isso também significa que a extensão deve ser compatível automaticamente com links diretos. Veja os exemplos de extensão de como usar o react-router.

Dados de contexto da extensão

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

Com as extensões, é possível compartilhar dados de contexto entre todos os usuários de uma extensão. Os dados de contexto podem ser usados para dados que não mudam com frequência e que não têm requisitos especiais de segurança. Tenha cuidado ao gravar os dados, porque não há bloqueio de dados, e a última gravação ganha. Os dados de contexto ficam disponíveis para a extensão imediatamente após a inicialização. O SDK da extensão Looker fornece 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. Portanto, isso também precisa ser considerado se você usar dados de contexto para sua extensão.

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

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

  • Scoped (associados): à extensão Um atributo de usuário com escopo é incluído na namespace da extensão, e o atributo de usuário precisa ser definido na instância do Looker antes de ser usado. Para adicionar um namespace a um atributo de usuário, use o nome da extensão como prefixo no nome do atributo. Os traços e os caracteres "#" 9::: ' no nome da extensão precisam ser substituídos por um sublinhado, já que não é possível usar traços e dois-pontos no nome dos atributos do usuário.

    Por exemplo: um atributo do usuário com escopo chamado my_value usado com um código de extensão de my-extension::my-extension precisa ter um nome de atributo de usuário de my_extension_my_extension_my_value definido. 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 são somente leitura. Um exemplo é o atributo de usuário locale.

Veja abaixo uma lista de chamadas de API dos atributos do usuário:

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

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

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

Armazenamento local

Os iframes no sandbox não permitem acesso ao armazenamento local do navegador. O SDK da extensão Looker permite que uma extensão leia e grave no armazenamento local da janela mãe. O armazenamento local recebe namespaces pela extensão, o que significa que não pode ler o armazenamento local criado pela janela pai ou por outras extensões.

O uso do armazenamento local requer o direito local_storage.

A API de extensão localhost é assíncrona, e não a API síncrona de armazenamento local do navegador.

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

Como atualizar o título da página

As extensões podem atualizar o título atual da página. Não é necessário ter direitos para realizar esta ação.

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

. . .

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

  extensionSDK.updateTitle('My Extension Title')

Gravando na área de transferência do sistema

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

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

Como incorporar painéis, aparências e explorações

O framework de extensões é compatível com a incorporação de painéis, aparências e explorações. Tanto os painéis regulares quanto os legados podem ser incorporados.

O direito use_embeds é obrigatório. Recomendamos que você use o SDK incorporado do Looker para JavaScript para incorporar conteúdo. Consulte a documentação do SDK incorporado para mais informações.

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

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 APIs externas

O framework de extensão oferece dois métodos para acessar endpoints de APIs externas:

  • O proxy do servidor: acessa o endpoint por meio do servidor do Looker. Esse mecanismo permite que IDs do cliente e 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 IU do Looker.

Em ambos os casos, você precisa especificar o endpoint da API externo na extensão external_api_urls do direito.

Proxy do servidor

O exemplo a seguir demonstra o uso do proxy do servidor para receber um token de acesso para ser usado pelo proxy de busca. O ID do cliente e o secret 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 chave secreta do 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
    . . .
  }

O nome do atributo de 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 único sublinhado.

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

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 do proxy do 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

. . .

  }

Integração com OAuth

O framework de extensões é compatível com a integração com provedores OAuth. O OAuth pode ser usado para acessar um token de acesso e acessar um recurso específico, como um documento do Planilhas Google.

Será necessário especificar o endpoint do servidor OAuth no direito extension oauth2_urls. Talvez também seja necessário especificar URLs adicionais 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 do secret
  • 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 para o servidor Looker com detalhes adicionais que podem ser usados para receber um token de acesso.

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

  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

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

Espartana

O Spartan é um método de usar a 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 simples navegando até uma instância do Looker será apresentado com qualquer fluxo de login configurado pelo administrador do Looker. Depois que o usuário for autenticado, uma extensão será apresentada a ele de acordo com o atributo de usuário landing_page, conforme mostrado abaixo. O usuário só pode acessar as extensões, ou seja, não pode acessar nenhuma outra parte do Looker. Se o usuário tiver acesso a várias extensões, elas controlarão a capacidade de navegar até as outras extensões usando extensionSDK.updateLocation. Há um método específico do SDK da extensão Looker para permitir que o usuário saia da instância do 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()

Definir usuários básicos

Para definir um usuário básico, você precisa criar um grupo chamado "Extensions Only"

Depois que o grupo "Extensions Only" tiver sido criado, navegue até a página Atributos do usuário na seção Administrador do Looker e edite o atributo de usuário landing_page. Selecione a guia Group Values e adicione o grupo "Extensions Only" O valor precisa ser definido como /spartan/my_extension::my_extension/, em que my_extension::my_extension é o ID da 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 somente quando necessário. Normalmente, os blocos de código são associados às 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 exibe um componente substituto enquanto o bloco de código é carregado. 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 de carregamento lento é implementado desta 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

Tremor em árvore

Os SDKs do Looker são compatíveis com agitação de árvores, mas eles ainda não são perfeitos. Estamos sempre modificando nossos SDKs para melhorar o suporte a tree shaking. Algumas dessas alterações podem exigir que você refatore o código para aproveitar as vantagens, mas isso será documentado nas notas da versão.

Para usar o "tree shaking", o módulo que você está usando precisa ser exportado como um esmodule, e as funções importadas não podem ter efeitos colaterais. Os módulos Looker SDK for TypeScript/Javascript, Looker SDK Runtime Library, Looker UI Components, Looker Extension SDK e SDK da extensão para React fazem isso.

Em uma extensão, escolha um dos SDKs do Looker, 3.1 ou 4.0, e use o componente ExtensionProvider2 do SDK de extensão para React. Se você precisar dos dois SDKs, continue usando o componente ExtensionProvider, mas verá um aumento no tamanho final do pacote.

O código a seguir configura o provedor de extensões. Você precisará informar ao provedor qual SDK você quer:

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

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

import * as lookerComponents from `@looker/components`

O exemplo acima traz tudo do módulo. Basta importar os componentes realmente necessários. Exemplo:

import { Paragraph }  from `@looker/components`

Glossário

  • Divisão de código: uma técnica para carregamento lento de JavaScript até que ela seja realmente necessária. O ideal é que o pacote de JavaScript carregado inicialmente seja o menor possível. Para isso, use a divisão de código. As funcionalidades que não são necessárias imediatamente não são carregadas até que sejam realmente necessárias.
  • IDE: ambiente de desenvolvimento integrado Um editor usado para criar e modificar uma extensão. Os exemplos são o Visual Studio Code, o Intellij e o WebStorm.
  • Cenário: geralmente uma visualização de página no Looker. As cenas mapeiam os principais trajetos. Às vezes, uma cena tem cenas filhas que mapeiam para subtrajetos no trajeto principal.
  • Transpilação: o processo de transformar o código-fonte em um idioma e transformá-lo em outra linguagem com um nível de abstração semelhante. Um exemplo é o TypeScript para JavaScript.