PowerAppsで他人のOutlookスケジュールを一覧表示する


# 他人の予定表を取得するアプリ
- Power Appsで他人のOutlookの予定一覧を簡単に閲覧する方法を解説します。
- Power Appsで検索してよく出てくる方法は、Outlookコネクタの「Office365Outlook.FindMeetingTimesV2」関数を使った方法で、日程調整アプリなどで他人の予定が空いているかどうかの確認に使用できます。 
    - この方法にはデメリットがあり、**空き時間しか分からない**、他人が会議に返答していないと**空き時間扱いになる**
- そこで今回は、Graph APIを使ってOutlookと同じく会議の開始～終了の時間や参加の情報まで一覧で取得します。今のところ標準コネクタで呼び出せるのでプレミアムプランは必要ありません。
- 例えば以下のように表示します。 工夫すればガントチャート表示もできると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/87f44705-7a30-6d55-14ff-fdaaba9ce39f.png)


# Outlookの予定表のアクセス権に関する知識
Outlookのデスクトップアプリの画面で確認すると、予定表には以下のような4段階のアクセス権があります。
既定では他人から、会議の`タイトルと場所`の表示が可能です。
※組織によっては共有相手が自組織の配下に制限されている場合もあります。
Outlookで他人の予定を閲覧すると、タイトル、場所と時間の情報まで見えると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/15608c68-86e7-e657-743e-c5da7a3fbc96.png)

## 予定表のアクセス権とApps/Automateから取得する方法を整理すると
- Power AppsのOutlookコネクタでは、他人の予定が見えるのは空き時間のみのようです
- 他人のメールアドレスを指定して予定を見るには、GraphAPIを使う必要がありそうです
- 間違ってたらごめんなさい

| 予定表のアクセス権         | 他人から見える情報    | Outlookコネクタ         | Graph API        |
|-------------------|--------------|--------------------|------------------|
| 空き時間情報の表示が可能      | 空き時間の表示      | FindMeetingTimesV2 | findMeetingTimes |
| タイトルと場所の表示が可能(既定)     | 会議時間、場所、タイトル | 他人の予定は不可           | getSchedule      |
| 全ての詳細を表示可能(本文を含む) | 上記に加えて本文     | 他人の予定は不可           | events           |


# Graph APIとGetSucheduleエンドポイントについて
- 使い方は以下のドキュメントの通りです
- 予定表を見たい相手のカレンダーが自分に対して「タイトルと場所の表示が可能」で共有されていることが前提ですが、Outlookのように詳細な情報が取得できます

https://learn.microsoft.com/ja-jp/graph/api/calendar-getschedule?view=graph-rest-1.0&tabs=http

**要求の例**
- メールアドレスアドレスと、開始時間、終了時間、時間間隔を指定します
```json
POST https://graph.microsoft.com/v1.0/me/calendar/getSchedule
Content-Type: application/json

{
    "schedules": ["adelev@contoso.com", "meganb@contoso.com"],
    "startTime": {
        "dateTime": "2019-03-15T09:00:00",
        "timeZone": "Tokyo Standard Time"
    },
    "endTime": {
        "dateTime": "2019-03-15T18:00:00",
        "timeZone": "Tokyo Standard Time"
    },
    "availabilityViewInterval": 30
}
```

**応答の例**
- ユーザーのスケジュールID、スケジュールアイテムが配列で返されます
- 会議の応答ステータス、件名、場所、時間の情報が見えます

```json
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.scheduleInformation)",
    "value": [
        {
            "scheduleId": "adelev@contoso.com",
            "availabilityView": "000220000",
            "scheduleItems": [
                {
                    "isPrivate": false,
                    "status": "busy",
                    "subject": "Let's go for lunch",
                    "location": "Harry's Bar",
                    "start": {
                        "dateTime": "2019-03-15T12:00:00.0000000",
                        "timeZone": "Pacific Standard Time"
                    },
                    "end": {
                        "dateTime": "2019-03-15T14:00:00.0000000",
                        "timeZone": "Pacific Standard Time"
                    }
                }
            ],
            "一部省略": {
            }
        }
    ]
}
```

# Power AutomateとGraph APIで他人の予定を取得する
- 直接Power Appsから呼び出すと応答JSONの解析が面倒そうなので、一旦Power Automateで前処理します
- 最近登場したユーザー定義型変数に期待？

## フロー全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/55e78c50-3e28-16ef-914f-5c5a79143339.png)

## 解説
抜粋して解説します。

## トリガー
- Emails：メールアドレスをカンマ区切りで指定
- DateTimeは開始時間、終了時間をJSTで指定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/a1fed460-0dd2-bd1f-e576-45a9cf62308b.png)


## Graph APIの呼び出し
これで指定ユーザーの予定を取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/ce37b938-b537-e2b0-8f20-a4d93f3a5633.png)


## JSONをフラットに変換してアプリで扱いやすくする

**JSONの解析**
- GraphAPIの応答は複雑なので、JSONの解析で加工しやすくしておきます
- スキーマは一工夫が必要です
- 予定がPrivateだとsubjectなどがnullになるので許容するよう変更が必要です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/5d56932f-0841-75b0-00de-480bb3302b74.png)


```json:スキーマ
{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "scheduleId": {
                "type": "string"
            },
            "availabilityView": {
                "type": "string"
            },
            "scheduleItems": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "isPrivate": {
                            "type": [
                                "boolean",
                                "null"
                            ]
                        },
                        "status": {
                            "type": "string"
                        },
                        "subject": {
                            "type": [
                                "string",
                                "null"
                            ]
                        },
                        "location": {
                            "type": [
                                "string",
                                "null"
                            ]
                        },
                        "isMeeting": {
                            "type": [
                                "boolean",
                                "null"
                            ]
                        },
                        "isRecurring": {
                            "type": [
                                "boolean",
                                "null"
                            ]
                        },
                        "isException": {
                            "type": [
                                "boolean",
                                "null"
                            ]
                        },
                        "isReminderSet": {
                            "type": [
                                "boolean",
                                "null"
                            ]
                        },
                        "start": {
                            "type": "object",
                            "properties": {
                                "dateTime": {
                                    "type": "string"
                                },
                                "timeZone": {
                                    "type": "string"
                                }
                            }
                        },
                        "end": {
                            "type": "object",
                            "properties": {
                                "dateTime": {
                                    "type": "string"
                                },
                                "timeZone": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "required": [
                        "status",
                        "start",
                        "end"
                    ]
                }
            },
            "workingHours": {
                "type": "object",
                "properties": {
                    "daysOfWeek": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    },
                    "startTime": {
                        "type": "string"
                    },
                    "endTime": {
                        "type": "string"
                    },
                    "timeZone": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "required": [
            "scheduleId",
            "availabilityView",
            "scheduleItems",
            "workingHours"
        ]
    }
}
```

<br>

**Apply to Eachを入れ子にして、scheduleItemsを配列に格納していく**
- そのままではPower Appsで使いにくいので
  scheduleIdとscheduleItemsの要素がフラットになるように配列変数に追加していきます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/ba97d757-1164-5ba3-bab2-268c2175df4b.png)

数式で以下のようなフラットなJSON配列を作成します。

```cs:作成の数式
{
  "scheduleId": @{items('Apply_to_each：本文')['scheduleId']},
  "status": @{items('Apply_to_each：Items')['status']},
  "subject": @{coalesce(items('Apply_to_each：Items')?['subject'], '-')},
  "location": @{coalesce(items('Apply_to_each：Items')?['location'], '')},
  "start": @{items('Apply_to_each：Items')?['start']?['dateTime']},
  "end": @{items('Apply_to_each：Items')?['end']?['dateTime']}
}

```

<br>

**最後はアプリに応答します**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293667/0a912563-1332-2f53-522f-af803be29a6c.png)


# Power Appsから呼び出す
- 以下の数式でPower Automateを呼び出します
- colSchedulesにスケジュール一覧がセットされるので、任意の形式で表示します
- 以下の例で_userEmailに他人のEmailが入るようにします
- カンマ区切りで指定すると複数ユーザーの予定を取得できると思います

```fsharp
// 8時～22時の時間テキスト
UpdateContext(
    {
        _startDatetimeJST : Text(DatePicker1.SelectedDate,"yyyy-mm-dd") & "T08:00:00",
        _endDatetimeJST : Text( DatePicker1.SelectedDate,"yyyy-mm-dd") & "T22:00:00"
    }
);

// スケジュールを取得
ClearCollect(
    colSchedules,
    ForAll(
        // フローを実行してParseJson
        Table(ParseJSON(
            Outlook_スケジュールを取得.Run(_userEmail, _startDatetimeJST,_endDatetimeJST).value
        )),
        // 匿名型を型オブジェクトにマッピング
        {
            scheduleId: Text(ThisRecord.Value.scheduleId),
            subject: Text(ThisRecord.Value.subject),
            location: Text(ThisRecord.Value.location),
            status: Text(ThisRecord.Value.status),
            start: DateAdd( DateTimeValue(ThisRecord.Value.start), 9,TimeUnit.Hours) ,
            end: DateAdd( DateTimeValue(ThisRecord.Value.end), 9,TimeUnit.Hours) 
        }
    )
);


If(IsEmpty(colSchedules),Notify("本日のスケジュールは一件も見つかりませんでした。",NotificationType.Information,2000))

```