コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

【Power Apps】超簡単!Power Apps でクリック/選択された座標を取得する

今回やること

今回は下記のようにクリック/選択された座標をPower Apps で取得する方法を紹介したいと思います。

方針

今回は以下の方針でクリックされた座標を取得できるようにしたいと思います。

  • 複雑なロジックは極力しようしない
  • 動作が重くなるような実装は避ける

Power Apps はローコードを売りにしていますので、上記方針で作成しています。

クリック/選択された座標をPower Apps で取得する

まずは「縦方向(空)」のギャラリーを配置して以下のように設定してください。

Gallery1.X & Gallery1.Y

0

Gallery1.Width

Parent.Width

Gallery1.Height

Parent.Height

Gallery1.TemplatePadding

0

ギャラリーの配下に「評価」を追加して以下のように設定します。

Rating1.X & Rating1.Y

0

Rating1.Width

Parent.Width

Rating1.Default

0

Rating1.ShowValue

false

X座標の選択された座標を取得する仕組みを作成する

X座標の選択された座標を取得する仕組みには先ほど作成した「評価」を用います。
以下のように設定を変更してください。

Rating1.Max

100

ここでMaxに設定した値が大きければ大きいほど選択したX座標の精度が高くなります。
ここの値を大きくしてもそれほど性能に影響しないので、今回は100に設定しています。
なおここに設定できる値は最大でも100までなので注意しましょう。

上記のようにどこを選択しているのか判定できますね。
あとはこの値をもとに座標を算出してあげればよいというわけです。

Y座標の選択された座標を取得する仕組みを作成する

Y座標の選択された座標を取得する仕組みにはギャラリーを用います。
今回は以下のように設定することとします。

Gallery1.TemplateSize

40

Gallery1.Items

Sequence(Self.Height / Self.TemplateHeight)

上記の設定により、TemplateSize に設定した値によって表示されるカラムの数が変化します。
具体的にいうと、TemplateSize の値が小さいほど、カラムの数は増えますし、値が大きいほどカラムの数は減ります。

当然カラムの数が多いほうが、Y座標の判定の精度は高くなりますが、その分アプリが重くなってしまいます。
ここはY座標の精度をとるか、アプリの重さをとるかのトレードオフとなりますね。

取得した値を実際の座標に変換する

選択した座標がきちんと取得/計算できている判断するために、適当なアイコンでも設定しましょう。
このときギャラリーが最前面にくるように設定しなければならないことに注意してください。

取得した値を計算するには以下のように設定します。

Icon1.X

Gallery1.Selected.Rating1.Value * Gallery1.Width / Rating1.Max

Icon1.Y

Gallery1.Selected.Value * Gallery1.TemplateHeight - Rating1.Height / 2

アイコンを選択された位置の中心に持ってきたい場合は調整が必要ですね。

Icon1.X

Gallery1.Selected.Rating1.Value * Gallery1.Width / Rating1.Max - Self.Width / 2

Icon1.Y

Gallery1.Selected.Value * Gallery1.TemplateHeight - Rating1.Height / 2 - Self.Height / 2

まずはX座標の計算についての解説です。
Gallery1.Selected.Rating1.Value では選択された評価の値を取得しています。
このままの値では座標の値ではないので座標に変換を行います。
評価の数値1あたりの座標の数値を算出するには、ギャラリーの幅 / 評価の最大値で算出できますね。 これを式にすると Gallery1.Width / Rating1.Max となるわけです。

続いてY座標の計算についての解説です。
Gallery1.Selected.Value では選択されたカラムの数を取得しています。
X座標のときと同様この数値を座標に変換します。
同様の理論でカラム1つあたりの座標を算出するための式を作成します。
それが Gallery1.TemplateHeight にあたるというわけですね。

おわり

以上でPower Apps でなるべく簡単なロジックでクリック/選択された座標を取得するための仕組みを作成することができました!
願わくばこの機能がいつか標準で取得できるようになるといいですね...

また、今回「評価」を用いましたが、「スライダー」でも同様に実現可能です。

【Power Automate】Share Point ドキュメントのディレクトリ構成を取得する

はじめに

棚卸作業などで、Share Point Online のドキュメントライブラリのディレクトリ構成の棚卸を行いたい!と考えてことはありませんか?
今回はそのようなときに利用できるフローをPower Automate で作成してみました。

なお、標準機能でディレクトリ構成の出力を行いたい場合や、普段利用しているツールと同様の方法でディレクトリ構成の出力を行いたい場合は、もくだいさんのドキュメントライブラリのフォルダ、ファイル一覧を取得したいをご参照ください。

さっそく作成してみる

今回利用する変数

今回利用する変数は以下です。
最初に変数の初期化を行いましょう。

f:id:koruneko:20200919115628p:plain

f:id:koruneko:20200919115725p:plain

名前 種類
InitParent 文字列 [ディレクトリ構成を作成したい親ディレクトリ]
ex ) /Shared Documents/General
Parent 文字列 @{variables('InitParent')}
Index 整数 1
resultDirectory アレイ
Directory アレイ @{body('選択')}
※後述

InitParent で設定したディレクトリのフォルダ一覧を取得する

InitParent で設定したディレクトリ以下のフォルダ/ファイルの一覧を取得します。
「フォルダーの一覧」を選択し、対象のサイトを選択後、先ほど作成した「initParent」をファイル識別子に設定します。
その際、【Power Automate】Share Point コネクタのファイル識別子に動的コンテンツを利用するでも説明したように、replace 関数を利用して値を設定しましょう!

ファイル識別子

replace(variables('Parent'), '/', '%2F')

続いて「選択」アクションを選択し、「フォルダーの一覧」で取得した結果で必要なものだけを抜き出しましょう。
今回は以下のように設定しました。
Parent には、変数のParent を用いています。

マップ

{
  "Parent": @{variables('Parent')},
  "isFolder": @{item()?['IsFolder']},
  "DisplayName": @{item()?['DisplayName']},
  "Path": @{item()?['Path']},
  "isDone": @{false}
}

f:id:koruneko:20200919120719p:plain

変数「Directory」の設定

「変数」>「変数の初期化」を選択し、「Directory」というアレイ型の変数を作成します。
値には、先ほど「選択」で出力された結果を選択します。

f:id:koruneko:20200922161557p:plain

サブフォルダを含めてフォルダーの一覧を取得する

上記の方法だけでは、サブフォルダーも含めてフォルダーの一覧を取得することはできませんでした。

ここでは上記で取得したフォルダー一覧をもとにサブフォルダーも含めてフォルダーの一覧を取得していこうと思います。

サブフォルダーまで探索するためのループ条件ですが、今回は
「Directory 変数に格納された値の長さ(数) と ループ処理を行った回数」 で比較を行いたいと思います。

f:id:koruneko:20200922162053p:plain

Do until のループ条件

@greaterOrEquals(length(variables('Directory')), variables('Index'))

今回ループ処理回数のカウントに「Index」という変数を用いていますが、iterationIndexes という関数を利用してもいいかもですね。

JSON 解析を用いて値を参照しやすくする

続いて、「JSON の解析」を用いて「Directory」に格納されている値を順に参照していきます。

以下のように設定します。

コンテンツ

last(take(variables('Directory'), variables('Index')))

スキーマ

{
    "type": "object",
    "properties": {
        "Parent": {
            "type": "string"
        },
        "isFolder": {
            "type": "boolean"
        },
        "DisplayName": {
            "type": "string"
        },
        "Path": {
            "type": "string"
        },
        "isDone": {
            "type": "boolean"
        }
    }
}

これにより、Index 番目の配列が参照されますね。

フォルダー一覧を取得する

続いて、「条件」コントロールを追加します。
この条件には、現在の対象となっているディレクトリが、ファイルか?フォルダーか?を判断するようにします。
もしフォルダーであれば、対象フォルダー以下のフォルダー一覧を取得し、「Directory」変数に値を追加します。
ファイルであれば、特になにも処理を行いません。
これにより、フォルダーが存在している場合「Directory」の値が増えるためループ回数が増えますね。

条件には以下のように設定します。

@body('JSON_の解析')?['isFolder']    次の値に等しい    @true

フォルダーであった場合には、フォルダー一覧を取得し、「Directory」に追加したいので以下のように設定します。

  • フォルダー一覧

サイトのアドレス

任意のアドレス

ファイル識別子

@replace(body('JSON_の解析')?['Path'], '/', '%2F')
  • 変数の設定

変数

Parent

@{body('JSON_の解析')?['Parent']}
  • 選択

開始

@{body('フォルダーの一覧_2')}

マップ

{
  "Parent": @{variables('Parent')},
  "DisplayName": @{item()?['DisplayName']},
  "isFolder": @{item()?['IsFolder']},
  "Path": @{item()?['Path']},
  "isDone": @{body('JSON_の解析')?['isDone']}
}
  • Apply to each
@{body('選択_2')}
  • 配列変数に追加

変数

Directory

@{items('Apply_to_each_2')}

f:id:koruneko:20200922180457p:plain

ここで、「Directory」に値を追加する際、Apply to each の処理が必要になってきます。
Apply to each なしで「選択」で出力された値を配列変数に追加しようとすると、エラーになるかと思います。
追加を行う際は、「選択」で取得した結果を1つずつ配列変数に追加する必要があるのでこのようになっています。

インデックの増加

ループ処理の最後に、「Index」の値を1増やす処理を追加しましょう!
これにより、対象ディレクトリのサブフォルダを含めたディレクトリー構成を取得することができます。

f:id:koruneko:20200922181412p:plain

取得したディレクトリ構成に属性を追加する

これまでの処理で取得された結果は、

  • フォルダー名
  • パス
  • 親フォルダー
  • フォルダーかファイルか

のみでした。

これらに加え更に

  • サイズ
  • 最終更新日

まで取得できるようにしたいと思います。

以下のような構成のフローを作成します。

f:id:koruneko:20200922181631p:plain

メタデータを取得する

Apply to each処理には以下を設定します。

@{variables('Directory')}

メタデータの取得ですが、これはファイルとフォルダーでそれぞれアクションが異なってきます。
なので「条件」を追加し、以前同様フォルダーか?ファイルか?を判定する条件を追加します。

設定するパラメータはほぼ同じなので、今回はフォルダーのメタデータの取得について解説します。

サイトのアドレス

任意のアドレス

ファイル識別子

@replace(items('Apply_to_each')?['Path'], '/', '%2F')
  • 作成
{
  "isDone": @{items('Apply_to_each')?['isDone']},
  "Parent": @{items('Apply_to_each')?['Parent']},
  "isFolder": @{items('Apply_to_each')?['isFolder']},
  "DisplayName": @{items('Apply_to_each')?['DisplayName']},
  "Path": @{items('Apply_to_each')?['Path']},
  "Size": @{body('フォルダー_メタデータの取得')?['Size']},
  "LastModified": @{body('フォルダー_メタデータの取得')?['LastModified']}
}
  • 配列変数に追加

名前

resultDirectory

@{outputs('作成_2')}

これで、メタデータの取得で得られた結果を踏まえた配列を取得することができるようになりました!

他の情報も取得したい場合は同様の方法で取得を行ってみてください。

ディレクトリ構成を見やすいかたちで出力する(作成まだ)

こちらは作成完了できたら追記します。
どなたかよい案があればください!!

おわりに

今回紹介したやり方を応用すれば、対象ディレクトリ配下のサブフォルダー含むすべてのファイルに対して、特定の処理を行う。
ということもできるようになります。

興味のある方はそちらも試してみるとよいですね。

【Power Automate】Share Point コネクタのファイル識別子に動的コンテンツを利用する

はじめに

この記事では、Power Automate にてShare Point コネクタ利用時に「ファイル識別子」に別のアクションで取得した、パスなどの動的コンテンツを設定したい場合のやり方について纏めています。

別のアクションで取得したパスを「ファイル識別子」に設定する場合、取得した値を少し加工してあげる必要があるのですが若干初見殺し感があるので備忘録も兼ねて纏めてみることにします。

「ファイルパス」と「ファイル識別子」

Power Automate からShare Point のドキュメントを指定するには、対象の「サイトのアドレス」と「ファイルパス」もしくは「ファイル識別子」の指定が必要なものがほとんどです。

では、「ファイルパス」と「ファイル識別子」の違いは何でしょうか?
実際に動かしてみて挙動をみてみましょう!

下記のようなフローを用意し、実際に動かしてみます。
2つとも同じ結果を取得するようなアクションですね。
違いは、「ファイルパス」か「ファイル識別子」という点です。

f:id:koruneko:20200918104026p:plain

そしてこちらが上記フローを実行した結果です。
「ファイル識別子」を指定したフローだけが失敗していますね。

f:id:koruneko:20200918104249p:plain

エラーとしては、下記のようなものが出力されています。

{
  "status": 400,
  "message": "Route did not match\r\nclientRequestId: 6d2fa675-2883-41b6-bd93-d6d7a73ac3f1\r\nserviceRequestId: 6d2fa675-2883-41b6-bd93-d6d7a73ac3f1"
}

ルートが一致しないと怒られていますね。

原因を調査してみる

「未加工入力の表示」で「ファイル識別子」に動的なコンテンツを設定した場合と、GUI 操作で直接パスを設定した場合の違いを比較してみましょう。
「未加工入力の表示」は実行履歴の以下より取得可能です。

f:id:koruneko:20200918104948p:plain

ここで取得した結果をWinMarge という比較・マージ用ツールを用いて差分を比較してみます。

f:id:koruneko:20200918110124p:plain

左がGUI 操作で直接パスを設定した場合、右が動的なコンテンツを設定した場合です。
比較の結果「id」に指定されている値が違うということがわかりました。

「/」が「%252f」に置換されていますね。
これでピンとこない方は

  • / %252f
  • %252f uri

などで調べてください。
※%252f ではなく%2F が検索結果に引っかかるかもしれませんが、どちらでも同じ挙動をします。

「ファイル識別子」に動的コンテンツを指定するには

これらの結果をもとに「ファイル識別子」に指定している動的コンテンツを加工してアクションが正常に実施されるように修正します。
修正内容は「/」を「%252f」(もしくは「%2F」)に置換してあげればよいです。

置換を行うには、「replace」関数を用います。

replace(items('Apply_to_each')?['Path'], '/', '%252F')

もしくは

replace(items('Apply_to_each')?['Path'], '/', '%2F')

これで正常にフローを実行することができました!

おわりに

今回は、問題解決アプローチも記事に組み込んで紹介してみました。
調べても出てこないような問題もPower Automate やPower Apps でアプリを作成しているかもしれません。
そのような場合は、自分でトライ&エラーを繰り返してみて原因を特定し、解決できるよう目指してみましょう!

【Power Apps】Sequence 関数を使ってみよう!

この記事で紹介すること

この記事ではPower Apps のSequence 関数について紹介したいと思います。
若干今更感ありますがそこはいわないで!

Sequence 関数を使うとなにができるの?

Sequence 関数を使うことにより、簡単な数列のテーブルを作成することができます。

docs.microsoft.com

実際に使用してみる

では、実際に使用してみましょう。

連続する番号のテーブルを作成する

1-10の連続する番号のテーブルを作成してみます。

ListBox1.Items

Sequence(10)

f:id:koruneko:20200907000257p:plain

これにより、1-10の連続する番号のテーブルを作成することができました。
この関数が登場するまでは、

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

のように記載しなくてはいけなかったので大変楽になりましたね!

開始番号を任意の数に設定する

先ほど作成したテーブルは1から開始されていました。
今度は10からテーブルを開始し、20までの連続するテーブルを作成してみようと思います。

ListBox1.Items

Sequence(11, 10)

f:id:koruneko:20200907001011p:plain

このように第2引数に数値を指定した場合、その数値から連続するテーブルを作成することができます。
なお開始する番号は規定では1に設定されているので、指定しなかった場合は1から開始されます。

連続する偶数を10個作成する

今度は連続する偶数を10個作成してみようと思います。

ListBox1.Items

Sequence(10, 2, 2)

f:id:koruneko:20200907001641p:plain

このように第3引数に数値を指定した場合、その数値分だけ値が増えたテーブルを作成することができます。
なおこのステップ数は規定では1に設定されているので、指定しなかった場合は1から開始されます。

数値がだんだん減っていくテーブルを作成する

ステップ数にマイナスの値を設定することにより、カウントダウンを行うようなテーブルを作成できます。

ListBox1.Items

Sequence(10, 5, -1)

f:id:koruneko:20200907002857p:plain

3の倍数だけ除外されたテーブルを作成する

これを実現するためには、Filter 関数を利用すればよいです。

ListBox1.Items

Filter(Sequence(10), Mod(ThisRecord.Value, 3) <> 0)

f:id:koruneko:20200907003344p:plain

ThisRecord.Value の部分はValue だけでも大丈夫です。

ForAll と組み合わせて利用する

恐らく、Sequence 関数を利用していく場合、ForAll 関数とともに利用するケースが多いかと思います。
ForAll 関数とまだ仲良くなれていない方はこの機会に是非仲良くなってみましょう!

例えば公式doc のように乱数を10個生成してみようかと思います。

ListBox1.Items

ForAll(Sequence(10), Rand())

f:id:koruneko:20200907004940p:plain

【おまけ】フィボナッチ数列を作成してみる

ForAll 関数とSequence 関数を活用してPower Apps でフィボナッチ数列を作成してみようと思います!
フィボナッチ数列ってなに?という方は、こちらをご確認ください。
フィボナッチ数列をわかりやすく解説!一般項の求め方をマスターしよう

簡単に説明すると、「2つ前の項と1つ前の項を足し合わせていくことでできる数列」で
0, 1, 1, 2, 3, 5, 8, 13, 21…
といったかたちの数列になります。綺麗ですね!

これをPower Apps で作成しようとすると、例えば以下のようになります。

Button1.OnSelect

Clear(fibo);
Collect(fibo, [0, 1]);
ForAll(
    Sequence(10),
    Collect(
        fibo,
        {Value:Last(FirstN(fibo, Value)).Value + Last(FirstN(fibo, Value + 1)).Value}
    )
)

f:id:koruneko:20200907011603p:plain

簡単に解説を行うと、まずは最初に第一項と第二項を宣言します。
次にForAll 関数ないで、fibo レコードを再帰的に呼び出してフィボナッチ数列を作成しています。
Sequence 関数で指定した数値 + 2項フィボナッチ数列が作成されていますね。

こちらの再帰的に呼び出している箇所については、【Power Apps】コレクションにインデックスを付与するで似たような解説を行っていますので、よろしければ参考にしてください!

【イベント開催のご案内】業務改善検討会を開催します!

業務改善検討会を開催します!

2020/09/26(土)の14:00 〜 17:00にて業務改善検討会 #0を開催します!

こちらのイベントは業務改善方法を皆で共有し、業務改善に関して参加者全員で考えるためのイベントとなっております。

興味のある方はconnpass より参加申し込みを行ってください。

connpass.com

本イベントはクローズドなものを想定しております。
資料の公開などは登壇者の方に一任しているため、情報が公開されない場合があります。

また、本イベントでは企業の内情が公開される可能性があるので、イベントの内容をSNSなどで公開することは厳禁です。

参加されるかたは、connpass にて記載されている、イベントの説明をご確認の上ご参加ください。

登壇枠について

次回イベント開催時登壇していただける方を募集しております!
登壇に興味のある方は下記Forms より申請お願いします!!!
【業務改善検討会】登壇者募集用フォーム

【免責事項】
記載されたメールアドレス、SNSはこちらから連絡を取る目的のみで使用します。
個人情報の取り扱いには細心の注意を払いますが、万が一悪意ある第三者によって情報が流出してしまった場合など、いかなる場合も管理者は責任を負いません。
本フォームに回答した場合上記に同意したとみなします。

ディスカッション内容のご提供に関して

次回イベントでのディスカッション内容を募集しております!
日々の業務で改善を行いたい業務がある場合は下記Forms より申請お願いします!!!
【業務改善検討会】ディスカッション内容募集用フォーム

※1 ただし、参加者皆さんへの共有として、このイベントで実施した改善については今後開催されるイベント内にて情報共有をお願いします。

※2 改善案が必ずしも得られるわけではありませんので、ご注意ください。

【免責事項】
記載されたメールアドレス、SNSはこちらから連絡を取る目的のみで使用します。
個人情報の取り扱いには細心の注意を払いますが、万が一悪意ある第三者によって情報が流出してしまった場合など、いかなる場合も管理者は責任を負いません。
本フォームに回答した場合上記に同意したとみなします。

Power Automate でPlanner の情報を取得する

はじめに

本記事は私が募集しているマシュマロにきていた質問に対する回答・解説を記載します。
今回、回答する質問は以下のようなPlanner に関する質問です。

f:id:koruneko:20200830220939p:plain

もし、私に他に聞いてみたいことがあればこちらにてお願いします!

Planner の情報を取得する

タスクのタイトルを取得する

質問者さんは解決しているようですが、Power Automate でPlanner のコネクタをこれからつかってみようかな?と考えている方向けに説明します。

まず、Power Automate には現在(2020/8/30)、以下のようなPlanner のコネクタが存在します。

トリガー
f:id:koruneko:20200830222405p:plain

アクション
f:id:koruneko:20200830222642p:plain

各コネクタの詳しい説明は、コネクタ リファレンス - Plannerをご確認ください。

それでは、本題です。
結論からいいますと、タスクのタイトルを「タスクの一覧を表示します」を利用します。

例えば以下のようなタスクが作成されていたとします。

f:id:koruneko:20200830223023p:plain

こちらのタスクのタイトル、「確認用」をPower Automate 上で取得してみます。

Power Automate にて「タスクの一覧を表示します」アクションを追加し、取得したいタスクが属している「グループID」、「プランID」を選択します。

f:id:koruneko:20200830223209p:plain

こちらのアクションを実行すると、実行履歴の出力欄にて以下のような結果を確認できるかと思います。
* 実行結果はJSON 形式で出力されます。
* 下記では抜粋して記載しています。

"title": "確認用"

上記アクションで取得した結果をTeams やOutlook などで送りたい場合は、下記のように「値 タイトル」を選択します。

f:id:koruneko:20200830223921p:plain

以上で、タスクのタイトルを取得することができました!
ただしこのままでは、対象プランのタスクタイトルが全て対象となっているので注意してください。

タスクのメモを取得する

タスクのメモを取得する際は、「タスクの詳細を取得する」を利用します。
アクションを追加したら、取得したいタスクのタスクID を選択、もしくは入力します。

f:id:koruneko:20200830224756p:plain

こちらのアクションを実行すると、実行結果で以下のように確認できます。

f:id:koruneko:20200830224847p:plain

Teams やOutlook などで送りたい場合は、「説明」を選択します。

f:id:koruneko:20200830225225p:plain

以上がタスクのメモを取得する方法です。

なおおまけ的な説明ですが、「タスクを取得する」アクションでは、対象のタスクにメモが存在するかどうかを確認することができます。
これは「説明があります」という実行結果を確認することで確認が可能です。
説明がある場合はtrue、なければfalse を返します。

タスクのコメントを取得する

最後にタスクのコメントの取得方法です。
Planner のタスクにつけたコメントはPlanner 上では以下のように確認できるかと思います。

f:id:koruneko:20200830230422p:plain

これらのコメントの取得ですが、Planner のコネクタでは取得できません。
では、どうやって取得するの?というと「Office 365 Groups Mail」コネクタを利用します。

f:id:koruneko:20200830231131p:plain

Planner につけたコメントですが、対象のタスクが属するグループのメールグループにメールが送られます。

f:id:koruneko:20200830231803p:plain

こちらに送られたメールを取得することによってコメントを取得してやろうという魂胆です。

対象グループのメール一覧を取得するには「List the conversations of a group」アクションを利用します。

「Group ID」には取得対象のグループID を選択、もしくは入力します。

f:id:koruneko:20200830231915p:plain

これにより、以下のような結果を取得することができます。

"topic": "タスク \"確認用\" のコメント",
"preview": "改行を含むコメント\r\nだよ\r\nこれらは、プラン Test のタスク 確認用 についてのコメントです。\r\nMicrosoft Planner に返信 するか、またはこのメールに返信して、タスクのコメントを追加します。\r\n________________________________\r\nFrom: koruneコルネ <korune@korune.onmicrosoft.com>\r\nSent: Sunday, August 23, 2020 2:56:14 AM\r\nTo: koruneコルネ <korune"

Power Automate 上でこれらを使用する場合は、「Conversation topic」と「Preview」を利用します。

f:id:koruneko:20200830232427p:plain

ただこの方法では、実行結果をみてもらえばわかるように、余計な文章まで取れてしまっています。
なので、split() 関数などで取得結果を整形してあげる必要があります。

また、グループに届いたメール全てが取得されますので、件名(topic)にてフィルターもかけてあげる必要があります。
この場合、「タスク "[タスクタイトル]" のコメント」形式の件名でメールが送られてくるはずなので、これらの情報をもとにフィルター処理を行ってください。

ただ、件名でフィルター処理するの面倒くさいですよね。
それにすべてのメールを取得すると、処理に時間がかかってしまいます。

なので、コメントを取得したいタスクが予めわかっている場合は、「Get a group conversation」を利用しましょう。
「Group ID」には先ほど同様、対象のグループのID を、「Conversation ID」には先ほどのアクションで取得した「Conversation ID」を入力します。

f:id:koruneko:20200830233800p:plain

Planner のコメントはOutlook には新規ではなく、返信される形で送られてくるので、「Conversation ID」は固定となります。

なので、初回実行時に「Conversation ID」を取得しておき、その値を設定してあげればフィルター処理などは不要になりますね。

ただ、これまでの説明をみていただいてわかるようにPlanner のコメントを取得するというのは少々面倒です...

試してみるというのはいいかもしれませんが、実際にこのやり方を用いて運用するかどうかは今後の保守性や実装にかかる時間を考慮して、慎重に考えたほうが良いと思います。

コメント取得方法(2020/10/04 追記)

-----------------2020/10/04(日) 追記-----------------
simo-k さんよりコメントにてアドバイスをいただきました。
以下方法を実践することにより、Planner のタスク名が変更になっても取得でき、また初回に「Conversation ID」を取得しなくてもよくなるようです。

1.conversationThreadIdの取得  {outputs('タスクを取得する')?['body/conversationThreadId']} 2.conversationThreadIdの判定  length>0でコメント有り。 3.スレッドの取得  "Get a conversation thread"で、別で求めたTeamIDと上記conversationThreadIdを利用。 ※この手順でやれば、タスク件名を途中へ変更されたり、同一タスク名が存在していても識別可能。

simo-k さん、ありがとうございます!

では早速やり方です。

まずは「タスクを取得する」を選択します。

f:id:koruneko:20201004155036p:plain

続いて、条件にてコメントが存在するかを判定します。
以下の式の値が0より大きかったらコメントがある。と判断します。

length(outputs('タスクを取得する')?['body/conversationThreadId'])

ここで1点注意です!
outputs('タスクを取得する')?['body/conversationThreadId']ですが、動的なコンテンツを探しても対応する値は見つからないかと思います。
ですので、上記式を直接記載することで、「Conversation ID」を取得しましょう。

あとは以前紹介したやり方と同じですね。

「Get a conversation thread」にて「Group ID」と「Thread ID」を設定してコメントを取得します。

f:id:koruneko:20201004155549p:plain

これでタスク名が変更になってもコメントを取得できるほか、初回に「Conversation ID」を取得する必要がなくなりましたね。

Azure Cosmos DB を利用してみる

はじめに

今回は、2020年3月6日にMicrosoft より発表されたAzure Cosmos DB Free レベルを試してみた記事となります。

発表内容の概要は下記の公式サイトをご覧ください。
Azure Cosmos DB Free レベルを使用できるようになりました

Azure Cosmos DB

そもそもAzure Cosmos DBってなに?という方はこちらをご確認ください。
Azure Cosmos DB の概要

簡単に纏めますと以下の特徴があります。

  • Microsoft の提供するグローバルに配布されるマルチモデル データベース サービス
  • ユーザーの最も近場所にあるデータのレプリカが利用される
  • 透過的なマルチマスター レプリケーションにより、Cosmos DB では読み取りと書き込みの両方に対して 99.999% の高可用性が実現
  • NoSQL のクライアント ドライバーでCosmos データベースを直接参照することが可能

f:id:koruneko:20200813171640p:plain

Azure Cosmos DB を利用してみる

リソースの作成

  1. Microsoft Azure Portalにアクセスし、「リソースの作成」を選択します。
    f:id:koruneko:20200813171853p:plain

  2. 「Azure Cosmos DB」を選択します。
    f:id:koruneko:20200813172104p:plain

  3. Azure Cosmos DB アカウントの作成画面が表示されるので、必要項目を入力します。
    まずは「基本」項目の入力です。

    • サブスクリプション:任意のサブスクリプションを選択します。
    • リソースグループ:任意のリソースグループを選択します。
    • アカウント名:任意のアカウント名を入力します。
    • API:自身の利用に適したAPI を選択します。Azure Cosmos DB に適した API を選ぶ
         今回は「コア(SQL)」を選択します。
    • Notebooks(Preview):Off
    • 場所:任意のregionを選択します。
    • Apply Free Tier Discount:Apply (ここでApply を選択しないとFree レベルを利用できません)
    • Account Type:Non-Production
    • geo 冗長性:無効
    • マルチリージョン書き込み:無効 f:id:koruneko:20200813172551p:plain

続いて、「ネットワーク」項目の入力です。

こちらでは、Cosmos DB へのネットワーク接続を選択します。

今回は「All networks」を選択します。

f:id:koruneko:20200813234917p:plain

次は「Backup Policy」です。

バックアップの種類、バックアップの実行頻度、バックアップデータの保存期間を設定することができます。

英語で記載もありますが、アカウント作成後、バックアップポリシーを切り替えることはできませんのでご注意ください。

今回は下の画像のように設定します。

f:id:koruneko:20200814030036p:plain

続いて、「Encryption」項目の入力です。

こちらでは、暗号化の設定を行います。

今回はデフォルトの設定を利用します。

f:id:koruneko:20200814031509p:plain

必要に応じてタグを設定し、「確認と作成」で表示された内容に誤りがなければ、「作成」を選択します。

デプロイが完了するまで、少し待ちましょう。

コンテナを作成する

デプロイが完了したら、リソースへ移動しましょう。
「クイックスタート」より、「Create 'Items' container」を選択します。

f:id:koruneko:20200814032733p:plain

「データ エクスプローラ」を選択し、画面を切り替えると、先ほど作成したコンテナを確認することができます。

f:id:koruneko:20200814033025p:plain

また、こちらのデータ エクスプローラーでもコンテナを作成することができます。

f:id:koruneko:20200814033209p:plain

こちらから作成を行う場合は以下の項目を入力する必要があります。

  • DataBase id:データベースのIDを選択、もしくは入力します。
  • Throughput:データベースのスループットを入力します。Free レベルは400RUまで無料枠です。
  • Container id:コンテナIDを入力します。
  • Partition key:複数サーバー間でパーテーション分割を行うために利用されます。
  • Analytical store:リアルタイムなアナリティクスを実行するための分析機能を有効にするか設定します。

f:id:koruneko:20200814033820p:plain

データを作成する

データを作成してみましょう。

「New Item」を選択します。

f:id:koruneko:20200814034941p:plain

適当なアイテムを作成し、「Save」を選択します。

{
    "id": "1",
    "Person":{
        "name": "korune",
        "age": 23,
        "sex": 0,
        "birthDay":"1997/02/24"
    }
}

f:id:koruneko:20200814040155p:plain

登録が完了すると、以下のようにシステム側で自動で項目と値が割り当てられます。

f:id:koruneko:20200814040345p:plain

クエリを実行してみる

画像の赤枠箇所を選択し、クエリ入力画面を開きます。

f:id:koruneko:20200814040545p:plain

「Query 1」タブにて、SQLを実行してみましょう。
以下のようなSQLを実行し全てのデータを抽出します。

SELECT * FROM Items

クエリの実行は「Execute Query」より行えます。

f:id:koruneko:20200814040926p:plain

実行を行うと、先ほどのデータが抽出されていることが確認できます。

【Adaptive Cards】入力項目を動的に増やす

はじめに

まずはこちらをご覧ください!

なんと送信されたアダプティブカードの入力項目をユーザー操作によって動的に増やすことができました!

今回はこのようなオブジェクトを動的に増やすことができるアダプティブカードの作成方法を紹介したいと思います。

ネタばらし

早速ですがネタばらしです。

先ほど紹介したアダプティブカードですが、あくまで動的に項目が増えているようにみせかけているだけです。
下の画像のように予め項目を作成しておき、初期表示時は非表示に設定し、ボタンが押されると表示/非表示が切り替わるように設定しています。

f:id:koruneko:20200811233033p:plain

これを実現している機能は「ToggleVisibility」というアクションです。

ToggleVisibility

ToggleVisibility の基本的な利用方法の紹介は、Hiro さんがAdaptive Cards のアクション [ToggleVisibility] と [ShowCard] を使ってみよう - ToggleVisiblity 編でわかりやすく解説してくれています。

ToggleVisibility を利用することにより、対象のオブジェクト表示/非表示を切り替えることができます。

Adaptive Cards Designer 上で「ActionSet」>「Action.ToggleVisibility」を選択することで利用が可能です。

f:id:koruneko:20200811234456p:plain

Action.ToggleVisibility のリファレンスをみてみると、このボタンが押されたときに表示/非表示が切り替わる項目は、「targetElements」にて対象の項目のID を指定する必要があるようです。

「ActionSet」及び「Action.ToggleVisibility」のプロパティをみてみましょう。

f:id:koruneko:20200812000655p:plain

f:id:koruneko:20200812000036p:plain

こちらをみてわかる通り、プロパティペインには「targetElements」を設定する箇所は存在しません。。。

では次にCARD PAYLOAD EDITOR より作成されたJSON をみてみましょう。

{
    "type": "ActionSet",
    "actions": [
        {
            "type": "Action.ToggleVisibility",
            "title": "Target1"
        }
    ],
    "id": "target1"
}

こちらをみても「targetElements」は存在しませんね...

ということで、直接CARD PAYLOAD EDITOR を編集して、「targetElements」を指定します。

以下のようなカードを作成してみます。

f:id:koruneko:20200812002317p:plain

f:id:koruneko:20200812002342p:plain

それぞれのオブジェクトのID は以下のように設定しました。

Item ID
Column(上) col1
Text1 text1
Input.Text(上) textInput1
Column(下) col2
Text2 text2
Input.Text(下) textInput2
Target1 target1
Target2 target2

作成が完了できたら、「Action.ToggleVisibility」のJSON を変更してみましょう。
「Action.ToggleVisibility」の要素に「targetElements」を作成します。

{
    "type": "ActionSet",
    "actions": [
        {
            "type": "Action.ToggleVisibility",
            "title": "Target1",
            "targetElements":[
                "col1"
            ]
        }
    ],
    "id": "target1"
}

上で記載したJSON と比較してわかるかもですが、

"targetElements":[
    "col1"
]

という設定を追記しています。

Hiro さんのブログでは、「targetElements」に表示/非表示を行いたい要素をひとつずつ設定していますが、このように親要素となるアイテムを「targetElements」に設定してあげることにより、子要素のアイテムも表示/非表示を切り替えることが可能です。

表示/非表示を行いたい項目が複数に渡り、また共通的な項目の場合は、例のようにColumnSet や Container 内にアイテムを設定してあげると、後々の編集なども容易になるかもしれません。

例のようなカードの作成方法

さて、本題の最初に挙げたような動的に入力項目が増えているように見えるアダプティブカードの作成方法ですが、こちらも「Action.ToggleVisibility」を応用したものになります。

流れとしては、

  1. 項目追加ボタンを押す。
  2. 自身の親カラム(Column)を非表示にし、次の入力項目カラムと項目追加/削除カラムを表示する。

となっています。
項目削除はその逆ですね。

アイテムの初期表示は、「Initially visible」により制御可能です。

f:id:koruneko:20200812004751p:plain

これらを理解したうえで、今回使用したJSON をみてみましょう!
※長いので一部省略しています。

{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.2",
    "body": [
        {
            "type": "TextBlock",
            "text": "日報"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "始業時間"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Time",
                            "id": "startTime"
                        }
                    ]
                }
            ]
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "退勤時間"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Time",
                            "id": "endTime"
                        }
                    ]
                }
            ]
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "タスク名"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "進捗率[%]"
                        }
                    ]
                }
            ]
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Text",
                            "placeholder": "Placeholder text",
                            "id": "taskName1"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Number",
                            "placeholder": "Placeholder text",
                            "min": 0,
                            "max": 100,
                            "id": " progress1",
                            "errorMessage": "0から100の数値を入力してください。"
                        }
                    ]
                }
            ],
            "id": "inputCol1"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "ActionSet",
                            "actions": [
                                {
                                    "type": "Action.ToggleVisibility",
                                    "title": "項目を追加する",
                                    "targetElements": [
                                        "inputCol2",
                                        "buttonCol1",
                                        "buttonCol2"
                                    ]
                                }
                            ],
                            "id": "addButton1"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch"
                }
            ],
            "id": "buttonCol1"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Text",
                            "placeholder": "Placeholder text",
                            "id": "taskName2"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Number",
                            "placeholder": "Placeholder text",
                            "id": " progress2",
                            "errorMessage": "0から100の数値を入力してください。",
                            "min": 0,
                            "max": 100
                        }
                    ]
                }
            ],
            "isVisible": false,
            "id": "inputCol2"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "ActionSet",
                            "actions": [
                                {
                                    "type": "Action.ToggleVisibility",
                                    "title": "項目を追加する",
                                    "targetElements": [
                                        "inputCol3",
                                        "buttonCol2",
                                        "buttonCol3"
                                    ]
                                }
                            ],
                            "id": "addButton2"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "ActionSet",
                            "actions": [
                                {
                                    "type": "Action.ToggleVisibility",
                                    "title": "項目を削除する",
                                    "targetElements": [
                                        "inputCol2",
                                        "buttonCol1",
                                        "buttonCol2"
                                    ]
                                }
                            ],
                            "id": "deletButton2"
                        }
                    ]
                }
            ],
            "id": "buttonCol2",
            "isVisible": false
        },
        
        ...(略)...
        
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Text",
                            "placeholder": "Placeholder text",
                            "id": "taskName9"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Number",
                            "placeholder": "Placeholder text",
                            "id": " progress9",
                            "errorMessage": "0から100の数値を入力してください。",
                            "min": 0,
                            "max": 100
                        }
                    ]
                }
            ],
            "isVisible": false,
            "id": "inputCol9"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "ActionSet",
                            "actions": [
                                {
                                    "type": "Action.ToggleVisibility",
                                    "title": "項目を追加する",
                                    "targetElements": [
                                        "inputCol10",
                                        "buttonCol9",
                                        "buttonCol10"
                                    ]
                                }
                            ],
                            "id": "addButton9"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "ActionSet",
                            "actions": [
                                {
                                    "type": "Action.ToggleVisibility",
                                    "title": "項目を削除する",
                                    "targetElements": [
                                        "inputCol9",
                                        "buttonCol8",
                                        "buttonCol9"
                                    ]
                                }
                            ],
                            "id": "deletButton9"
                        }
                    ]
                }
            ],
            "id": "buttonCol9",
            "isVisible": false
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Text",
                            "placeholder": "Placeholder text",
                            "id": "taskName10"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Input.Number",
                            "placeholder": "Placeholder text",
                            "id": " progress10",
                            "errorMessage": "0から100の数値を入力してください。",
                            "min": 0,
                            "max": 100
                        }
                    ]
                }
            ],
            "isVisible": false,
            "id": "inputCol10"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "ActionSet",
                            "id": "addButton10"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "ActionSet",
                            "actions": [
                                {
                                    "type": "Action.ToggleVisibility",
                                    "title": "項目を削除する",
                                    "targetElements": [
                                        "inputCol10",
                                        "buttonCol9",
                                        "buttonCol10"
                                    ]
                                }
                            ],
                            "id": "deletButton10"
                        }
                    ]
                }
            ],
            "id": "buttonCol10",
            "isVisible": false
        },
        {
            "type": "ActionSet",
            "actions": [
                {
                    "type": "Action.Submit",
                    "title": "送信",
                    "id": "submit"
                }
            ]
        }
    ]
}

...(略)...と記載されている箇所は、本ブログの説明内容と、前後の記載を参考に自身で埋めてみてください。

もしわからない箇所などあればお気軽にお尋ねくださいー。

コルネ (@koruneko32767) | Twitter

まとめ

このようにアダプティブカードも工夫次第で、少し変わった動きをさせることができます。

ただこの方法で作成したアダプティブカードの結果をPower Automate で取得した場合、ユーザーが非表示にしていた項目も未入力項目として取得されてしまいますので、データの取得の際には未入力項目を弾くなどのなんらかの処理を加えたほうが良いかもしれません。

【Power Apps】入力候補を表示させる

今回作成するもの

今回はPower Apps で、入力候補を表示してくれるオブジェクトの作成を行ってみたいと思います。

上記ツイート埋め込み動画の、
左:Power Apps 標準のコンボボックス
右:今回作成した入力補助を行ってくれるテキストボックス

動画のように、Power Apps 標準オブジェクトを使用しただけでは、入力候補の表示は行えません。
今回は右のような機能の実装方法を紹介します。

利用するオブジェクト

以下オブジェクトを利用します。

  • Gallery(Label):入力候補を表示するためのオブジェクト
  • TextInput:テキスト入力を行うためのオブジェクト
  • Icon:テキスト入力の右端に配置するためのオブジェクト
  • Checkbox:アイコンを押している状態かを判定するためのオブジェクト
          透明に設定する

f:id:koruneko:20200809154855p:plain

入力リストを作成する

今回、入力リストには「Power Automate のマイフロー一覧」を用いたいと思います。
※ このリストの作成方法に関しては今回詳しく触れません。

  1. コネクタより、「Power Automate 管理」を追加します。
  2. App.OnStart に以下式を入力します。

App.OnStart

Clear(ListMyFlowGallery);
ForAll(
    PowerAutomate管理.ListMyFlows(First(PowerAutomate管理.ListUserEnvironments().value).name).value,
    Collect(
        ListMyFlowGallery,
        {id:name, dispName:PowerAutomate管理.GetFlow(First(PowerAutomate管理.ListUserEnvironments().value).name, name).properties.displayName}
    )
);

これで、「ListMyFlowGallery」というコレクションにマイフロー一覧が設定されます。

入力候補を設定する

ギャラリーを設定する

「挿入」>「ギャラリー」>「縦方向(空)」を選択します。
こちらが、入力候補を表示させるためのオブジェクトになります。

Items にはListMyFlowGallery を設定します。

Gallery1.Items

ListMyFlowGallery

ギャラリー内に表示させるラベルを作成します。

Gallery1 を選択した状態で、「ラベル」を選択します。

ラベルにはマイフローの表示名を表示させたいと思います。
これはListMyFlowGallery の「dispName」にあたります。

ラベルのプロパティを以下のように設定します。

Label1.Text

ThisItem.dispName

Label1.Width

Parent.Width

Label1.HoverFill

Color.DarkGray

これで、

  • ラベルに表示される名前がマイフローの表示名
  • ラベルの幅がギャラリーの幅と同様の設定
  • マウスホバーした項目がグレーに変化

となるように設定できました。

テキスト入力項目を設定する

「テキスト」>「テキスト入力」を選択します。
こちらで入力された内容をもとに入力候補に表示されるアイテムをフィルターしたいと思います。

設定が完了したら、テキスト入力ではなく先ほど作成したギャラリーのプロパティを編集します。
ギャラリーに表示されるアイテムを絞りたいので、ギャラリーのItems を編集します。

Gallery1.Items

Search(ListMyFlowGallery, TextInput1.Text, "dispName")

これでテキスト入力に入力された内容でアイテムを絞ることができましたね。

細かい設定

入力保管項目の表示条件

入力保管項目が、表示される条件は以下の時としたいと思います。

  • 右端のプルダウンが選択されたとき
  • テキスト入力にテキストが入力されたとき

まずはプルダウンを作成したいと思います。

「アイコン」>「下」を選択します。

X、Y、Width、Height はテキスト入力項目に合わせておきましょう。

Icon1.X

TextInput1.X + TextInput1.Width - Self.Width

Icon1.Y

TextInput1.Y

Icon1.Width

Self.Height

Icon1.Height

TextInput1.Height

標準のコンボボックスと同じような見た目にしたければ以下の設定も変更します。

Icon1.Fill

ColorFade(RGBA(56, 96, 178, 1), -20%)

Icon1.Color

RGBA(255, 255, 255, 1)

Icon1.PaddingXXX(Top、Left、Right、Bottom)

7

こちらのアイコンはただの見た目にしか使用しないため設定はここまでです。

プルダウン箇所が押されたかどうかは「入力」>「チェック ボックス」を使用します。

X、Y、Width、Height はアイコンの設定と同様としてください。
表示されるテキストは空文字を設定してください。

こちらのチェックボックスは選択されたかどうかを判断するためだけのもので、実際には画面上に表示されなくてよいです。
だからといって、「表示(Visible)」をfalse に設定してはいけません。
非表示にしてしまうと選択も出来なくなってしまうからです。
なので、こちらのチェックボックスは透明に設定しましょう!

「XXXColor」や「XXXFill」となっている色に関するプロパティを「RGBA(0, 0, 0, 0)」に設定します。
これでどう動作させようが、透明に設定できたかと思います。

プルダウンが作成できたので、入力保管項目(Gallery1)の表示条件を変更したいと思います。

Gallery1.Visible

Or(Checkbox1.Value, !IsBlank(TextInput1.Text))

「Checkbox1.Value」は先ほど作成したプルダウンのチェックボックスが選択されている状態か、否かを判定しています。
前者の場合はtrue に、後者の場合はfalse になりますね。

「!IsBlank(TextInput1.Text)」はテキスト入力項目に文字が入力されているか否かを判定しています。
IsBlank の否定形(!)なので、前者の場合はtrue に、後者の場合はfalse になりますね。

これで、入力保管項目の表示条件が作成できました!

入力保管項目で選択されたアイテムをテキスト入力に設定する

入力保管項目で入力されたアイテムでテキスト入力項目を更新したいと思います。

これを実現するために、変数を利用します。

App.OnStart に以下の式を追加します。

App.OnStart

Set(flowName, "")

こちらの変数をテキスト入力のデフォルト値に設定しましょう。

TextInput1.Default

flowName

これでテキスト入力項目のデフォルト値にflowName が設定されましたので、入力保管項目のアイテムが選択されたときに変数が更新されるようにしましょう。

Lanel1.OnSelect

Set(flowName, ThisItem.dispName)

これで入力保管項目で選択されたアイテムをテキスト入力に設定することができました!!

おわり

以上でPower Apps での入力保管項目の作成は完了です。
標準のオブジェクトで満足できない場合、工夫によってはこのように自作しちゃうこともできちゃいますので、諦めずに実現方法を考えてみるといいかもですね。

【Power Automate × Microsoft Teams】ワードウルフを作成してみる ~その2~

はじめに

この記事は【Power Automate × Microsoft Teams】ワードウルフを作成してみる ~その1~の続きです。
まだご覧になれれていない方は上記記事をご覧になることをお勧めします。

PowerAutomate の作成

カウントダウンメッセージ

残りX分などのカウントダウンメッセージをチームに送信する処理を作成します。

f:id:koruneko:20200803023528p:plain

カウントダウンを行うために、処理を指定時間停止させます。
これを実現するために。「スケジュール」の「遅延」を利用します。

f:id:koruneko:20200803024211p:plain

f:id:koruneko:20200803024257p:plain

作成できたら、以下のように入力します。

  • カウント:待ち時間の入力項目です。今回は「4」と入力します。
  • 単位:「カウント」で入力した数値の単位を指定します。今回は「分」を選択します。

これにより4分間、「待ち時間」で処理が停止します。

この処理の後にチームに残り時間を知らせるメッセージを送ればカウントダウンが作成できますね。

f:id:koruneko:20200803024622p:plain

同じ要領で、1分待機した後にゲーム終了メッセージを送信する処理も作成してみましょう。

投票処理

ゲーム終了後に、「誰がワードウルフだと思ったか?」を参加者全員のチームに送信する処理を作成します。

f:id:koruneko:20200803024748p:plain

参加者全員に同時にメッセージを送信する

メッセージはアダプティブカードを送信し、アダプティブカードより投票を行いますので、ユーザーの応答を待つ必要があります。
このとき気を付けなくてはいけない点として、複数人に同時に投票メッセージが送信されるように設定しないといけません。
なにも設定を行わないと、最初にメッセージを送ったユーザーが解答(もしくはタイムアップ)するまで次のユーザーへのメッセージの送信が行われません。

上記設定を行うために、「アダプティブカードを Teams ユーザーに投稿して応答を待機」を「Apply to each」内に作成し、「Apply to each」の「設定」より「コンカレンシー制御」を「オン」にします。

f:id:koruneko:20200803025703p:plain

f:id:koruneko:20200803025554p:plain

f:id:koruneko:20200803030153p:plain

その後、並列処理を行ってほしい度合い(今回でいうと想定する参加者の最大数)だけ、次数を設定し、「完了」を選択します。

なおこちらを設定する際の注意点として、処理の順番にこだわらない場合のみ設定してください。

投票確認メッセージの送信

先ほども記載した通り、投票メッセージの送信には、アダプティブカード(待機)を利用します。

投票確認メッセージは以下のようなアダプティブカードを作成します。

f:id:koruneko:20200806004328p:plain

このとき、選択肢のユーザーは参加者によって可変としたいと思います。
これを実現するために、コンボボックスの選択肢に設定する値は、前回「参加者情報の作成」で作成した、「choice」変数を利用したいと思います。

まずは、アダプティブカードのコンボボックス、"Input.ChoiceSet" の利用方法をみてみましょう。

{
    "type": "Input.ChoiceSet",
    "choices": [
        {
            "title": "Choice 1",
            "value": "Choice 1"
        },
        {
            "title": "Choice 2",
            "value": "Choice 2"
        }
    ],
    "placeholder": "Placeholder text"
}

"choices" の中身がコンボボックスで表示される値になります。
前回作成した、「choice」変数の中身と一緒ですね!

では、これらの情報をもとに、アダプティブカードの作成を行います。
なお作成の際、「試験的な機能」を「オン」にされている方は一度「オフ」にしてください。

f:id:koruneko:20200806005811p:plain

理由は、card JSON の解説の際に行いたいと思います。

アダプティブカードには以下を設定します。

受信者

@{items('投票処理')?['mention']};

メッセージ

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.2",
    "body": [
        {
            "type": "TextBlock",
            "text": "~投票タイム~",
            "weight": "Bolder",
            "size": "Medium"
        },
        {
            "type": "TextBlock",
            "isSubtle": true,
            "wrap": true,
            "text": "「ウルフ」だと思われるユーザーを選択してください。"
        },
        {
            "type": "TextBlock",
            "text": "名前",
            "wrap": true
        },
        {
            "type": "Input.ChoiceSet",
                "choices": [
                @{take(variables('choise'), sub(length(variables('choise')), 1))}
            ],
           "placeholder": "Placeholder text",
            "id": "choiseResult"
        }
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "送信",
            "id": "submit"
        }
    ]
}

更新メッセージ

投票が完了しました。

カードの更新が必要

はい

メッセージに設定している、card JSON ですが、

@{take(variables('choise'), sub(length(variables('choise')), 1))}

こちらがコンボボックスの選択肢を設定している箇所になります。
なぜ、このような式にしているかというと、最後の「,」を取り除くためですね。
記事書いてて気づきましたが、「作成」アクション利用したほうがよかったかもですね。。。

また、こちらの箇所が「試験的な機能」をオフにしてもらった原因の箇所となります。
オンにしたまま、上記JSON を記載すると、JSON の解析でエラーが発生してしまうかと思います。
これは、Power Automate 上のAdaptive Cards Designer が、渡された配列を文字列としか認識してくれず、”title”、"value"が設定されていないと、よみとられてしまっているからのようです。

なお、こちらを設定、保存後に「試験的な機能」をオンにしてフローを実行しても正しく動作を行うことができます。
ただし、編集を行いたい際は都度「試験的な機能」をオフにする必要があるようです。
ちょっとめんどくさいですね。。。

投票結果の取得

投票メッセージで回答された投票結果を「result」変数に設定したいと思います。

「作成」を作成して、以下のように設定します。

{
  "dispName": @{items('投票処理')?['dispName']},
  "vote": @{body('投票確認メッセージの送信')?['data']?['choiseResult']}
}

「@{body('投票確認メッセージの送信')?['data']?['choiseResult']}」でユーザーが選択した"ChoiceSet"の値を取得することができます。
"choiseResult"には「"id": "choiseResult"」で設定した値を設定してください。
また、こちらの式は、探しても見つからないかもしれません。
そのときはこちらの式を手入力すると、次回更新時Power Automate が式として認識し表示も変わるかと思います。

こちらの「作成」での出力結果を変数「result」に設定しましょう。

投票結果の送信

f:id:koruneko:20200806015928p:plain

先ほど作成した、変数「result」を「JSON の解析」で分解を行います。
スキーマは以下のようになるかと思います。

スキーマ

{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "dispName": {
                "type": "string"
            },
            "vote": {
                "type": "string"
            }
        }
    }
}

こちらをもとに投票結果のメッセージを作成します。
変数「voltMessage」に以下を設定します。

@{items('投票結果メッセージの作成')?['dispName']}さん:@{items('投票結果メッセージの作成')?['vote']}<br>

最後に任意のチャネルにメッセージを送信します。
任意のチャネルを選択し、メッセージは例えば以下のように設定します。

メッセージ

@{variables('mention')}
投票結果はこちらです。<br>
@{variables('voltMessage')}

みなさんに送ったワードはこちらです。<br>
@{variables('resultMessage')}

ゲーム続行の確認

最後にゲームを同じメンバーで続けるか?を参加者に確認するメッセージを作成しましょう。

アダプティブカードを Teams チャネルに投稿して応答を待機」を選択します。

作成が完了したら、「アダプティブカードの編集」を選択します。

下記ようなアダプティブカードを作成します。

f:id:koruneko:20200806021042p:plain

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.2",
    "body": [
        {
            "type": "TextBlock",
            "text": "同じメンバーでゲームを続けますか?",
            "weight": "Bolder",
            "size": "Medium"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "width": "stretch",
                    "type": "Column",
                    "items": [
                        {
                            "type": "ActionSet",
                            "actions": [
                                {
                                    "title": "はい",
                                    "type": "Action.Submit",
                                    "id": "continueYes",
                                    "data": true
                                }
                            ]
                        }
                    ]
                },
                {
                    "width": "stretch",
                    "type": "Column",
                    "items": [
                        {
                            "type": "ActionSet",
                            "actions": [
                                {
                                    "title": "いいえ",
                                    "type": "Action.Submit",
                                    "id": "continueNo",
                                    "data": false
                                }
                            ]
                        }
                    ]
                },
                {
                    "width": "stretch",
                    "type": "Column"
                },
                {
                    "width": "stretch",
                    "type": "Column"
                }
            ]
        }
    ]
}

「はい」が押されたのか、「いいえ」が押されたのかは「(body('ゲームを続けるか?')['submitActionId'])」で取得することができます。
こちらで取得することができるのは、押されたSubmitButton のid です。 なので上記でいうと、
「はい」が押されたときは"continueYes"が、
「いいえ」が押されたときは"continueNo"が、
それぞれ返されます。

ゲームの実行条件は、「isContinue」が「true」のときと設定しましたね。
なので、ゲーム続行確認メッセージの応答で更新したいと思います。

変数「isContinue」には、「はい」が押されたときには「true」、「いいえ」が押されたときは「false」がそれぞれ設定されるようにします。

「変数の設定」を選択して、以下のように設定します。

@{equals(body('ゲームを続けるか?')['submitActionId'], 'continueYes')}

また、id にそれぞれ「true」「false」を設定しておくのもいいかもですね。

変数のリセット

ゲームを続行するにあたり一部変数のリセットを行う必要があります。

対象項目は「resultMessage」「voltMessage」「result」の3つです。

f:id:koruneko:20200806022222p:plain

これらは、変数に値をセットする際、更新ではなく追加を行っていますね。
なのでこのようにリセットしないと前回のゲームの情報が引き継がれてしまいます。

設定する値は、画像のように

null

を設定しておきましょう。

さいごに

以上でPower Automate を利用した、Teams でのワードウルフの作成方法の紹介は終了です。
少し説明を省いてしまった箇所もありますので、不明点などありましたらお気軽にお尋ねください。

Japan Power Platform Game Builders (JPPGB)というFacebook グループを作成してみました!

f:id:koruneko:20200806023135p:plain

作成したゲームを自慢したり、作成途中のゲームの実装方法などについて質問情報共有を行う場として作成しました。

グループが盛り上がってきたらなにかしらのイベントなども作成していきたいですねー。


スポンサードリンク