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

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

Como usar o SDK de extensão do Looker

Para adicionar funções do SDK da extensão do Looker, primeiro você precisa conseguir 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 a partir do 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 chamar:
    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, você não pode usar a janela pai para abrir uma nova janela de 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 são aplicáveis às extensões baseadas em React.

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

Se o MemoryRouter for utilizado e você usar react-router na sua extensão, o framework de extensão sincronizará automaticamente o roteador da extensão com o roteador do host do Looker. Isso significa que a extensão será notificada de cliques nos botões "Voltar" e "Avançar" do navegador e da rota atual quando a página for recarregada. Isso também significa que a extensão deve ser automaticamente compatível com links diretos. Veja os exemplos de extensão sobre como utilizar o 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 de uma extensão. Os dados de contexto podem ser usados para dados que não são alterados com frequência e que não têm requisitos de segurança especiais. Tome 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 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, de modo que também precisará 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 do usuário do Looker. Há dois tipos de acesso a atributos do usuário:

  • Com escopo: associado à extensão. Um atributo de usuário com escopo é definido como namespace para a extensão, e o atributo de usuário precisa ser definido na instância do Looker antes de ser usado. Para atribuir um namespace a um atributo de usuário, insira o nome da extensão como prefixo no nome do atributo. Os traços e os caracteres "::" no nome da extensão precisam ser substituídos por um sublinhado, já que não é possível usar traços e dois pontos nos nomes de atributo 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 de my_extension_my_extension_my_value definido. Uma vez definido, o atributo do usuário pode ser lido e atualizado pela extensão.

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

Veja abaixo 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 de usuário não existir para o usuário.
  • userAttributeSetItem: salva um atributo do usuário atual. Ocorrerá uma falha nos atributos do usuário global. O valor salvo é visível apenas para o usuário atual.
  • userAttributeResetItem: redefine um atributo de usuário do usuário atual para o valor padrão. Ocorrerá uma falha nos atributos do usuário global.

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, 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 um namespace da extensão, o que significa que ele 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 extensão localhost API é assíncrona em vez da síncrona API 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. Os direitos não são necessários para realizar esta ação.

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

. . .

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

  extensionSDK.updateTitle('My Extension Title')

Gravar 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 de leitura da á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ão é compatível com a incorporação de painéis, visuais e explorações.

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 de incorporação 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 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 fornece dois métodos para acessar endpoints de API externos:

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

Em ambos os casos, você precisa especificar o endpoint de API externa no direito external_api_urls da extensão.

Proxy do servidor

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

O framework de extensão oferece suporte à integração com provedores OAuth. O OAuth pode ser usado para receber um token de acesso e acessar um recurso específico, por exemplo, 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 são compatíveis com os 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 secundária é aberta e carrega uma página do servidor OAuth. O servidor OAuth autentica o usuário e redireciona ao servidor do 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 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

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

Espartanas

O 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 básico que acessa uma instância do Looker recebe um fluxo de login configurado pelo administrador. Depois que o usuário for autenticado, uma extensão será apresentada a ele de acordo com o atributo do usuário do landing_page, conforme mostrado abaixo. O usuário só pode acessar as extensões. Ele não pode acessar outras partes do Looker. Se o usuário tiver acesso a várias extensões, elas vão permitir que o usuário navegue até as outras usando extensionSDK.updateLocation. Há um método específico de 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()

Como definir usuários básicos

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

Depois que o grupo "Apenas extensões" for criado, navegue até 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 de grupo e adicione o grupo "Somente extensões". O valor deve ser definido como /spartan/my_extension::my_extension/, em que my_extension::my_extension é o ID da sua extensão. Agora, quando o 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 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 componente padrão:

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

export default Comp1

Tremor de árvore

Os SDKs do Looker são compatíveis com o tree shaking, mas ainda não são perfeitos. Estamos sempre modificando nossos SDKs para melhorar a compatibilidade com o tree shaking. Algumas dessas alterações podem exigir que você refatore seu código para aproveitar, mas quando isso for necessário, 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. O SDK do Looker para TypeScript/Javascript, Biblioteca do ambiente de execução do SDK do Looker, Componentes da IU do Looker, SDK da extensão do Looker 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ão. 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 extensão:

import * as lookerComponents from `@looker/components`

O exemplo acima traz tudo do módulo. Em vez disso, importe somente os componentes de que realmente precisa. Exemplo:

import { Paragraph }  from `@looker/components`

Glossário

  • Divisão de código - Uma técnica de carregamento lento de JavaScript até que ela seja realmente necessária. O ideal é que você mantenha o pacote JavaScript carregado o menor possível. Para isso, use a divisão de código. Qualquer funcionalidade que não seja imediatamente necessária 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. Exemplos: Visual Studio Code, Intellij e WebStorm.
  • Cena: geralmente uma visualização de página no Looker. As cenas mapeiam para os principais trajetos. Às vezes, uma cena terá cenas derivadas que mapeiam para subtrajetos no trajeto principal.
  • Transcompilação — O processo de transformar o código-fonte em um idioma em outro com um nível de abstração semelhante. Um exemplo é do TypeScript para JavaScript.