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 の [アプリの構成] ページに移動します。次の情報を入力します。

  • [アプリの公開設定] を 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/ など)を少なくとも 1 つ追加します。

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

  • [Developer Links] で、組織に適したフィールドに入力します。

ストアの掲載情報

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

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

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

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 の [ストアの掲載情報] ページの上部にあるリンクを使用して、アドオンをインストールできるようになりました。ページ上部の [View In Marketplace] をクリックしてリスティングを表示し、[Install] を選択します。

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

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

iframe 内のウェブアプリの例

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

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

ウィンドウまたは 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.")

両方のルートが、それぞれの Jinja テンプレートに message 変数を渡すことに注意してください。これは、ユーザーがどのページに到達したかを特定するのに役立ちます。

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

アプリケーションのルート ディレクトリに 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 モジュールにあります。モジュールを使用してプロジェクトを実装することは想定されていません。チュートリアルの各ステップに沿って、単一のプロジェクトを構築することをおすすめします。

エンドポイントを定義するコントローラ クラス(このサンプル プロジェクトでは 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 を使用したユーザーのログイン)に進む準備ができました。