Esta página oferece exemplos de código escritos em React e JavaScript para funções comuns que pode querer usar nas suas extensões.
Usar o SDK de extensões do Looker
As extensões têm de estabelecer uma ligação com o anfitrião do Looker. No React, isto é feito envolvendo a extensão num componente ExtensionProvider40
. Este componente estabelece uma ligação com o anfitrião do Looker e disponibiliza o SDK de extensões do Looker e o SDK do Looker à 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>
)
}
Contexto sobre fornecedores de extensões
Os fornecedores de extensões expõem o SDK de extensões do Looker e a API do SDK às extensões. Foram criadas diferentes versões do fornecedor de extensões desde que a estrutura de extensões foi criada. Esta secção explica o histórico dos fornecedores de extensões e por que motivo o ExtensionProvider40 é o fornecedor recomendado.
O primeiro fornecedor de extensões foi ExtensionProvider
, que expôs os dois SDKs do Looker, as versões 3.1 e 4.0. A desvantagem era que a inclusão de ambos os SDKs aumentava o tamanho do pacote de produção final.
Em seguida, foi criado o grupo ExtensionProvider2
. Esta foi criada porque não fazia sentido uma extensão usar ambos os SDKs e obrigar o programador a escolher um ou outro. Infelizmente, isto resultou na inclusão de ambos os SDKs no tamanho do pacote de produção final.
Quando o SDK 4.0 passou para a versão GA, foi criado o ExtensionProvider40
. A vantagem do ExtensionProvider40
é que o programador não tem de escolher que 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, isto tem a vantagem de reduzir o tamanho do pacote.
Para adicionar funções do SDK de extensões do Looker, primeiro, tem de obter uma referência ao SDK, o que pode ser feito a partir do fornecedor ou globalmente. Em seguida, pode chamar funções do SDK como faria em qualquer aplicação JavaScript.
- Para aceder ao SDK a partir do fornecedor, siga estes passos:
import { ExtensionContext40 } from '@looker/extension-sdk-react'
export const Comp1 = () => {
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK, coreSDK } = extensionContext
- Para aceder ao SDK globalmente (a extensão tem de ser inicializada antes de ser chamada), siga estes passos:
const coreSDK = getCoreSDK()
Agora, pode usar o SDK como faria em qualquer aplicação JavaScript:
const GetLooks = async () => {
try {
const looks = await sdk.ok(sdk.all_looks('id'))
// process looks
. . .
} catch (error) {
// do error handling
. . .
}
}
Navegar noutros locais na instância do Looker
Uma vez que a extensão é executada numa iFrame em sandbox, não pode navegar para outro local na instância do Looker atualizando o objeto window.location
do elemento principal. É possível navegar através do SDK de extensões do Looker.
Esta função requer a autorização navigation
.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
. . .
extensionSDK.updateLocation('/browse')
Abrir uma nova janela do navegador
Uma vez que a extensão é executada num iFrame em sandbox, não pode usar a janela principal para abrir uma nova janela do navegador. É possível abrir uma janela do navegador através do SDK de extensões do Looker.
Esta função requer a autorização new_window
para abrir uma nova janela para uma localização na instância atual do Looker ou a autorização new_window_external_urls
para abrir uma nova janela que é executada num anfitrião 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')
Encaminhamento e links diretos
O seguinte aplica-se a extensões baseadas em React.
Os componentes ExtensionProvider
, ExtensionProvider2
e ExtensionProvider40
criam automaticamente um React Router denominado MemoryRouter
para sua utilização. Não tente criar um BrowserRouter
, uma vez que não funciona em iFrames em sandbox. Não tente criar um HashRouter
, uma vez que não funciona em iFrames no sandbox para a versão do navegador Microsoft Edge não baseada no Chromium.
Se o MemoryRouter
for usado e usar o react-router
na extensão, a framework de extensões sincroniza automaticamente o router da extensão com o router do anfitrião do Looker. Isto significa que a extensão vai receber uma notificação dos cliques nos botões anterior e seguinte do navegador, bem como da rota atual, quando a página for recarregada. Isto também significa que a extensão deve suportar automaticamente links diretos. Consulte os exemplos de extensões para saber como usar react-router
.
Dados de contexto das extensões
Os dados de contexto da framework de extensões não devem ser confundidos com os contextos do React.
As extensões têm a capacidade de partilhar dados de contexto entre todos os utilizadores 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 de segurança especiais. Deve ter cuidado ao escrever os dados, uma vez que não existe bloqueio de dados e a última escrita é a que prevalece. Os dados de contexto estão disponíveis para a extensão imediatamente após o arranque. O SDK de extensões do Looker fornece funções para permitir que os dados de contexto sejam atualizados e renovados.
O tamanho máximo dos dados de contexto é de aproximadamente 16 MB. Os dados de contexto são serializados numa string JSON, pelo que também tem de ter isso em conta se usar dados de contexto para a 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 utilizador
O SDK de extensões do Looker fornece uma API para aceder aos atributos do utilizador do Looker. Existem dois tipos de acesso a atributos do utilizador:
Com âmbito: associado à extensão. Um atributo do utilizador com âmbito é associado ao espaço de nomes da extensão, e o atributo do utilizador tem de ser definido na instância do Looker antes de poder ser usado. Para definir um espaço de nomes para um atributo do utilizador, adicione o nome da extensão como prefixo do nome do atributo. Todos os traços e os carateres "::" no nome da extensão têm de ser substituídos por um sublinhado, uma vez que não é possível usar traços nem dois pontos nos nomes dos atributos do utilizador.
Por exemplo: um atributo do utilizador no âmbito do utilizador denominado
my_value
usado com um ID de extensão demy-extension::my-extension
tem de ter um nome de atributo do utilizador demy_extension_my_extension_my_value
definido. Depois de definido, o atributo do utilizador pode ser lido e atualizado pela extensão.Global: estes são atributos de utilizador globais e são só de leitura. Um exemplo é o atributo do utilizador
locale
.
Segue-se uma lista de chamadas da API de atributos do utilizador:
userAttributeGetItem
— Lê um atributo do utilizador. Pode ser definido um valor predefinido, que é usado se não existir um valor de atributo do utilizador para o utilizador.userAttributeSetItem
: guarda um atributo do utilizador para o utilizador atual. Falha para atributos de utilizador globais. O valor guardado só é visível para o utilizador atual.userAttributeResetItem
: repõe um atributo do utilizador para o utilizador atual com o valor predefinido. Falha para atributos de utilizador globais.
Para aceder aos atributos do utilizador, tem de especificar os nomes dos atributos nas autorizações global_user_attributes
e/ou scoped_user_attributes
. Por exemplo, no ficheiro de manifesto do projeto do LookML, 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 o acesso ao armazenamento local do navegador. O SDK de extensões do Looker permite que uma extensão leia e escreva no armazenamento local da janela principal. O armazenamento local tem um espaço de nomes para a extensão, o que significa que não pode ler o armazenamento local criado pela janela principal ou outras extensões.
A utilização do armazenamento local requer a autorização 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')
Atualizar o título da página
As extensões podem atualizar o título da página atual. Não são necessários direitos para realizar esta ação.
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
const { extensionSDK } = extensionContext
extensionSDK.updateTitle('My Extension Title')
Escrever na área de transferência do sistema
Os iFrames colocados em sandbox não permitem o acesso à área de transferência do sistema. O SDK de extensões do Looker permite que uma extensão escreva texto na área de transferência do sistema. Por motivos de segurança, a extensão não tem autorização para ler a partir da área de transferência do sistema.
Para escrever na área de transferência do sistema, precisa da autorização 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) {
. . .
}
Incorporar painéis de controlo, Looks e Explores
A estrutura de extensões suporta a incorporação de painéis de controlo, Looks e explorações.
A autorização use_embeds
é obrigatória. Recomendamos que use o SDK de incorporação JavaScript do Looker 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 (<EmbedContainer ref={embedCtrRef} />)
Os exemplos de extensões usam componentes com estilos para fornecer estilos simples ao iFrame gerado. Por exemplo:
import styled from "styled-components"
export const EmbedContainer = styled.div`
width: 100%;
height: 95vh;
& > iframe {
width: 100%;
height: 100%;
}
Aceder a pontos finais de APIs externas
A framework de extensões oferece dois métodos para aceder a pontos finais de APIs externas:
- O proxy do servidor: acede ao ponto final através do servidor do Looker. Este mecanismo permite que o servidor do Looker defina de forma segura os IDs de cliente e as chaves secretas.
- O proxy de obtenção: acede ao ponto final a partir do navegador do utilizador. O proxy é a IU do Looker.
Em ambos os casos, tem de especificar o ponto final da API externa na autorização external_api_urls
da extensão.
Proxy do servidor
O exemplo seguinte demonstra a utilização do proxy do servidor para obter um token de acesso para utilização pelo proxy de obtenção. O ID de cliente e o segredo têm de ser definidos como atributos do utilizador para a extensão. Normalmente, quando o atributo do utilizador está configurado, o valor predefinido é definido como o ID do cliente ou o segredo.
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 utilizador tem de ser mapeado para a extensão. Os travessões têm de ser substituídos por sublinhados e os carateres ::
têm de ser substituídos por um único sublinhado.
Por exemplo, se o nome da sua extensão for my-extension::my-extension
, os atributos do utilizador que têm de ser definidos para o exemplo anterior seriam os seguintes:
my_extension_my_extension_my_client_id
my_extension_my_extension_'my_client_secret'
Proxy de obtenção
O exemplo seguinte demonstra a utilização do proxy de obtenção. Usa o token de acesso do exemplo de proxy do servidor anterior.
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
A framework de extensões suporta a integração com fornecedores de OAuth. O OAuth pode ser usado para obter um token de acesso para aceder a um recurso específico, por exemplo, um documento do Google Sheets.
Tem de especificar o ponto final do servidor OAuth na autorização extension oauth2_urls
. Também pode ter de especificar URLs adicionais na autorização external_api_urls
.
As estruturas de extensões suportam os seguintes fluxos:
- Fluxo implícito
- Tipo de concessão com código de autorização com chave secreta
- Desafio e verificador de código PKCE
O fluxo geral é que é aberta uma janela secundária que carrega uma página do servidor OAuth. O servidor OAuth autentica o utilizador e redireciona-o de volta para o servidor do Looker com detalhes adicionais que podem ser usados para obter 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 com 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 do código PKCE:
import { ExtensionContext40 } from '@looker/extension-sdk-react'
. . .
const extensionContext = useContext(
ExtensionContext40
)
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
O Spartan refere-se a um método de utilização da instância do Looker como um ambiente para expor extensões, e apenas extensões, a um conjunto designado de utilizadores. Um utilizador do Spartan que navega para uma instância do Looker vê o fluxo de início de sessão que o administrador do Looker configurou. Assim que o utilizador for autenticado, é apresentada uma extensão ao utilizador de acordo com o respetivo landing_page
atributo de utilizador, conforme mostrado em seguida. O utilizador só pode aceder a extensões e não pode aceder a nenhuma outra parte do Looker. Se o utilizador tiver acesso a várias extensões, as extensões controlam a capacidade do utilizador de navegar para as outras extensões através de extensionSDK.updateLocation
. Existe um método específico do SDK de extensões do Looker para permitir que o utilizador termine sessão na 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()
Definir utilizadores espartanos
Para definir um utilizador espartano, tem de criar um grupo denominado "Apenas extensões".
Depois de criar o grupo "Apenas extensões", navegue para a página Atributos do utilizador na secção Administração do Looker e edite o landing_page
atributo do utilizador. Selecione o separador Valores de grupo e adicione o grupo "Apenas extensões". O valor deve ser definido como /spartan/my_extension::my_extension/
, em que my_extension::my_extension
é o ID da extensão. Agora, quando esse utilizador iniciar sessão, é encaminhado para a extensão designada.
Divisão de código
A divisão de código é a técnica em que o código só é pedido quando é necessário. Normalmente, os fragmentos de código estão associados a rotas do React, em que cada rota tem o seu próprio fragmento de código. No React, isto é feito com os componentes Suspense
e React.lazy
. O componente Suspense
apresenta um componente alternativo enquanto o fragmento de código é carregado. React.lazy
é responsável por carregar o fragmento de código.
Configuração para dividir o 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 em diferido é implementado da seguinte forma:
import { lazy } from 'react'
const Comp1 = lazy(
async () => import(/* webpackChunkName: "comp1" */ './Comp1')
)
export const AsyncComp1 = () => <Home />
O componente é implementado da seguinte forma. O componente tem de ser exportado como um componente predefinido:
const Comp1 = () => {
return (
<div>Hello World</div>
)
}
export default Comp1
Agitar árvores
Embora os SDKs do Looker suportem atualmente a eliminação de código não utilizado, esta função ainda precisa de melhorias. Estamos continuamente a modificar os nossos SDKs para melhorar o suporte da eliminação de código não utilizado. Algumas destas alterações podem exigir que refatore o seu código para tirar partido das mesmas, mas, quando tal for necessário, será documentado nas notas de lançamento.
Para usar o tree-shaking, o módulo que usa tem de ser exportado como um esmodule e as funções que importa têm de estar livres de efeitos secundários. O SDK Looker para TypeScript/Javascript, a biblioteca de tempo de execução do SDK Looker, os componentes da IU do Looker, o SDK de extensão do Looker e o SDK de extensão para React cumprem todos estes requisitos.
Numa extensão, use o SDK Looker 4.0 e o componente ExtensionProvider2
ou ExtensionProvider40
do SDK de extensão para React.
O seguinte código configura o fornecedor de extensões. Tem de indicar ao fornecedor o SDK que 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 (
<ExtensionProvider2 type={Looker40SDK}>
<MyExtension />
</ExtensionProvider2>
)
})
Não use o seguinte estilo de importação na sua extensão:
import * as lookerComponents from `@looker/components`
O exemplo anterior importa tudo do módulo. Em vez disso, importe apenas os componentes de que realmente precisa. Por exemplo:
import { Paragraph } from `@looker/components`
Glossário
- Divisão de código: uma técnica para o carregamento lento de JavaScript até ser realmente necessário. Idealmente, deve manter o pacote JavaScript carregado inicialmente o mais pequeno possível. Isto pode ser conseguido através da divisão de código. Qualquer funcionalidade que não seja imediatamente necessária não é carregada até ser realmente necessária.
- IDE: ambiente de programação integrado. Um editor usado para criar e modificar uma extensão. Alguns exemplos são o Visual Studio Code, o Intellij e o WebStorm.
- Cena: geralmente, uma visualização de página no Looker. As cenas são mapeadas para trajetos principais. Por vezes, uma cena tem cenas secundárias que são mapeadas para subrotas dentro da rota principal.
- Transpilação: o processo de usar código fonte escrito num idioma e transformá-lo noutro idioma com um nível de abstração semelhante. Um exemplo é a conversão de TypeScript para JavaScript.