PowerAppsのTimerを使った繰り返し処理でYammerのメッセージを取得する

# はじめに
この記事はノンプログラミングでアプリが作成できるMicrosoftのPowerAppsを使って、同じくMicrosoftの企業向けSNSであるYammerの特定のグループからメッセージを取得しよう、という内容です。
各コントロールの設定のみ説明し、接続やコントロールの配置などの手順については省いています。ご了承ください。

### え、20件しか取れないの？
同僚から「Yammerの参加者別に『いいね！』された数を集計出来ないかな？」と相談されたので、そんなの簡単じゃん、とPowerAppsでチョチョっと作ったのですが、どうも数が合わない。調べてみると同じ悩みの人がいて、YammerのAPIでは１回の取得で20メッセージまでしか取れず、呼び出し頻度も30秒に10回以内であることがわかりました。**[こちらの記事](https://techcommunity.microsoft.com/t5/Yammer-Developer/Message-In-group-limitation/td-p/5986 "https://techcommunity.microsoft.com/t5/Yammer-Developer/Message-In-group-limitation/td-p/5986")**

では３秒待って繰り返せば良い、ということになりますが、困ったことに、PowerAppsには繰り返しのロジックがありません。
普通ならここでMicrosoft Flowに乗り換えるのでしょうが、なんか悔しくて、さらに調べて試してみました。
# 反復のアイディア
### タイマーを使う、という発想
今度はPowerAppsとloopをキーワードに検索してみます。すると**[こんな記事](https://powerusers.microsoft.com/t5/General-Discussion/Equivalent-of-while-loop/m-p/146083#M50094 "https://powerusers.microsoft.com/t5/General-Discussion/Equivalent-of-while-loop/m-p/146083#M50094")**に出合いました。
こちらで質問しているColleenさん、途中で「Timerコントロールを使ってほぼWhileと同じ処理が出来たよ、ちょっと遅いけどね」と書いています。遅くていいんです、どうせ３秒待つんだから。
### ではどのように設定するのか
ColleenさんはRe-stateと書いています。本当に状態回復の意味で書いたのかも知れませんが、私はRestartじゃね？、と解釈しました。処理開始でタイマースタート、最初に処理を実行したら３秒待って、Restart。終了条件が来たらRestartをfalseに。いけそうですね。Timerコントロールの設定的には以下のようなものです。

| プロパティ    | 設定値  | コメント                |
|:------------:|:------:|:----------------------:|
| Start        | bool値 | trueでタイマースタート   |
| OnTimerStart | 処理   | メッセージ取得処理を入れる |
| Duration     | 3000   | ３秒待つ               |
| Restart      | bool値 | trueで繰り返し          |

# 実装してみよう
実装にあたっては、わかりやすくするために以下の要件としました。

* ドロップダウンリストでYammerグループを選択後、ボタンを押して取得開始
* いいねの集計でなく、単にメッセージを取得してギャラリーで内容を表示します（最後でちょっと補足）
* 取得処理では、最初の１回をボタンで処理した後、タイマーを起動します。そのためOnTimerEndに処理を入れます

## まずは最終イメージ
これを作ります。投稿者IDを名前にしたい、とか、日本時間にしたい、とか、メッセージの順番が、なんてのは最後でちょっと補足。（肝心のメッセージが１５件しか無い！後半までに水増ししておきます）
サンプル投稿は**[こちらの記事](https://memo.tyoshida.me/powerapps/how-two-students-used-powerbi-powerapps-for-store-management/)**から引用しました。ありがとうございます。
![コメント 2018-12-08 110900.jpg](https://qiita-image-store.s3.amazonaws.com/0/278581/73be652a-d186-df46-8159-c6c1a5c629ff.jpeg)

## Yammerコネクタ
使うコネクタはYammerのみです。接続すると関数が使えるようになります。使う関数は、上記の状態までなら以下の２つだけです。
詳しくは[こちら](https://docs.microsoft.com/ja-jp/connectors/yammer/ "https://docs.microsoft.com/ja-jp/connectors/yammer/")を参照してください

| 関数 | 用途 | パラメーター |
|:-------------------:|:---:|:-----------:|
|Yammer.GetGroups()   | 自分が使えるグループを取得します | 今回は設定しません |
| Yammer.GetMessagesInGroup(id,{older_than:message.id}) | idで指定したグループのメッセージを取得します | older_than: 指定したmessage.idより古いメッセージを取得 |

# 各コントロールの設定内容
## 1.ドロップダウン（最終イメージのDropdown1）
ここではitemsプロパティを設定するだけで、グループ名が表示されると思います。選択した結果のグループIDには、`Dropdown1.Selected.id`でアクセス出来ます。

| プロパティ | 設定値 | コメント |
|:-----:|:---------------------:|:-:|
| Items | Yammer.GetGroups() | グループ名が表示されなければ、詳細設定のValueでfull_nameを選んでください |

## 2.取得ボタン（最終イメージのButton1）
こちらも設定はOnSelectプロパティのみですが、設定値は結構長文になります。
処理の中心ですので、ひとつひとつ説明して行きますね。

```Button1.Onselect
UpdateContext({timerStart:false,pageCount:1,messageRead:0,oldestMessageId:0,totalMessage:0});
ClearCollect(workCollection,Yammer.GetMessagesInGroup(Dropdown1.Selected.id).messages);
ClearCollect(MessagesInGroup,workCollection);
UpdateContext({messageRead:Count(workCollection.id),oldestMessageId:Last(workCollection).id});
UpdateContext({totalMessage:messageRead});
If(messageRead=20,UpdateContext({timerStart:true,pageCount:2}),UpdateContext({pageCount:0}))
```

### 2-1.変数を準備
>UpdateContext({timerStart:false,pageCount:1,messageRead:0,oldestMessageId:0,totalMessage:0})


| 変数名 | 用途 |
|:--------------:|:-:|
| timerStart | タイマーを開始する時、および繰り返しする場合にtrueを入れます |
| pageCount | 取得回数をカウントして、終わると0をセットします。Label1でメッセージを出しわけるために使っています|
| messageRead | 取得毎に返されたメッセージ数を保持します。20未満なら最終ページと判断してtimerStartをfalseにします|
| oldestMessageId | 取得した中で一番古いメッセージのIDを保持します。次回の取得ではそれより古いメッセージを対象にします |
| totalMessage | 読み取ったメッセージ総数を保持します |

### 2-2.Yammerからメッセージを取得
>ClearCollect(workCollection,Yammer.GetMessagesInGroup(Dropdown1.Selected.id).messages)

Yammerからメッセージを取得して一時的なコレクション`workCollection`にセットします。投稿内容自体はさらに内側の`messages`というレコード（構造体？）の中にありますので、ひと皮剥く感じで戻り値に`.messages`を付けています。

### 2-3.取得したメッセージを蓄積
>ClearCollect(MessagesInGroup,workCollection)

コレクション`MessageInGroup`にメッセージを蓄積します。ここでは初回なのでClearCollectを使っています。

### 2-4.取得後の変数の更新
>UpdateContext({messageRead:Count(workCollection.id),oldestMessageId:Last(workCollection).id});
UpdateContext({totalMessage:messageRead})

| 変数名 | 設定値 | コメント |
|:-:|:-:|:-:|
| messageRead | Count(workCollection.id) | このidはメッセージ固有のidで、何メッセージ取ってきたのか数えています。後段で20未満ならtimerStartをfalseにしています |
| oldestMessageId | Last(workCollection).id | Yammerは新しい順にメッセージを取ってきているようなので、Lastレコードを選んでそのidをセットしています。次回の取得ではそれより古いメッセージを対象にします |
| totalMessage | messageRead | 読み込んだ総メッセージ数ですが、ここでは初回なのでmessageReadを直接セットしています |

### 2-5.繰り返すかどうかの判断と変数の更新
>If(messageRead=20,UpdateContext({timerStart:true,pageCount:2}),UpdateContext({pageCount:0}))

取得したメッセージが２０であれば、残りがあるかも知れないため繰り返す必要があります。
ここでは以降タイマーによる処理に移すために、timerStartをtrueにしpageCountも進めています。２０未満であればこれで終わりですので、timerStartをtrueにすることなく、表示を変更するためにpageCountを0にしています。

## 3.タイマー（最終イメージのTimer1）
タイマーはStartにtrueがセットされると動作を開始し、Durationにセットされたミリ秒だけ処理を待ち、その後OnTimerEndに指定された処理を実行します。Restartがtrueの間は繰り返し、falseになると終了します。

| プロパティ    | 設定値  | コメント                |
|:------------:|:----------:|:----------------------:|
| Start        | startTimer | trueで始まり、終了させる時にfalseをセットします   |
| Duration     | 5000   | 余裕をもって５秒待ちます  |
| OnTimerEnd | 処理   | １回目の処理とほぼ同じですが、この後説明します |
| Restart      | startTimer | trueで始まり、終了させる時にfalseをセットします |

### OnTimerEndプロパティに設定する処理
ボタンに設定した処理からの変更点は以下の太字の部分です。

>ClearCollect(workCollection,Yammer.GetMessagesInGroup(Dropdown1.Selected.id,**{older_than:oldestMessageId}**).messages);
**Collect**(MessagesInGroup,workCollection);
UpdateContext({messageRead:Count(workCollection.id),oldestMessageId:Last(workCollection).id});
UpdateContext({totalMessage:**totalMessage+messageRead})**;
If(messageRead=20,UpdateContext({pageCount:**pageCount+1**}),UpdateContext({**timerStart:false**,pageCount:0}))

* 取得済のメッセージを除くために、`older_than:`というオプションを追加しています。
* メッセージの蓄積については２回目以降なので、`ClearCollect`ではなく`Collect`を使っています。
* totalMessage、pageCountの計算方法を汎用的にしています。
* 取得メッセージ数が２０に満たない場合は最終ページなのでtimerStartをfalseにして繰り返しを終了します。

## 4.ギャラリー（最終イメージのGallery1）
ギャラリーには蓄積したメッセージを表示しましょう。セットするのはデータソースとレイアウト、データ項目です。画像入りのレイアウトから変更したので、コントロール名の後ろに2が付いたりしています。

| プロパティ | 設定値 | コメント |
|:-----:|:---------------------:|:-:|
| Items | MessageInGroup | 蓄積したメッセージを対象にします |


| コントロール名 | 設定値 | コメント |
|:-----------:|:------------------:|:-:|
| Body1 | ThisItem.body.plain | 投稿内容です。タグ付きで格納されていますが、plain形式で表示します |
| SubTitile2 | ThisItem.created_at | 投稿日時です。UTCで表示されています |
| Title2 | ThisItem.sender_id | 投稿者の内部ID番号です。 |

こんな感じ。
![コメント 2018-12-08 181043.jpg](https://qiita-image-store.s3.amazonaws.com/0/278581/5b593eb7-6b2e-7b49-230c-bf3ceeb3742b.jpeg)

## 5.ラベル（最終イメージのLabel1）
処理の繰り返しが入ると終了までに時間がかかるので、ラベルで状態を表示しています。設定はTextプロパティひとつです。

| プロパティ | 設定値 | コメント |
|:-----:|:---------------------:|:-:|
| Text | If(pageCount>0,"抽出しています..." & totalMessage,If(pageCount=0,"抽出が終わりました..." & totalMessage & "メッセージ","グループを選んでボタンを押してください")) |  |

# 動かしてみよう
最後に動作確認しましょう。
２０メッセージより少ないグループを選んでボタンを押すと数秒で終了します。２０メッセージ以上あるグループでは、２０メッセージの表示とともにタイマーが動き始め、５秒後に処理が繰り返されることがわかります。
追加のサンプル投稿は**[こちらの記事](https://www.tbs.co.jp/gibomusu_blues/interview/interview01.html)**から引用しました。ありがとうございます。
![timer.jpg](https://qiita-image-store.s3.amazonaws.com/0/278581/8756dd9a-0c62-1911-4725-6722338a62e2.jpeg)
![コメント 2018-12-08 193921.jpg](https://qiita-image-store.s3.amazonaws.com/0/278581/4d943395-0092-2c97-f4ac-4407b58ca6a6.jpeg)

# 最後に
わかってみれば簡単なTimerを使った繰り返しワザ、如何でしたでしょうか。私のニーズにはバッチリでした。
最後にいくつか見た目の改善をしましょう。
### 投稿者を名前で表示したい！
Title2コントロールのTextプロパティを以下のように変更します。

| 変更前 | 変更後 |
|:-:|:-:|
| ThisItem.sender_id | Yammer.GetUserDetailsById(ThisItem.sender_id).full_name |

### 投稿日時を日本時間にしたい！
SubTitle2コントロールのTextプロパティを以下のように変更します。

| 変更前 | 変更後 |
|:-:|:-:|
| ThisItem.created_at | DateTimeValue(ThisItem.created_at) |
### メッセージを古い順にしたい！
ギャラリーのItemsプロパティを以下のように変更します。

| 変更前 | 変更後 |
|:-:|:-:|
| MessagesInGroup | SortByColumns(MessagesInGroup,"created_at",Ascending) |

### ところで「いいね！」のカウントどうするの？
説明しているとまだまだ長くなってしまいますので、関数だけ。

```
ClearCollect(MessagesByUser,GroupBy(MessagesInGroup,"sender_id","OtherColumns"));
ClearCollect(LikesByUser,SortByColumns(AddColumns(MessagesByUser,"MessageCount",Count(OtherColumns.id),"LikeCount",Sum(OtherColumns,liked_by.count)),"LikeCount",Descending))
```

それでは、良いお年をお迎えください。