Classroom アドオンを作成する

これは、Classroom アドオンのチュートリアル シリーズの最初のチュートリアルです。

このチュートリアルでは、ウェブ アプリケーションを開発して Classroom アドオンとして公開するための準備を行います。今後のチュートリアルの手順では、このアプリを拡張します。

このチュートリアルでは、次のことを行います。

  • アドオン用の新しい Google Cloud プロジェクトを作成します。
  • プレースホルダのログイン ボタンを含むスケルトン ウェブアプリを作成します。
  • アドオンの Google Workspace Marketplace ストア リスティングを公開します。

完了したら、アドオンをインストールして Classroom アドオンの iframe に読み込むことができます。

前提条件

言語を選択して、適切な前提条件を確認します。

Python

この Python の例では、Flask フレームワークを使用します。すべてのチュートリアルの完全なソースコードは、[概要] ページからダウンロードできます。このチュートリアルのコードは /flask/01-basic-app/ ディレクトリにあります。

必要に応じて Python 3.7 以降をインストールし、pip が使用可能であることを確認します。

python -m ensurepip --upgrade

また、新しい Python 仮想環境を設定して有効にすることをおすすめします。

python3 -m venv .classroom-addon-env
source .classroom-addon-env/bin/activate

ダウンロードしたサンプルの各チュートリアル サブディレクトリには、requirements.txt が含まれています。pip を使用すると、必要なライブラリをすばやくインストールできます。このチュートリアルに必要なライブラリをインストールするには、次のコマンドを使用します。

cd flask/01-basic-app
pip install -r requirements.txt

Node.js

この Node.js の例では、Express フレームワークを使用しています。すべてのチュートリアルの完全なソースコードは、[概要] ページからダウンロードできます。

必要に応じて、NodeJS v16.13 以降をインストールします。

npm を使用して、必要なノード モジュールをインストールします。

npm install

Java

この Java の例では、Spring Boot フレームワークを使用しています。すべてのチュートリアルの完全なソースコードは、[概要] ページからダウンロードできます。

Java 11 以降がマシンにまだインストールされていない場合は、インストールします。

Spring Boot アプリケーションでは、Gradle または Maven を使用してビルドを処理し、依存関係を管理できます。この例には、Maven 自体をインストールしなくてもビルドが成功するようにする Maven ラッパーが含まれています。

提供されている例を実行するには、プロジェクトをダウンロードしたディレクトリで次のコマンドを実行して、プロジェクトを実行するための前提条件があることを確認してください。

java --version
./mvnw --version

Windows の場合:

java -version
mvnw.cmd --version

Google Cloud プロジェクトの設定

Classroom API へのアクセスと必要な認証方法は、Google Cloud プロジェクトによって制御されます。次の手順では、アドオンで使用する新しいプロジェクトを作成して構成するための最小限の手順について説明します。

プロジェクトを作成する

プロジェクト作成ページにアクセスして、新しい Google Cloud プロジェクトを作成します。新しいプロジェクトには任意の名前を指定できます。[作成] をクリックします。

新しいプロジェクトが完全に作成されるまで数分かかります。完了したら、必ずプロジェクトを選択します。プロジェクトは、画面上部のプロジェクト選択プルダウン メニューで選択できます。または、右上の通知メニューで [プロジェクトを選択] をクリックします。

Google Cloud コンソールでプロジェクトを選択します。

Google Workspace Marketplace SDK を Google Cloud プロジェクトに接続する

API ライブラリ ブラウザに移動します。Google Workspace Marketplace SDK を検索します。結果のリストに SDK が表示されます。

Google Workspace Marketplace SDK カード

[Google Workspace Marketplace SDK] カードを選択し、[有効にする] をクリックします。

Google Workspace Marketplace SDK を構成する

Google Workspace Marketplace には、ユーザーと管理者がアドオンをインストールするためのリスティングが用意されています。Marketplace SDK の [アプリの設定]、[ストアの掲載情報]、[OAuth 同意画面] を設定して、続行します。

アプリの構成

Marketplace SDK の [アプリの設定] ページに移動します。次の情報を入力します。

  • [App Visibility] を Public または Private に設定します。

    • 公開設定は、最終的にエンドユーザーにリリースされるアプリを対象としています。公開アプリは、エンドユーザーに公開する前に承認プロセスを受ける必要がありますが、ドラフトとしてインストールしてテストできるユーザーを指定できます。これは公開前の状態です。承認のために送信する前に、アドオンをテストして開発できます。
    • 非公開設定は、内部テストと開発に適しています。限定公開アプリは、プロジェクトが作成されたドメイン内のユーザーのみがインストールできます。したがって、Google Workspace for Education サブスクリプションを利用しているドメイン内でプロジェクトが作成された場合にのみ公開設定を限定公開に設定してください。そうしないと、テストユーザーが Classroom アドオンを起動できなくなります。
  • インストールをドメイン管理者に制限する場合は、[インストール設定] を Admin Only install に設定します。

  • [App Integration] で [Classroom add-on] を選択します。安全なアタッチメント設定 URI を入力するよう求められます。これは、ユーザーがアドオンを開いたときに読み込まれることを想定している URL です。このチュートリアルでは、https://<your domain>/addon-discovery に設定します。

  • 許可されるアタッチメント URI 接頭辞は、courses.*.addOnAttachments.create メソッドと courses.*.addOnAttachments.patch メソッドを使用して、AddOnAttachment で設定された URI を検証するために使用されます。検証はリテラル文字列の接頭辞照合であり、現時点ではワイルドカードを使用できません。少なくともコンテンツ サーバーのルートドメイン(https://localhost:5000/https://cdn.myedtech.com/ など)を追加します。

  • 前の手順の OAuth 同意画面で指定したOAuth スコープを追加します。

  • [Developer Links] で、組織に応じて各フィールドに値を入力します。

ストアの掲載情報

Marketplace SDK の [ストア掲載情報] ページに移動します。次の情報を入力します。

  • [アプリの詳細] で言語を追加するか、すでに表示されている言語の横にあるプルダウンを開きます。アプリケーション名と説明を入力します。これらは、アドオンの Google Workspace Marketplace ストアの掲載情報ページに表示されます。[完了] をクリックして保存します。
  • アドオンの [カテゴリ] を選択します。
  • [Graphics Assets] で、必須フィールドの画像を指定します。これらは後で変更できます。今はプレースホルダとして使用できます。
  • [サポート リンク] で、リクエストされた URL を入力します。前の手順でアプリの公開設定を [非公開] に設定した場合は、プレースホルダを使用できます。

前の手順で [App Visibility] を [Private] に設定した場合は、[PUBLISH] をクリックすると、アプリはすぐにインストールできるようになります。アプリの公開設定を [公開] に設定した場合は、[ドラフト テスター] 領域にテストユーザーのメールアドレスを追加し、[ドラフトを保存] をクリックします。

OAuth 同意画面は、ユーザーがアプリを初めて承認したときに表示されます。有効にしたスコープに従い、ユーザーの個人情報とアカウント情報にアプリがアクセスすることを許可するように求められます。

OAuth 同意画面の作成ページに移動します。次の情報を提供します。

  • [ユーザーの種類] を [外部] に設定します。[CREATE] をクリックします。
  • 次のページで、必要なアプリの詳細と連絡先情報を入力します。[承認済みドメイン] に、アプリをホストするドメインを入力します。[保存して次へ] をクリックします。
  • ウェブアプリに必要な OAuth スコープを追加します。スコープとその目的の詳細については、OAuth 構成ガイドをご覧ください。

    Google が login_hint クエリ パラメータを送信するには、次のスコープの少なくとも 1 つをリクエストする必要があります。この動作の詳細については、OAuth 設定ガイドをご覧ください。

    • https://www.googleapis.com/auth/userinfo.email(すでに含まれている)
    • https://www.googleapis.com/auth/userinfo.profile(すでに含まれている)

    次のスコープは Classroom アドオンに固有のものです。

    • https://www.googleapis.com/auth/classroom.addons.teacher
    • https://www.googleapis.com/auth/classroom.addons.student

    また、アプリがエンドユーザーに要求する他の Google API スコープも含めます。

    [保存して次へ] をクリックします。

  • [テストユーザー] ページに、テスト アカウントのメールアドレスを一覧表示します。[保存して次へ] をクリックします。

設定が正しいことを確認して、ダッシュボードに戻ります。

アドオンをインストールする

Marketplace SDK の [ストアの掲載情報] ページの上部にあるリンクを使用して、アドオンをインストールできるようになりました。ページ上部の [アプリの URL] をクリックしてリスティングを表示し、[インストール] を選択します。

基本的なウェブアプリを作成する

2 つのルートを持つスケルトン ウェブ アプリケーションを設定します。今後のチュートリアルの手順でこのアプリケーションを拡張しますので、今はアドオン /addon-discovery のランディング ページと「会社サイト」のモック インデックス ページ / を作成します。

iframe 内のウェブアプリの例

次の 2 つのエンドポイントを実装します。

  • /: ウェルカム メッセージと、現在のタブとアドオン iframe の両方を閉じるボタンを表示します。
  • /addon-discovery: ウェルカム メッセージと 2 つのボタンを表示します。1 つはアドオンの iframe を閉じるボタン、もう 1 つはウェブサイトを新しいタブで開くボタンです。

ウィンドウまたは iframe を作成および閉じるボタンが追加されます。次のチュートリアルでは、ユーザーを安全に新しいタブにポップして認可を行う方法を説明します。

ユーティリティ スクリプトを作成

static/scripts ディレクトリを作成します。新しいファイル addon-utils.js を作成します。次の 2 つの関数を追加します。

/**
 *   Opens a given destination route in a new window. This function uses
 *   window.open() so as to force window.opener to retain a reference to the
 *   iframe from which it was called.
 *   @param {string} destinationURL The endpoint to open, or "/" if none is
 *   provided.
 */
function openWebsiteInNewTab(destinationURL = '/') {
  window.open(destinationURL, '_blank');
}

/**
 *   Close the iframe by calling postMessage() in the host Classroom page. This
 *   function can be called directly when in a Classroom add-on iframe.
 *
 *   Alternatively, it can be used to close an add-on iframe in another window.
 *   For example, if an add-on iframe in Window 1 opens a link in a new Window 2
 *   using the openWebsiteInNewTab function, you can call
 *   window.opener.closeAddonIframe() from Window 2 to close the iframe in Window
 *   1.
 */
function closeAddonIframe() {
  window.parent.postMessage({
    type: 'Classroom',
    action: 'closeIframe',
  }, '*');
};

ルートを作成する

/addon-discovery エンドポイントと / エンドポイントを実装します。

Python

アプリケーション ディレクトリを設定する

この例では、アプリケーション ロジックを Python モジュールとして構造化します。これは、提供されている例の webapp ディレクトリです。

サーバー モジュールのディレクトリを作成します(例: webapp)。static ディレクトリをモジュール ディレクトリに移動します。モジュール ディレクトリにも template ディレクトリを作成します。ここに HTML ファイルを配置します。

サーバー モジュールをビルドする*

モジュール ディレクトリに __init__.py ファイルを作成し、次のインポートと宣言を追加します。

from flask import Flask
import config

app = Flask(__name__)
app.config.from_object(config.Config)

# Load other module script files. This import statement refers to the
# 'routes.py' file described below.
from webapp import routes

次に、ウェブアプリのルートを処理するファイルを作成します。これは、提供されている例では webapp/routes.py です。このファイルに 2 つのルートを実装します。

from webapp import app
import flask

@app.route("/")
def index():
    return flask.render_template("index.html",
                                message="You've reached the index page.")

@app.route("/classroom-addon")
def classroom_addon():
    return flask.render_template(
        "addon-discovery.html",
        message="You've reached the addon discovery page.")

どちらのルートも、message 変数をそれぞれの Jinja テンプレートに渡します。これは、ユーザーがアクセスしたページを特定するのに役立ちます。

構成ファイルと起動ファイルを作成する

アプリケーションのルート ディレクトリに main.py ファイルと config.py ファイルを作成します。config.py で秘密鍵を構成します。

import os

class Config(object):
    # Note: A secret key is included in the sample so that it works.
    # If you use this code in your application, replace this with a truly secret
    # key. See https://flask.palletsprojects.com/quickstart/#sessions.
    SECRET_KEY = os.environ.get(
        'SECRET_KEY') or "REPLACE ME - this value is here as a placeholder."

main.py ファイルでモジュールをインポートし、Flask サーバーを起動します。

from webapp import app

if __name__ == "__main__":
    # Run the application over HTTPs with a locally stored certificate and key.
    # Defaults to https://localhost:5000.
    app.run(
        host="localhost",
        ssl_context=("localhost.pem", "localhost-key.pem"),
        debug=True)

Node.js

ルートは、次の行で app.js ファイルに登録されます。

const websiteRouter = require('./routes/index');
const addonRouter = require('./routes/classroom-addon');

app.use('/', websiteRouter);
app.use('/addon-discovery', addonRouter);

/01-basic-app/routes/index.js を開き、コードを確認します。このルートは、エンドユーザーが会社のウェブサイトにアクセスしたときに到達します。このルートは、index Handlebars テンプレートを使用してレスポンスをレンダリングし、title 変数と message 変数を含むデータ オブジェクトをテンプレートに渡します。

router.get('/', function (req, res, next) {
  res.render('index', {
    title: 'Education Technology',
    message: 'Welcome to our website!'
  });
});

2 番目のルート /01-basic-app/routes/classroom-addon.js を開き、コードを確認します。このルートは、エンドユーザーがアドオンにアクセスしたときに到達します。このルートは、discovery Handlebars テンプレートと addon.hbs レイアウトを使用して、会社のウェブサイトとは異なるページをレンダリングします。

router.get('/', function (req, res, next) {
  res.render('discovery', {
    layout: 'addon.hbs',
    title: 'Education Technology Classroom add-on',
    message: `Welcome.`
  });
});

Java

Java コードサンプルでは、モジュールを使用して順序付けられたチュートリアルの手順をパッケージ化しています。これは最初のチュートリアルであるため、コードは step_01_basic_app モジュールにあります。モジュールを使用してプロジェクトを実装することは想定されていません。チュートリアルの各ステップに沿って、1 つのプロジェクトを構築することをおすすめします。

このサンプル プロジェクトでは、コントローラ クラス Controller.java を作成して、エンドポイントを定義します。このファイルで、spring-boot-starter-web 依存関係から @GetMapping アノテーションをインポートします。

import org.springframework.web.bind.annotation.GetMapping;

クラスの目的を示すために、クラス定義の上に Spring フレームワーク コントローラ アノテーションを含めます。

@org.springframework.stereotype.Controller
public class Controller {

次に、2 つのルートと、エラー処理用の追加のルートを実装します。

/** Returns the index page that will be displayed when the add-on opens in a
*   new tab.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the index page template if successful, or the onError method to
*   handle and display the error message.
*/
@GetMapping(value = {"/"})
public String index(Model model) {
  try {
    return "index";
  } catch (Exception e) {
    return onError(e.getMessage(), model);
  }
}

/** Returns the add-on discovery page that will be displayed when the iframe
*   is first opened in Classroom.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the addon-discovery page.
*/
@GetMapping(value = {"/addon-discovery"})
public String addon_discovery(Model model) {
  try {
    return "addon-discovery";
  } catch (Exception e) {
    return onError(e.getMessage(), model);
  }
}

/** Handles application errors.
*   @param errorMessage message to be displayed on the error page.
*   @param model the Model interface to pass error information to display on
*   the error page.
*   @return the error page.
*/
@GetMapping(value = {"/error"})
public String onError(String errorMessage, Model model) {
  model.addAttribute("error", errorMessage);
  return "error";
}

アドオンをテストする

サーバーを起動します。次に、教師のテストユーザーとして Google Classroom にログインします。[授業] タブに移動し、新しい課題を作成します。[アドオン] 選択ツールからアドオンを選択します。iframe が開き、Marketplace SDK の [アプリの設定] ページで指定したアタッチメント設定 URI がアドオンによって読み込まれます。

これで、これで、次のステップである Google SSO によるユーザーのログインに進むことができます。