プリザンターの通知をMicrosoftTeamsにカード型で通知してみる

#  プリザンターの通知をMicrosoftTeamsにカード型で通知してみる
## プリザンターとは
> Pleasanter（プリザンター）はオープンソースのビジネスアプリケーションプラットフォームです。**顧客管理**や**営業支援**、**プロジェクト管理**など様々な業務をノンプログラミングでWebデータベース化することができます。動作が軽く、汎用性が高いため、日常的なコミュニケーションやマネジメントの支援ツールとして利用できます。

* [https://github.com/Implem/Implem.Pleasanter](https://github.com/Implem/Implem.Pleasanter)参照
    * Qiita内では、`＠Implem`さんの記事です。
        * [大きな組織で情報共有するためのOSSつくってみた](https://qiita.com/Implem/items/1d815f93719d4129dc4f)


### ダウンロード
セットアップモジュールのダウンロードサイトです。
http://pleasanter.org

## MicrosoftTeamsの通知方法
## 設定手順
* 実は、通知手順は、プリザンターでしっかりと説明されているので**説明は省きます。**
* **以下のサイトをよく確認の上、作業してください。**
    * [Microsoft Teamsに通知する場合の設定](https://github.com/Implem/Implem.Pleasanter/wiki/Microsoft-Teams%E3%81%AB%E9%80%9A%E7%9F%A5%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E8%A8%AD%E5%AE%9A)
        * 簡単説明
            * MicrosoftTeamsに`Incoming Webhookコネクタ`の追加
            * コネクタのURLの取得
            * プリザンターの通知設定
* 上記手順だけで、MicrosoftTeamsへは通知することができます。
* でも、**そのままだと通知が見にくいので、カードタイプの通知をしてみるってのが今回の目的**です。

* もし、Teamsのストアになかった場合は、`Incoming Webhook コネクタ`がシステム管理者に許可されていない場合がありますので、システム管理者に連絡してストアアプリの許可をしてもらってください。
    * https://docs.microsoft.com/ja-jp/microsoftteams/admin-settings

* 上記の手順のままだとつらつらと表示される通知で誰が更新したのかもわからない状態なので、Azure DevOpsの通知のようにカード形式で実装します。
* 例
 ![WS000000638.JPG](https://qiita-image-store.s3.amazonaws.com/0/90597/b3b338dd-f88f-f689-e363-871ed685a424.jpeg)





## 通知方式
### アダプティブ カード形式
* [アダプティブ カード形式が使用される Outlook のアクション可能メッセージ カードのデザイン](https://docs.microsoft.com/ja-jp/outlook/actionable-messages/adaptive-card)参照

> Office 365 コネクタは、現時点ではアダプティブ カード形式をサポートしていません。 Office 365 コネクタを実装している場合は、MessageCard 形式のリファレンスを参照してください。

ということで、**やっぱり旧型式のメッセージカード使って、MicrosoftTeamsへ通知をします。**

### メッセージ カード形式
* [従来の操作可能なメッセージ カード リファレンス](https://docs.microsoft.com/ja-jp/outlook/actionable-messages/message-card-reference)参照

> カードはひとめでわかる読みやすい情報を提供し、ユーザーがすぐに理解して、該当する場合にそれを基に行動を起こせるようであるべきです。したがって、優れたカードをデザインするための指針は「クロムよりもコンテンツ」です。つまり、カードは要点をついたものとし、アイコンやカスタム色などの煩雑になりかねないものの使用は最小限に抑えるということです。


となっていますが、注意書きに以下のような記載があります。

> 注意
>> このドキュメントでは、操作可能なメッセージ カード形式の元の JSON 形式について説明します。 これは、アダプティブ カード形式に置き換えられました。 Microsoft は新しい操作可能なメッセージの統合には、アダプティブ カード形式の使用をおすすめします。既存の統合は、アダプティブ カード形式への更新を検討してください。

と書いてあるので、**将来的にはアダプティブカード形式に変更する必要がありそうです。**


## ソースコードの修正
### Notification.csの修正

* 修正前

```csharp:Implem.Pleasanter/Libraries/Settings/Notification.cs
case Types.Teams:
	if (Parameters.Notification.Teams)
	{
	    new Teams(context,"*{0}{1}*\n{2}\n{3}".Params(Prefix, title, url, body).Send(Address);
	}
```

* 修正後

```csharp:Implem.Pleasanter/Libraries/Settings/Notification.cs
case Types.Teams:
	if (Parameters.Notification.Teams)
	{
	    new Teams(context, Prefix, title, url, body).Send(Address);
	}
```

### Teams.csの修正
#### カード用のJSONクラスの作成
* 別のクラスファイルで行った方が良いかと思いますが、今回は簡易的な作業なため、同じファイル内に記述します。

```csharp:Implem.Pleasanter/Libraries/DataSources/Teams.cs()
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Implem.Pleasanter.Libraries.DataSources
{
    public class Teams
    {
    // ～～中略～～
    // ～～ここに追記～～
        public class CardObject
        {

            [JsonProperty("@context")]
            public string context { get; set; }

            [JsonProperty("@type")]
            public string type { get; set; }

            [JsonProperty("summary")]
            public string summary { get; set; }

            [JsonProperty("themeColor")]
            public string themeColor { get; set; }

            [JsonProperty("title")]
            public string title { get; set; }

            [JsonProperty("sections")]
            public List<CardSection> sections { get; set; }

            [JsonProperty("Potentialaction")]
            public List<PotentialAction> potentialAction { get; set; }
        }

        public class CardSection
        {
            [JsonProperty("activityTitle")]
            public string activityTitle { get; set; }
            [JsonProperty("activitySubtitle")]
            public string activitySubtitle { get; set; }
            [JsonProperty("text")]
            public string text { get; set; }
            [JsonProperty("activityImage")]
            public string activityImage { get; set; }

        }


        public class PotentialAction
        {
            [JsonProperty("@type")]
            public string type { get; set; }
            [JsonProperty("name")]
            public string name { get; set; }
            [JsonProperty("targets")]
            public List<CardTarget> targets { get; set; }
        }

        public class CardTarget
        {
            [JsonProperty("os")]
            public string os { get; set; }
            [JsonProperty("uri")]
            public string uri { get; set; }
        }

    }
}    
```

#### カード用のJSONクラスの宣言

* 修正前

```csharp:Implem.Pleasanter/Libraries/DataSources/Teams.cs()
    public class Teams
    {
        public Context context;
        public string text;
```

* 修正後

```csharp:Implem.Pleasanter/Libraries/DataSources/Teams.cs()
    public class Teams
    {
        public Context context;
        public string text;
        public CardObject cardObject; //追記
```

#### メッセージカードクラスへ格納

* 修正前

```csharp:Implem.Pleasanter/Libraries/DataSources/Teams.cs
public Teams(Context , _text)
{
    context = _context;
    text = _text; //削除予定
}
```

* 修正後

```csharp:Implem.Pleasanter/Libraries/DataSources/Teams.cs
    public Teams(Context _context, string _Prefix, string _title, string _url, string _body)
    {
        context = _context;

        cardObject = new CardObject();
        var cardSection = new List<CardSection>();
        var potentialAction = new List<PotentialAction>();
        var cardTarget = new List<CardTarget>();

        cardSection.Add(new CardSection()
        {
            activityTitle = _title,
            activitySubtitle = "Updated by " + context.User.Name.Trim(),
            text = _body.Replace("\n", "\n\n")
            activityImage = "",
        });

        cardTarget.Add(new CardTarget
        {
            os = "default",
            uri = _url
        });

        potentialAction.Add(new PotentialAction()
        {
            type = "OpenUri",
            name = "View",
            targets = cardTarget
        });

        cardObject.context = "https://schema.org/extensions";
        cardObject.type = "MessageCard";
        cardObject.summary = _Prefix;
        cardObject.themeColor = "0078D7";
        cardObject.sections = cardSection;
        cardObject.potentialAction = potentialAction;

    }
```

#### JSONシリアライズ

* 修正前

```csharp:Implem.Pleasanter/Libraries/Settings/Notification.cs
public void Send(string address)
{
    foreach (var url in address.Split(',').Select(x => x.Trim()))
    {
        Task.Run(() =>
        {
            try
            {
                using (var client = new WebClient())
                {

                    string json = Newtonsoft.Json.JsonConvert.SerializeObject(new { text }); //ここを修正予定
                    client.Headers[HttpRequestHeader.ContentType] = "application/json;charset=UTF-8";
                    client.Headers[HttpRequestHeader.Accept] = "application/json";
                    client.Encoding = Encoding.UTF8;
                    client.UploadString(url, "POST", json);
                }
            }
            catch (Exception e)
            {
                new SysLogModel(context, e);
            }
        });
    }
}
```

* 修正後

```csharp:Implem.Pleasanter/Libraries/Settings/Notification.cs
public void Send(string address)
{
    foreach (var url in address.Split(',').Select(x => x.Trim()))
    {
        Task.Run(() =>
        {
            try
            {
                using (var client = new WebClient())
                {

                    string json = Newtonsoft.Json.JsonConvert.SerializeObject(cardObject); //修正
                    client.Headers[HttpRequestHeader.ContentType] = "application/json;charset=UTF-8";
                    client.Headers[HttpRequestHeader.Accept] = "application/json";
                    client.Encoding = Encoding.UTF8;
                    client.UploadString(url, "POST", json);
                }
            }
            catch (Exception e)
            {
                new SysLogModel(context, e);
            }
        });
    }
}
```


## 完成
* あとは、WebDeployでAzureに発行すれば、完成です。
* 通知のイメージは以下のようになります。
    * Viewボタンを押せば、プリザンターのEditページを開けるようにしました。
    * 更新者名も表示するようにしました。

![WS000000640.JPG](https://qiita-image-store.s3.amazonaws.com/0/90597/74769aef-488f-583a-c3d9-d97aebd415ba.jpeg)



# まとめ
* `Microsoft Teams`も使用したことがなかったのですが、改めてそちらも覚えることが多々ありそうです。
* いろいろ活用場面がありそうなので、しばらく検証期間としてAzure上に立てて目下検証中です。
* `プリザンター`は、実に素晴らしいソフトウェアです。
    * ただ我儘を言えば、
        * もう少しAPIについて機能追加がされればよいなとは、思います。。。
        * 認証もAzureADの認証が増えればよいなぁとも思います。
        * GitHub上がマスターバージョンのみってのも前のバージョンが落とせないので、バージョンアップさせにくいです。

## 追記（2019/01/08）
* Usingが漏れていたので修正しました。
* Context内にUser情報があったので、パラメータから外しました。