ささみ学習帳 - sasami's study book

ささみ学習帳

Microsoft365 や Power Platform について学んだこと・アイデアのメモ

Plannerに担当者1人ごとにタスクを作成するPower Automateクラウドフロー

 

Plannerでタスク管理するうえで使いづらい所の1つ

Microsoft Plannerのタスクには複数の担当者を割り当てることができますが、誰か1人が完了したらタスクが完了してしまいます。

複数ユーザー割り当てできるが個別作業のタスクには向かない

例えば、「健康診断の日程調整アンケートへの回答」といったタスクの場合は、担当者それぞれが回答しタスクを完了する形で登録したいところですが、Plannerの標準機能ではタスクを人数分複製して担当者を割り当てるといった人数次第では膨大で地道な作業が必要になってきます。

個別作業のタスクはユーザーごとにタスク登録すればよいが人数が増えると煩雑な作業に

そこで、この作業を自動化するフローを作成してみました。

 

このフローで実現できること

  • 選択したTeamsチャネルメッセージを引用してPlannerプランにタスクを作成する
    • プランは指定チーム内で選択
    • タスクは指定したユーザーごとに1つ作成する
      • (同名のタスク×担当者人数)のタスクを作成する
  • それらのタスクはタスクと同名のバケットに保存する

 

想定する使い方

チーム内の連絡事項はTeamsのチャネルで行うこととし、チャネルに投稿したメッセージをPlannerにタスクとして登録する

 

フロー実行イメージ

チャネルメッセージを選択してフローを実行します。

 

表示されたメッセージに従い、チャットを確認します。

 

Power Automateからチャットアダプティブカードが届きます。

 

アダプティブカードに登録内容を入力し「担当者ごとにタスク作成」ボタンをクリック

 

しばらく待つと完了通知が届きます。

 

タスク作成を指示したプランをPlannerで確認すると、選択したプランにバケットが作成され同名のタスクを担当者ごとに作成された状態となります。

 

 

準備・必要なもの

  • メンバー全員が参加するTeamsチーム
  • Plannerプランを↑のチームに作成

 

フロー全体

 

フロー解説

1.トリガー「選択したメッセージの場合」

チャネルのメッセージ「…」からフローを実行します。

アダプティブカードを設定し、↓のようなメッセージが表示されるようにしています。

 

{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.3",
    "body": [
        {
            "type": "TextBlock",
            "text": "間も無くPower Automate からチャットメッセージが届きます。メッセージを開いて続けてください。",
            "wrap": true,
            "spacing": "None"
        }
    ]
}

2.作成-teamId

プランを作成したMicrosoft365グループ(チーム)のteamIdを指定します。

  • 入力:{プランを作成したチームのId}

3.チャットや2で指定したチャネル以外のチームからフローを実行した場合は終了する

「選択したメッセージの場合」トリガーは、チャットやチャネルのメッセージの「…」メニューから実行できます。ですが、今回のフローは最終的にPlannerプランにタスクを登録する事が目的なので、プランのメンバーがアクセスできるメッセージ以外を設定できてしまっても意味がありません。その為、「選択したメッセージの場合」トリガーの出力に含まれるteamIdが、プランのteamIdと一致しない場合はフローを終了しています。運用者がアクセス許可を意識して運用できる場合は、この条件自体を削除しても問題ないかと思います。

 

4.スコープ「チームメンバーを取得する」

タスクの担当者を選択できるようにしたいのでプランのチームメンバーを取得します。選択アクションで表示名+メールアドレスのアレイ・メールアドレスのアレイを作成しています。

表示名+メールアドレスのアレイは担当者の選択肢で、メールアドレスのアレイは既定で全員を選択状態にするために使用します。

 

表示名+メールアドレスのアレイは表示名でソートします。

sort(
    body('選択-メンバーの名前とメールアドレスを取得'),
    'Title'
)

メールアドレスのアレイは結合アクションで","コンマ区切りのテキストに変換します。

 

5.スコープ「プランの一覧を取得する」

2.で指定したチームに含まれるプランを取得し、タスクを作成するプランを選択できるようにします。「グループのプランを一覧表示」アクションの出力から、選択アクションでTitleとValue(plan id)のみのアレイを作成し、タイトルでソートしています。

sort(
    body('選択-プランの名前とIdを取得'),
    'Title'
)

 

6.アダプティブカードを送信して応答を待機

プラン・バケット名(=タスク名)・開始日・終了日・メモ・担当者を指定できるアダプティブカードを作成します。

 

メッセージ本文のjson

{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.3",
"msteams": {  
        "width": "Full"  
    },  
    "body": [
        {
            "type": "Container",
            "items": [
                {
                    "type": "TextBlock",
                    "text": "Plannerに担当者ごとにタスク登録",
                    "wrap": true,
                    "weight": "Bolder"
                }
            ],
            "style": "accent"
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "FactSet",
                    "facts": [
                        {
                            "title": "件名",
                            "value": "@{triggerBody()?['teamsFlowRunContext']?['messagePayload']?['subject']}"
                        },
                        {
                            "title": "本文抜粋",
                            "value": "@{concat(
if(
    greater(
        length(triggerBody()?['teamsFlowRunContext']?['messagePayload']?['body']?['plainText']),100
    ),
    substring(triggerBody()?['teamsFlowRunContext']?['messagePayload']?['body']?['plainText'],0,100),
    triggerBody()?['teamsFlowRunContext']?['messagePayload']?['body']?['plainText']
),
'………'
)}"
                        }
                    ],
                    "separator": true
                },
                {
                    "type": "ActionSet",
                    "actions": [
                        {
                            "type": "Action.OpenUrl",
                            "title": "メッセージを開く",
                            "url": "@{triggerBody()?['teamsFlowRunContext']?['messagePayload']?['linkToMessage']}"
                        }
                    ]
                }
            ],
            "style": "emphasis"
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "Input.ChoiceSet",
                    "choices": @{outputs('作成-プランリスト')},
                    "placeholder": "プランを選択",
                    "id": "ChoiceSetPlan",
                    "label": "プラン",
                    "isRequired": true,
                    "errorMessage": "プランの選択は必須です。"
                },
                {
                    "type": "Input.Text",
                    "placeholder": "バケット名を入力",
                    "id": "TextBucket",
                    "label": "バケット名",
                    "isRequired": true,
                    "errorMessage": "バケット名は必須です。",
                    "value":"@{triggerBody()?['teamsFlowRunContext']?['messagePayload']?['subject']}"
                },
                {
                    "type": "ColumnSet",
                    "columns": [
                        {
                            "type": "Column",
                            "width": "stretch",
                            "items": [
                                {
                                    "type": "Input.Date",
                                    "id": "DateStart",
                                    "label": "開始日",
                                    "value": "@{convertFromUtc(
    utcNow(),
    'Tokyo Standard Time',
    'yyyy-MM-dd'
)}"
                                }
                            ]
                        },
                        {
                            "type": "Column",
                            "width": "stretch",
                            "items": [
                                {
                                    "type": "Input.Date",
                                    "id": "DateDue",
                                    "value": "@{convertFromUtc(
    addDays(utcNow(),30),
    'Tokyo Standard Time',
    'yyyy-MM-dd'
)
}",
                                    "label": "終了日"
                                }
                            ]
                        }
                    ]
                },
                {
                    "type": "Input.Text",
                    "id": "TextMemo",
                    "label": "メモ",
                    "isMultiline": true,
                    "value":"@{concat(
'【最新の情報はリンク先のメッセージを確認してください。】\n\n',
if(
    greater(
        length(triggerBody()?['teamsFlowRunContext']?['messagePayload']?['body']?['plainText']),100
    ),
    substring(triggerBody()?['teamsFlowRunContext']?['messagePayload']?['body']?['plainText'],0,100),
    triggerBody()?['teamsFlowRunContext']?['messagePayload']?['body']?['plainText']
),
'………'
)}"
                },
                {
                    "type": "Input.ChoiceSet",
                    "choices": @{outputs('作成-メンバーリスト')},
                    "placeholder": "Placeholder text",
                    "isMultiSelect": true,
                    "style": "filtered",
                    "id": "ChoiceSetAssign",
                    "label": "担当者",
                    "value": "@{body('結合-デフォルト選択リスト')}",
                    "isRequired": true,
                    "errorMessage": "担当者の選択は必須です。"
                }
            ],
            "separator": true
        },
        {
            "type": "Container",
            "items": [
                {
                    "type": "TextBlock",
                    "text": "(選択した担当者のメッセージのアクセス許可はタスク作成者が担保してください)",
                    "wrap": true,
                    "size": "small"
                }
            ],
            "style": "accent"
        },
        {
            "type": "ActionSet",
            "actions": [
                {
                    "type": "Action.Submit",
                    "id": "SubmitCreateTask",
                    "associatedInputs": "auto",
                    "title": "担当者ごとにタスク作成"
                }
            ],
            "$data": "SubmitCancel"
        }
    ]
}

 

このようなアダプティブカードが Power Automate bot からチャットで届きます。
(※メッセージを選択したチャネルではなくチャットで届きます)

 

アダプティブカードに入力して「担当者ごとにタスクを作成」をクリックすると次のようなJSONで返されます。

{
  "responseTime": "2023-08-31T02:30:24.4431862Z",
  "responder": {
    "objectId": "448060bd-fe71-424f-b70b-3456084c7ffd",
    "tenantId": "e67f4bb8-ad29-4458-9ae2-83b1ed90bdbd",
    "email": "sasami_axis@example.com",
    "userPrincipalName": "sasami_axis@example.com",
    "displayName": "Sasami (ささみ)"
  },
  "submitActionId": "SubmitCreateTask",
  "messageId": "1693449000686",
  "messageLink": "https://teams.microsoft.com/l/message/19:448060bd-fe71-424f-b70b-3456084c7ffd_358f0194-6b0e-4dd3-af35-c24fe8a9ec87@unq.gbl.spaces/1693449000686?context=%7B%22contextType%22:%22chat%22%7D",
  "data": {
    "ChoiceSetPlan": "VSJXDG9TYUOas5jnEYQaQGUACVY8",
    "TextBucket": "月次報告書だしてね",
    "DateStart": "2023-08-31",
    "DateDue": "2023-09-30",
    "TextMemo": "皆さん、お疲れさまです。今月も頑張ってくれはって、感謝しております。さて、今月の月次報告書の提出期限は8月25日(水)ですが、どうぞお忘れなくよろしくお願いします。月次報告書は、各自の業務内容や成果、課題や改善策などをまとめて、メールで送ってください。提出先は私のメールアドレスです。もし何かわからないことや困ったことがあれば、遠慮なく私に連絡してくださいね。\r\n月次報告書は、チームの活動状況や成長を把握するために大切なものです。皆さんの意見や感想もぜひ聞かせてください。よろしくお願いします。",
    "ChoiceSetAssign": "axxxxxxxx@example.com,MiriamG@example.com,PattiF@example.com,AdeleV@example.com,LeeG@example.com,NestorW@example.com,HenriettaM@example.com,IsaiahL@example.com,DiegoS@example.com,GradyA@example.com,AlexW@example.com,JohannaL@example.com,JoniS@example.com,LynneR@example.com,LidiaH@example.com,PradeepG@example.com,MeganB@example.com,sasami_axis@example.com,lesser_panda@example.com,arai_kuma@example.com,sasori@example.com,giant_panda@example.com"
  }
}

 

7.JSONの解析-カードの応答

アダプティブカードの応答として返されたjsonの値をパースします。

スキーマにはこちらを使用しています。

{
    "type": "object",
    "properties": {
        "responseTime": {
            "type": "string"
        },
        "responder": {
            "type": "object",
            "properties": {
                "objectId": {
                    "type": "string"
                },
                "tenantId": {
                    "type": "string"
                },
                "email": {
                    "type": "string"
                },
                "userPrincipalName": {
                    "type": "string"
                },
                "displayName": {
                    "type": "string"
                }
            }
        },
        "submitActionId": {
            "type": "string"
        },
        "messageId": {
            "type": "string"
        },
        "messageLink": {
            "type": "string"
        },
        "data": {
            "type": "object",
            "properties": {
                "ChoiceSetPlan": {
                    "type": "string"
                },
                "TextBucket": {
                    "type": "string"
                },
                "DateStart": {
                    "type": "string"
                },
                "DateDue": {
                    "type": "string"
                },
                "TextMemo": {
                    "type": "string"
                },
                "ChoiceSetAssign": {
                    "type": "string"
                }
            }
        }
    }
}

 

8.スコープ-バケット重複チェック

このフローでは担当者の数だけタスクを作成しますので、うっかりミスで重複してタスクを登録してしまった場合のリカバリーが大変になります。この為、アダプティブカード選択したプランに同名のバケットが存在する場合は処理を中止しています。

 

9.スコープ-Plannerでタスクを作成する

9-1.バケットを作成

まずタスクを格納するバケットを作成します。

 

9-2.作成-Assign

アダプティブカードの応答に含まれるAssignがタスクの担当者です。

","コンマ区切りのテキストになっていますので、Split関数でアレイに変換します。

split(
    body('JSON_の解析-カードの応答')?['data']?['ChoiceSetAssign'],
    ','
)

 

9-3.Apply to each-担当者づつ

9-2.でアレイ化した担当者で繰り返し処理します。

 

9-3-1.タスクを作成する

タスクを作成します。元メッセージへのリンクを設定しますが、リンクはタスクを作成するアクションでは設定できない為、タスク詳細の更新アクションで追記します。

開始日・終了日はUTCとなっているため日本時間に変換します。

formatDateTime(
    body('JSON_の解析-カードの応答')?['data']?['DateStart'],
    'yyyy-MM-ddTHH:mm:ss'
)
formatDateTime(
    body('JSON_の解析-カードの応答')?['data']?['DateDue'],
    'yyyy-MM-ddTHH:mm:ss'
)

 

10.スコープ-操作元ユーザーに通知

処理完了を操作元ユーザーにチャットで返します。

プラン名・バケット名・メッセージURLを出力しています。

 

不具合メモ

2023年8月時点では、Teamsがパブリックプレビュー/ターゲットリリースの環境では、「選択したメッセージの場合」トリガーでメッセージの件名が取得できない問題が発生しているようです。このページのスクリーンショットで件名が取得出来ていないのはその為です。

上記のフローは本来は、選択したメッセージの件名をアダプティブカードのバケット名の既定値として表示する動作をします。

 

今後の予定

  • 先日作成した選択したメッセージをPlannerにタスク登録するPowerAppsアプリにも今回のフローの機能を組み込めるのでは…

sasami-axis.hatenablog.com

 

 

さいごに

このフローは、RPACommunity でわたるふさんがLT登壇された「Microsoft Teamsでメンション先をチェックボックスで指定する仕組みを作ってみた」のフローの選択したメッセージトリガー→アダプティブカードで応答、という手法をそのまま使わせていただいています。わたるふさんのblogはいつも参考にさせていただいています。大感謝です。

wataruf.hatenablog.com