何やってたっけ? ってなっていませんか
お仕事をしていると、いろんなタスクをこなしているけれど、一日の時間を何にどれくらい費やしていたかを振り返るのってなかなか難しいですね。
工数管理が厳しい会社などだと、記録や提出が求められたりするかと思います。専用のツールもあるでしょうがいちいち入力も面倒なもの。
そこで、普段会議予定が詰め込まれがちなOutlookのカレンダーに記録された予定から、カテゴリタグの機能を使い、Power Automateで先週の工数を記録しようというのが今回の試みです。
Outlookカレンダーのカテゴリ管理機能
Outlookのカレンダーは会議予定が外部から差し込まれたりと予定を管理するだけでなく、実際に行った作業を何をしていたか忘れないように記録しておくような用途にも使います。
「カテゴリの管理」をクリックすると、既存のカテゴリの名称を変更したり、新規に追加シタリが可能となります。

今回はデフォルトのカテゴリを「自学習」と「キッティング」「説明会」に変更して使ってみます。この辺りはご自身のお仕事の性質に合わせてどうぞ。

カレンダーに記録するときにはこんな感じでカテゴリタグを付けておきます。新規イベントの真ん中中央にある荷札タグのようなアイコンから選択します。カテゴリをつかうのでイベントタイトルはシンプルにできますね。

こちらがサンプルで埋めてみた先週1週間の予定です。カテゴリをつけるとその種類によって色分けされるのがよいですね。もし他のユーザーの予定も一緒に表示されるので紛らわしいという場合は、カテゴリの色を同系色にまとめると良いかもしれません。

Teams会議にはあえてカテゴリは付けませんでした。あとでどのように分類できるか確認してみます。
Power Automateから予定表を取得
Office365 outlookカテゴリのアクションのなかに、イベントの取得が2種類ありました。せっかくなのでV4のほうをつかってみます。
早速テスト実行してみました。取得できたJSONは1要素が1イベントです。categoriesのところに先程名前をつけたカテゴリー名が入っています。角括弧つきなので配列担っています。1つのイベントに複数のカテゴリを付けられるということでしょうね。

やはり複数付けられます。いちおう複数のカテゴリがついていた場合に、どれが要素の先頭になるのかも確認しておきます。

表に出ているカテゴリが配列の先頭になるようです。イベント編集画面でカテゴリはあとから追加したものがメインになるようで、入れ替えはできませんでした。複数カテゴリを使って、かつメインを決めたいときには最後に設定するような小技が必要でした。

先週分の予定だけを取得するには?
取得したイベントのJSON情報を「アレイのフィルタ」にかけてもよいのですが、取得してくるイベントJSON自体を少なくしたほうが効率がよいので、ODataフィルタークエリを試してみます。
先週の予定だけを OData フィルタークエリで取得したい場合は、OData が相対日付関数をサポートしていないため、「先週の開始日時」と「先週の終了日時」 を事前に変数や式で設定して、それを OData フィルターに埋め込む必要があります。
実行日の全集の月曜日はこのように式を書きます。
formatDateTime(
addDays(
utcNow(),
sub(-7, mod(add(dayOfWeek(utcNow()),6),7))
),
'yyyy-MM-ddT00:00:00Z'
)
同じくこちらは先週の金曜日
formatDateTime(
addDays(
utcNow(),
sub(-3, mod(add(dayOfWeek(utcNow()),6),7))
),
'yyyy-MM-ddT23:59:59Z'
)
今月の1日以降を求めたい場合はこちら
formatDateTime(
utcNow(),
'yyyy-MM-01T00:00:00Z'
)
それぞれ「作成」アクションの式にいれて、適当な名前を付けておきます。

こんどは、「イベントの取得(V4)」トリガーのフィルタークエリに以下のように記載します。
start/dateTime ge '@{outputs('先週月曜')}' and start/dateTime lt '@{outputs('先週金曜')}'
start/dateTime ge '@{outputs('先週1日')}' and start/dateTime lt '@{outputs('先週金曜')}'
JSONを分析する
こんどは取得できたデータの方を分析してみましょう。
カテゴリーがついていない場合は?
さきほどカテゴリーはどのように記録されているかは確認しました。カテゴリーが何も設定されていない場合には、空の配列になっています。

空の配列だけを抜き出すには、「アレイのフィルター処理」アクションを使って、左辺に以下の式。右辺を0
にします。length関数は配列の数を取得することができます。
length(item()?['categories'])
各イベントのendからstartの時刻をtick関数を使って引き算し、ミリ秒を分に変換するには以下のような式を書きます。これを「選択」アクションをつかってまずは配列にして確かめてみます。
div(
sub(
ticks(item()?['end']),
ticks(item()?['start'])
),
600000000
)
配列の要素の合計を出すには?
配列の各要素の中にある数字を合計するには、もちろんループを回して足し込んでもよいのですが、ここで以前に私が記事を書いた爆速加算法が活躍します。
この方法は、Power Automateに標準ではついていない配列の要素の合計を、xmlの機能をつかって行うというものです。
まずは、先程の「選択」アクションをマップモードに切り替えます。

「作成」アクションを追加して、配列化した選択をこのようなJSONの形にします。
{
"root": {
"Numbers": @{body('選択')}
}
}
わかりにくくなる前に作成の名前を変えておいて、最後の「作成xml」には下記のように前のJSONをxml関数で囲みます。
xml(outputs('作成JSON'))
最後に、以下のxpath関数というのを使うと、要素の合計を一瞬で計算できるのです。
xpath(outputs('作成XML'),'sum(/root/Numbers)')
設定したカテゴリごとの工数は?
カテゴリなしについて工数の合計を取得することができたので、他のカテゴリについてもスコープにまとめてコピーすれば、アレイのフィルタ部分を修正するだけで各カテゴリの工数合計を取得することは容易です。

でも、それでは面白くないので、用意してあるカテゴリも自動で取得してやることにしましょう。
「Outlookのカテゴリー名を取得する」というアクションがありました。

設定項目はなにも必要なく、作成済みのカテゴリが配列として返ってきました。

このカテゴリーにマッチするものをイベントの塊であるJSONからフィルターして、先程の合計計算をすればよいということになります。
繰り返しの処理をおこなうので、「それぞれに適用する」を使います。
さっき作ったスコープをApply to eachの中にコピーして再利用します。
ここからが少しトリッキーです。
スコープとフィルターの名前は相応のものに変えました。
スコープの一番上に、新たに「作成」を追加して、名前を「カテゴリー」としました。
各項目には下記の式をセットしてください。
@{body('Outlook_のカテゴリ名を取得する')}
@{items('Apply_to_each')?['displayName']}
@outputs('イベントの取得_(V4)')?['body/value']
@first(item()?['categories'])
@outputs('カテゴリ')
これでテスト実行してみると、Apply to each はOutlookで設定されているカテゴリーの数だけループします。
カテゴリーフィルターの差出人は全イベントJSONなので、そのなかのCategories配列の最初の項目と、ループ1回ごとに取り出されたカテゴリー名(作成:カテゴリを見るとそのループの回でなにかがわかります)とを比較しています。
その後の計算処理は再利用されていることが、実行してみるとわかったと思います。

結果をループごとに変数に記録していく
各カテゴリーごとの工数合計は取得できたものの、このままではどこにも記録されません。
「作成」で用意した値はみずもの。何かをためていくには変数を利用します。今回のようにカテゴリー名とその値という組み合わせを扱うには、やはり配列(アレイ)が使いやすいです。
最初の方に「変数を初期化する」を追加して適当な名前の変数を種類「アレイ」で作成します。

Apply to eachの中の合計処理のスコープの下に「配列変数に追加」アクションを追加します。
先程初期化した変数を選択したら、値には以下のような波括弧でくくったライブラリー形式を設定します。
{
"カテゴリ":@{outputs('カテゴリ')},
"工数":@{outputs('作成合計_2')}
}
追加した結果が確認できるように、デバッグ用の「作成」をループの外に置いて、カテゴリと工数合計変数をセットしておきました。

テスト実行してみると、とってもいい感じにカテゴリと工数合計の1組が配列として変数に記録されていることがわかります。

カテゴリなしスコープの結果もこの配列変数に追加すればよいですね?

Teams会議の合計時間も知りたい
ここまでで、各カテゴリと、カテゴリが付けられていない予定の工数を合計して配列で管理できるようになりました。
全体の工数のうち、会議であった時間も管理しようと思います。
イベントの取得でとってきたJSONの中身を見れば、locationという項目を見ればTeams会議であることはわかりますが、言語によってここの文字列は変わってしまいます。
Teams会議であれば、bodyの中身に「https://teams.microsoft.com/」 という文字列が含まれているのでこれをつかって判定できそうです。
カテゴリなしスコープをコピーして、名前を適当に書き換えます。
最初のフィルター部分は、詳細設定モードでこのように記述します。
@equals(contains(item()?['body'], 'https://teams.microsoft.com/'), true)
最後に配列に追加する際に、カテゴリ名を「内Teams会議」というように編集しておきます。

Teamsに投稿する
これで配列変数のなかにすべてのカテゴリと、Teams会議で使った工数合計が集まりました。
Teamsチャットで自分に送信してみましょう。

あとは、配列変数を「HTTPテーブルの作成」アクションに渡して、その結果をTeamsの投稿アクションのメッセージに追加してやるだけです。期間を表示したければ「作成」として持っていますから使えます。フォーマットがイケてないのは適当に変換してやるとよいでしょう。

とりあえず、分数ではありますが、合計を投稿することができました!

時間の範囲がUTCなのはご愛嬌。
表示する際に日本時間に変換したいようでしたら、こちらを参考にしてください。
私もいつもメモ代わりに使っている記事です。
「モカ式 utc」
で検索すると出てきますので、覚えておくと便利ですよ (宣伝です)
あとは任せた!
カテゴリと合計の数が取得できているので、あとはループを回してSPOリストなり、Excelのテーブルなりに行追加してくことは簡単です。
トリガーを定期実行にしておけば、なんにも考えずに前週や一ヶ月の工数が計算できてしまいます。もちろんちゃんとカテゴリ設定をしたり、カレンダーに入力しておけばですが。それはまた別のお話。
まとめ
Outlookの予定表にあるカテゴリ機能をつかって、Power Automateで取り出すのを試してみました。
- 合計の計算はxmlへ変換することで実行できます。ちょっとややこしいですが。
- 今回はカテゴリの種類を取得して、それぞれをループで回してフィルタの条件にしてみました。これもちょっと複雑ですが、ループもフィルターもitem()をつかうと要素が取れるのは同じと理解していると使いどころがあります。
- フィルターのなかで外側のループの要素をつかうには、item() ではなく、どのループの値を扱うのかを指定できるitems('ループの名前')を使うのがポイントです。
- 配列が空であることを調べるにはlength関数を使います。配列要素の数がわかるので、比較は0とか数字になります。
- contains関数は配列に要素が含まれているかを調べる関数で、結果はTrueかFalseになります。この関数はフィルターアクションと組み合わせると絶大な威力を発揮します。クラウドフローの爆速化には欠かせない関数です。
- categoriesの値は配列なので、その先頭要素だけをつかうためにfirst関数も使いました。比較をする際にうまく行かない場合は、配列をそのまま比較対象にしていることが多いです。気が付かないとハマりますね。
こんな人が書いてます。
こちらの記事はランゲルハンス島のDDさんが紹介しました。ブログでクラウドフローのTIPSのようなものを書いたり、Qiita記事を書いたりしていますのでご贔屓に。
フォローやいいねいただけると嬉しいです。
関西のPowerPlatform系の勉強会にときどき出没しますので、気軽に声をかけていただけると喜びます!

















