Classroom アドオンを作成する

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

このチュートリアルでは、ウェブ アプリケーションを開発し、Classroom アドオンとして公開するための土台を築きます。以降のチュートリアルでは、このアプリを展開します。

このチュートリアルでは、次のことを完了します。

  • ウェブアプリ用の新しい Google Cloud プロジェクトを作成します。
  • プレースホルダ ログインボタンを含むスケルトン ウェブアプリを作成します。
  • ウェブアプリの限定公開 Google Workspace Marketplace(GWM)ストアの掲載情報を公開する。

完了したら、アドオンをインストールして、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 コンソールで
プロジェクトを選択し

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

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

Google Workspace Marketplace SDK カードは

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

GWM SDK を設定する

GWM から、ユーザーと管理者がアドオンをインストールするリストが提供されます。続行するには、OAuth 同意画面と GWM SDK の [アプリの構成] と [ストアの掲載情報] を設定します。

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 スコープも含めます。

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

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

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

アプリの構成

GWM SDK の [App Configuration] ページに移動します。次の情報を入力します。

  • [App Visibility] を Private に設定します。この設定はテストと開発の目的に適しており、これらのチュートリアルに適しています。アドオンを一般公開する準備ができている場合にのみ、Public を選択してください。

  • インストールをドメイン管理者に制限する場合は、[インストール設定] を Admin Only install に設定します。

  • [アプリの統合] で [Classroom アドオン] を選択します。安全なアタッチメントのセットアップ URI の入力を求められます。これは、ユーザーがアドオンを開いたときに読み込まれる URL です。このチュートリアルでは、https://<your domain>/addon-discovery にする必要があります。

  • 許可されたアタッチメント URI 接頭辞は、courses.*.addOnAttachments.create メソッドと courses.*.addOnAttachments.patch メソッドを使用して AddOnAttachment に設定された URI を検証するために使用されます。この検証はリテラル文字列の接頭辞の一致であり、現時点ではワイルドカードを使用できません。ここでは空欄のままでかまいません。

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

  • [デベロッパー リンク] で、組織の状況に応じて各フィールドに入力します。

ストアの掲載情報

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

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

[公開] をクリックして設定を保存します。前の手順でアプリの公開設定を [非公開] に設定すると、すぐにアプリをインストールできるようになります。[App Visibility] を [Public] に設定すると、アプリはインストール可能になる前に GWM チームによる審査に送られます。

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

これで、GWM 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 above, 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('/classroom-addon', 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 Framework コントローラ アノテーションを追加して、クラスの目的を示します。

@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 が開き、GWM SDK の [アプリの構成] ページで指定したアタッチメントのセットアップ URI がアドオンに読み込まれます。

これで完了です。次のステップ(Google SSO でのユーザーのログイン)に進むことができます。