SharePointにチャットボットを組み込んでみる

SharePointの画面にチャットボットを組み込みたいという話があったので、[前に作ったチャットボット](https://qiita.com/megasys1968/items/58ae9d9c8154dd33cfb3)を流用して実現してみました。
その手順や気づいたことを書き留めようと思います。

## はじめに
SharePointに自分で機能を追加するのに[SharePoint Framework](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/sharepoint-framework-overview)が用意されてました。
SharePoint Frameworkでは、以下の種類のコンポーネントを開発できるそうです。

* Webパーツ
    * ページ内に表示される部品。
* 拡張機能
    * アプリケーション・カスタマイザ
        * スクロールしても表示が残るヘッダやフッタなど。
    * フィールド・カスタマイザ
        * リストの列の見た目を変える。
    * ListViewコマンド・セット
        * リストにボタンを追加して、処理を実行できるようにする。
* ライブラリ
    * 他のコンポーネントから利用できる部品。

今回はアプリケーション・カスタマイザで開発するのが良いと判断しました。
この記事ではSharePoint Frameworkのバージョン1.12.1を使用しています。

## 開発環境のセットアップ
[この手順](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/set-up-your-development-environment)に従ってGulp, Yeoman, YeomanのSharePointジェネレーターをインストール。
Node.jsはサポートされているバージョンがインストール済みだったので割愛。

```
$ npm install gulp yo @microsoft/generator-sharepoint --global
```

## プロジェクトの作成

[この手順](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/extensions/get-started/build-a-hello-world-extension)で、まずプロジェクト用のchatbotディレクトリを作成し、そのディレクトリ配下で以下のコマンドを実行。

```
$ yo @microsoft/sharepoint

     _-----_
    |       |    .--------------------------.
    |--(o)--|    |      Welcome to the      |
   `---------´   |  SharePoint Client-side  |
    ( _´U`_ )    |         Solution         |
    /___A___\    |     Generator@1.12.1     |
     |  ~  |     '--------------------------'
   __'.___.'__
 ´   `  |° ´ Y `

Let's create a new SharePoint solution.
? What is your solution name? chatbot
? Which baseline packages do you want to target for your component(s)? SharePoint Online only (latest)
? Where do you want to place the files? Use the current folder
Found npm version 6.14.11
? Do you want to allow the tenant admin the choice of being able to deploy the solution to all sites immediately without running any feature deployment or adding apps in sites? No
? Will the components in the solution require permissions to access web APIs that are unique and not shared with other components in the tenant? No
? Which type of client-side component to create? Extension
? Which type of client-side extension to create? Application Customizer
Add new Application Customizer to solution chatbot.
? What is your Application Customizer name? Chatbot
? What is your Application Customizer description? User assistant.
```

* 拡張機能の手順に、Do you want to allow the tenant admin〜とWill the components in the solution require permissions〜は載ってませんでしたが、[Webパーツの方](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part)にあったので、それに従ってデフォルトのNにしました。

なお、最初にプロジェクトを作成した時だけ、以下のコマンドで[開発者用自己署名証明書の信頼](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/set-up-your-development-environment#trusting-the-self-signed-developer-certificate)を行います。

```
gulp trust-dev-cert
```

## フッタの実装
[プレースホルダーのサンプル](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/extensions/get-started/using-page-placeholder-with-extensions)を参考に、まずは中身のないフッタだけを作成します。

```scss:/src/extensions/chatbot/ChatbotCustomizer.module.scss
.app {
  position: relative;
  height: 1px;
  display: flex;
}
```

```typescript:/src/extensions/chatbot/ChatbotApplicationCustomizer.ts
import { override } from '@microsoft/decorators';
import {
  BaseApplicationCustomizer, PlaceholderContent, PlaceholderName
} from '@microsoft/sp-application-base';

import styles from './ChatbotCustomizer.module.scss';

/**
 * If your command set uses the ClientSideComponentProperties JSON input,
 * it will be deserialized into the BaseExtension.properties object.
 * You can define an interface to describe it.
 */
export interface IChatbotApplicationCustomizerProperties {
}

/** A Custom Action which can be run during execution of a Client Side Application */
export default class ChatbotApplicationCustomizer
  extends BaseApplicationCustomizer<IChatbotApplicationCustomizerProperties> {

  private _bottomPlaceholder: PlaceholderContent | undefined;

  @override
  public onInit(): Promise<void> {
    this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
    return Promise.resolve();
  }

  private _renderPlaceHolders(): void {
    if (!this._bottomPlaceholder) {
      this._bottomPlaceholder = this.context.placeholderProvider.tryCreateContent(PlaceholderName.Bottom);

      if (this._bottomPlaceholder.domElement) {
        this._bottomPlaceholder.domElement.innerHTML
          = `<div class="${styles.app}"></div>`;
      }
    }
  }
}
```
* VS Code上で編集していると、最初はscssファイルをインポートする部分でエラーが発生しますが、以降の手順でビルドが行われると、解消されます。

## テスト
serve.jsonのpageUrlを拡張機能を組み込む予定のSharePointのURLに変更し、プロパティは使わないので削除します。

```json:/config/serve.json
{
  "$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
  "port": 4321,
  "https": true,
  "serveConfigurations": {
    "default": {
      "pageUrl": "https://hogehoge.sharepoint.com/sites/portal",
      "customActions": {
        "da62dff0-4c91-4aed-a978-0e4a95fa289b": {
          "location": "ClientSideExtension.ApplicationCustomizer"
        }
      }
    },
    "chatbot": {
      "pageUrl": "https://hogehoge.sharepoint.com/sites/portal",
      "customActions": {
        "da62dff0-4c91-4aed-a978-0e4a95fa289b": {
          "location": "ClientSideExtension.ApplicationCustomizer"
        }
      }
    }
  }
}
```

* VS Codeで開くと、`Property serveConfigurations is not allowed.`って警告が表示されるけど、指定したURLとか正常に機能しているから、気にしないで良さそうです。


そして```gulp serve```を実行するとブラウザにページが表示されて、以下の確認画面が表示されるので、[デバッグ スクリプト を読み込む]をクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387890/12f4d578-c069-5002-dd79-9ce16a0b7aaf.png)

* ビルドされたJavaScript等が、gulp serveによりポート4321で公開され、ここで読み込まれるという仕組みのようです。

すると画面上では何も見えませんが、DOMツリー上で高さ1pxのフッタが追加されていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387890/4f14490e-b315-a4dc-3246-a24b490444db.png)


## チャットボットを組み込む
今回のアプローチとしてチャットボットのコードをプロジェクトに移植するのではなく、[こちら](https://github.com/kongmengfei/SharedSPFx/blob/main/app-extension-js/src/extensions/helloWorld/HelloWorldApplicationCustomizer.ts)を参考に、Svelteで作ったチャットボットのjsファイルをそのまま組み込むことにしました。

実装は簡単で、jsファイルをプロジェクト内に配置し、カスタマイザのonInitメソッド内でrequireします。

```typescript:/src/extensions/chatbot/ChatbotApplicationCustomizer.ts
  @override
  public onInit(): Promise<void> {
    require('../../../assets/bundle.js');

    this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);
    return Promise.resolve();
  }

  private _renderPlaceHolders(): void {
    // 割愛
        this._bottomPlaceholder.domElement.innerHTML = `
          <div class="${styles.app}">
            <chat-frame title="お問い合わせ" closeclass="${styles.close}"></chat-frame>
          </div>`;
```

あとはチャットボットのスタイルをChatbotCustomizer.module.scssに指定します。

* CSSのクラス名はカプセル化(例:app→app_22ddff86)されるので、クラス名を直に指定するのではなく、${styles.app}のように変数で指定する。

## リリース版のビルドをする

[これ](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/extensions/get-started/serving-your-extension-from-sharepoint)と[これ](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/extensions/get-started/hosting-extension-from-office365-cdn)の手順を参考します。

まず、手順と同様に、サイトレベルに拡張機能をリリースすることにして、/config/package-solution.jsonからClientSideInstance.xmlへの指定を削除します。

そして、以下を実行してリリース版のビルドを行います。

```
gulp bundle --ship
gulp package-solution --ship
```

## アプリカタログにリリースする

ビルドでできあがった/sharepoint/solution/chatbot.sppkgをアプリカタログにリリースします。

* まだアプリカタログがない場合は、[ここ](https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/set-up-your-developer-tenant#create-app-catalog-site)を参照。
    * ただ、[Web サイトのアドレス サフィックス]を例のまま「アプリ」にしたら、うまくリリースできない現象に遭遇しました。URLに使われるので英語が無難なようです。

* 手順では分かりづらいですが、アプリカタログのサイトで[SharePoint用アプリ]をクリックして、sppkgファイルをドラッグ&ドロップします。  
　　　　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387890/fb76ef37-7a33-b73d-a602-ba646e8f3c2b.png)

## サイトにインストールする

アプリをインストールするサイトの[サイトコンテンツ]ページを開き、[新規]から[アプリ]をクリックします。
表示されたページからアプリをクリックすると、インストールが行われます。

## 気づいたこと

* スタイルにopacityを使うと、ビルド後は指定した値が無視されて1%(透明になって見えなくなる)になります。
* アプリを削除したい場合、サイトコンテンツをクラシック表示する必要があります。
* アプリをバージョンアップしたい場合、/config/package-solution.json内のversionを更新して、アプリカタログに再リリースします。
    * サイトにインストールているアプリは旧バージョンのままなので、サイトコンテンツでアプリの詳細情報を見ると、新バージョンの取得ができる。

        ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387890/6ed80e05-a53e-c753-c239-a3f4a0be7ebd.png)

    * ただ、何回か再リリースするうちに、アプリが表示されなくなることがありました。その時は、アプリカタログから削除して、package-solution.jsonのidをちょっと変更してリリースすることで対処できましたが、このあたりは要継続調査。