拡張フレームワークの React と JavaScript のコードサンプル

このページでは、拡張機能で使用する一般的な関数について、React と JavaScript で記述されたコードサンプルを示します。

Looker Extension SDK の使用

Looker Extension SDK から関数を追加するには、最初に SDK への参照を取得する必要があります。これはプロバイダから、またはグローバルに実行できます。その後、任意の JavaScript アプリケーションの場合と同じように、SDK 関数を呼び出すことができます。

  • プロバイダから SDK にアクセスするには:
  import { ExtensionContext2 } from '@looker/extension-sdk-react'

  export const Comp1 = () => {
    const extensionContext = useContext(
      ExtensionContext2
    )
    const { extensionSDK, coreSDK } = extensionContext
  • SDK にグローバルにアクセスするには(この呼び出しの前に拡張機能を初期化する必要があります)。
    const coreSDK = getCoreSDK2()

これで、あらゆる JavaScript アプリケーションと同様に SDK を使用できるようになりました。

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

この拡張機能はサンドボックス化された iframe で実行されるため、親の window.location オブジェクトを更新して Looker インスタンス内の他の場所に移動することはできません。Looker Extension SDK を使用して移動できます。

この関数を使用するには navigation の利用資格が必要です。

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

. . .

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

. . .

  extensionSDK.updateLocation('/browse')

新しいブラウザ ウィンドウを開く

拡張機能はサンドボックス化された iframe で実行されるため、親ウィンドウを使用して新しいブラウザ ウィンドウを開くことはできません。Looker Extension SDK を使ってブラウザ ウィンドウを開くことはできます。

この関数は、現在の Looker インスタンス内の場所に対して新しいウィンドウを開くための new_window の利用資格、または別のホストで実行される新しいウィンドウを開く new_window_external_urls の利用資格のいずれかが必要です。

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

ルーティングとディープリンク

React ベースの拡張機能には次のものが対象となります。

ExtensionProviderExtensionProvider2 のコンポーネントは、MemoryRouter という React Router を自動的に作成します。サンドボックス化された iframe では機能しないため、BrowserRouter は作成しないでください。HashRouter は作成しないでください。Microsoft Edge ブラウザの非 Chromium ベースのバージョン用サンドボックス化された iframe では動作しないためです。

MemoryRouter が利用されており、拡張機能で react-router を使用している場合、拡張機能フレームワークは拡張機能のルーターを Looker ホストルーターに自動的に同期します。つまり、ページが再読み込みされたときやブラウザの前後のボタンがクリックされたときに、現在のルートが拡張機能に通知されるということです。これは、広告表示オプションがディープリンクを自動的にサポートしていることを意味します。react-router の使用方法については、拡張機能の例をご覧ください。

拡張機能のコンテキスト データ

拡張機能フレームワークのコンテキスト データを、React コンテキストと混同しないでください。

拡張機能は、拡張機能のすべてのユーザー間でコンテキスト データを共有できます。コンテキスト データは、頻繁には変更されず、特別なセキュリティ要件がないデータに使用できます。データロックは行わず、最後の書き込みが有効になるため、データを書き込むときは注意が必要です。コンテキスト データは、起動時にすぐに拡張機能で使用できます。Looker Extension SDK は、コンテキスト データのアップデートと更新を可能にする関数を提供します。

コンテキスト データの最大サイズは約 16 MB です。コンテキスト データは JSON 文字列にシリアル化されるため、拡張子にコンテキスト データを使用する場合は同様に考慮する必要があります。

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

ユーザー属性

Looker Extension SDK には、Looker のユーザー属性にアクセスするための API が用意されています。ユーザー属性アクセスには、次の 2 種類があります。

  • 対象範囲 - 拡張機能に関連付けられます。対象範囲のユーザー属性は拡張機能に名前空間であり、使用する前に Looker インスタンスでユーザー属性を定義する必要があります。ユーザー属性に名前空間を設定するには、属性名の前に拡張機能名を付加します。ユーザー属性名にダッシュとコロンを使用できないため、拡張名に含まれるダッシュと「::」はアンダースコアで置き換える必要があります。

    たとえば、対象範囲のユーザー属性名 my_valuemy-extension::my-extension という拡張機能 ID と一緒に使用するには、ユーザー属性名に my_extension_my_extension_my_value を定義する必要があります。定義したユーザー属性は、拡張機能によって読み取られ、更新されます。

  • グローバル - グローバル ユーザー属性であり、読み取り専用です。サンプルは、locale ユーザー属性です。

以下は、ユーザー属性 API 呼び出しのリストです。

  • userAttributeGetItem - ユーザー属性を読み取ります。デフォルト値を定義することができ、ユーザーのユーザー属性値が存在しない場合に使用されます。
  • userAttributeSetItem - 現在のユーザーのユーザー属性を保存します。グローバル ユーザー属性の場合は失敗します。保存された値は現在のユーザーにのみ表示されます。
  • userAttributeResetItem - 現在のユーザーのユーザー属性をデフォルト値にリセットします。グローバル ユーザー属性の場合は失敗します。

ユーザー属性にアクセスするには、global_user_attributes または scoped_user_attributes の利用資格で属性名を指定する必要があります。たとえば、LookML のプロジェクト マニフェスト ファイルには、次のものを追加します。

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

ローカル ストレージ

サンドボックス化された iframe では、ブラウザのローカル ストレージにアクセスできません。Looker Extension SDK を使用すると、拡張機能で親ウィンドウのローカル ストレージへの読み書きが可能になります。ローカル ストレージは拡張機能に名前空間化されているため、親ウィンドウまたは他の拡張機能によって作成されたローカル ストレージを読み取ることはできません。

ローカル ストレージを使用するには、local_storage の利用資格が必要です。

拡張機能の localhost API は、同期ブラウザのローカル ストレージ API ではなく非同期です。

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

ページタイトルの更新

拡張機能では現在のページのタイトルが更新されることがあります。この操作のための利用資格は必要ありません。

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

. . .

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

  extensionSDK.updateTitle('My Extension Title')

システム クリップボードへの書き込み

サンドボックス化された iframe では、システム クリップボードにアクセスできません。Looker Extension SDK では、拡張機能がシステムのクリップボードにテキストを書き込むことができます。セキュリティ上の理由から、拡張機能はシステム クリップボードから読み取ることはできません。

システム クリップボードに書き込むには、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) {
      . . .
    }

ダッシュボード、Look、Explore の埋め込み

この拡張機能フレームワークでは、ダッシュボード、Look、Explore の埋め込みをサポートしています。

use_embeds エンタイトルメントは必須です。コンテンツを埋め込むには、Looker JavaScript Embedded SDK を使用することをおすすめします。詳しくは、埋め込み SDK のドキュメントをご覧ください。

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

この拡張機能の例では、スタイル設定されたコンポーネントを使用して、生成された iframe にシンプルなスタイルを提供します。例:

import styled from "styled-components"

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

外部 API エンドポイントへのアクセス

拡張機能フレームワークには、外部 API エンドポイントにアクセスするための 2 つのメソッドがあります。

  • サーバー プロキシ - Looker サーバー経由でエンドポイントにアクセスします。このメカニズムにより、Looker ID によってクライアント ID とシークレット キーを安全に設定できます。
  • フェッチ プロキシ - ユーザーのブラウザからエンドポイントにアクセスします。プロキシは Looker UI です。

どちらの場合も、拡張機能 external_api_urls の利用資格で外部 API エンドポイントを指定する必要があります。

サーバー プロキシ

次の例は、サーバー プロキシを使用して、取得プロキシによって使用するアクセス トークンを取得する方法を示しています。クライアント ID と Secret は、拡張機能のユーザー属性として定義する必要があります。通常、ユーザー属性が設定されている場合、デフォルト値はクライアント ID または Secret に設定されます。

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

ユーザー属性名は拡張子にマッピングする必要があります。ダッシュはアンダースコアに置き換え、:: 文字は単一のアンダースコアに置き換える必要があります。

たとえば、拡張機能の名前が my-extension::my-extension の場合、上記の例で定義する必要があるユーザー属性は次のようになります。

my_extension_my_extension_my_client_id
my_extension_my_extension_'my_client_secret'

取得プロキシ

取得プロキシの使用例を次に示します。前述のサーバー プロキシの例のアクセス トークンを使用します。

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

. . .

  }

OAuth の統合

この拡張機能フレームワークは OAuth プロバイダとの統合をサポートしています。OAuth を使用して、特定のリソース(Goorgle シートのドキュメントなど)にアクセスするためのアクセス トークンを取得できます。

OAuth サーバーのエンドポイントは extension oauth2_urls の利用資格で指定する必要があります。external_api_urls の利用資格の追加の URL の指定が必要な場合もあります。

拡張フレームワークは次のフローをサポートしています。

  • 暗黙的フロー
  • 秘密鍵を使用した認証コード権限付与タイプ
  • PKCE コードの課題と検証ツール

一般的なフローは、子ウィンドウを開き、OAuth サーバーページを読み込むことです。OAuth サーバーはユーザーを認証し、アクセス トークンの取得に使用できる詳細情報を使用して、Looker サーバーにリダイレクトします。

暗黙的フロー:

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

秘密鍵を使用した認証コード権限付与タイプ:

  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

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

Spartan

Spartan は、指定されたユーザーのセットに拡張機能(拡張機能のみ)を公開する環境として Looker インスタンスを使用する方法を指します。Looker インスタンスに移動する Spartan ユーザーは、Looker 管理者が構成したログインフローと一緒に表示されます。ユーザーが認証されると、次のように landing_pageユーザー属性に従って拡張機能がユーザーに提示されます。ユーザーは拡張機能のみにアクセスできます。Looker の他の部分にはアクセスできません。ユーザーが複数の拡張機能にアクセスできる場合、これらの拡張機能は extensionSDK.updateLocation を使用してユーザーが他の拡張機能に移動できるかどうかを制御します。ユーザーが Looker インスタンスからログアウトできるようにするために、特定の Looker Extension SDK メソッドが 1 つあります。

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

Spartan ユーザーの定義

簡潔ユーザーを定義するには、「Extensions Only」というグループを作成する必要があります

「Extensions Only」グループが作成されたら、Looker の [Admin] セクションの [User Attributes] ページに移動して、landing_page ユーザー属性を編集します。[Group Values] タブを選択し、「Extensions Only」グループを追加します 値は /spartan/my_extension::my_extension/ に設定する必要があります。ここで、my_extension::my_extension は拡張機能の ID です。これで、ユーザーがログインすると、指定した拡張機能にユーザーがルーティングされるようになります。

コード分割

コード分割は、必要な場合にのみコードをリクエストする手法です。通常、コードチャンクは、各ルートが独自のコードチャンクを取得する React ルートに関連付けられます。React では、このために Suspense コンポーネントと React.lazy コンポーネントを使用します。Suspense コンポーネントには、コードチャンクの読み込み中にフォールバック コンポーネントが表示されます。React.lazy はコードチャンクの読み込みを行います。

コード分割の設定:

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>

遅延読み込みコンポーネントは、次のように実装されます。

import { lazy } from 'react'

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

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

コンポーネントは次のように実装されます。このコンポーネントは、デフォルトのコンポーネントとしてエクスポートする必要があります。

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

export default Comp1

ツリー シェイキング

Looker SDK ではツリー シェイクがサポートされていますが、まだ完全ではありません。Google はツリー シェイキングのサポートを改善するため SDK の変更を絶えず行っています。一部の変更では、コードを利用できるようにリファクタリングする必要がありますが、必要な場合はリリースノートに記載されています。

使用するモジュールのツリー シェイキングを利用するには、ES モジュールとしてエクスポートする必要があります。また、インポートする関数に副作用がないようにしてください。Looker SDK for TypeScript/JavaScriptLooker SDK ランタイム ライブラリLooker UI コンポーネントLooker Extension SDKExtension SDK for React のすべてがこれを行います。

拡張機能では、Looker SDK(3.1 または 4.0)のいずれかを選択し、Extension SDK for ReactExtensionProvider2 コンポーネントを使用する必要があります。両方の SDK が必要な場合は、ExtensionProvider コンポーネントを引き続き使用しますが、最終的なバンドルサイズが増加します。

次のコードで、拡張機能プロバイダを設定します。使用する SDK をプロバイダに指示する必要があります。

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

拡張機能で次のインポート スタイルを使用しないでください。

import * as lookerComponents from `@looker/components`

上記の例では、モジュールからすべてのものが渡されています。代わりに、実際に必要なコンポーネントのみをインポートします。例:

import { Paragraph }  from `@looker/components`

用語集

  • コード分割 - JavaScript を実際に必要になるまで遅延読み込みする手法です。最初に読み込んだ JavaScript バンドルをできるだけ小さくすることをおすすめします。これは、コード分割を利用することで実行できます。すぐに必要ではない機能は、実際に必要になるまで読み込まれません。
  • IDE — 統合開発環境拡張機能の作成と変更に使用するエディタ。たとえば、Visual Studio Code、Intellij、WebStorm などです。
  • シーン - 通常は Looker のページビューです。シーンは主要なルートにマッピングされます。主要なルートのサブルートにマッピングされる子シーンが、シーンに含まれていることがあります。
  • トランスパイル - 1 つの言語で書かれたソースコードを、類似した抽象化レベルの別の言語に変換するプロセス。たとえば、TypeScript to JavaScript などです。