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 に設定します。

  • [アプリの統合] で [Classroom アドオン] を選択します。安全なアタッチメントのセットアップ 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 の [ストアの掲載情報] ページに移動します。 次の情報を入力します。

  • [App Details] で、言語を追加するか、すでにリストされている言語の横にあるプルダウンを展開します。アプリケーションの名前と説明を入力します。これらは、アドオンの Google Workspace Marketplace ストアの掲載情報ページに表示されます。[完了] をクリックして保存します。
  • アドオンの [カテゴリ] を選択します。
  • [グラフィック アセット] の必須項目に画像を指定します。これらは後で変更でき、ここではプレースホルダにすることができます。
  • [Support Links] で、リクエストされた URL を入力します。前のステップで [App Visibility] を [Private] に設定している場合、これらはプレースホルダになります。

前の手順で [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 つのボタン(アドオン 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.")

どちらのルートも、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 モジュールにあります。モジュールを使用してプロジェクトを実装することは想定されていません。チュートリアルの各ステップを実施しながら、単一のプロジェクト上でビルドすることをおすすめします。

このサンプル プロジェクトでは、エンドポイントを定義するコントローラ クラス 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";
}

アドオンをテストする

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

お疲れさまでした。次のステップ(Google SSO でのユーザーのログイン)に進む準備が整いました。