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

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

Power AppsからGraph APIを叩く


スポンサードリンク

はじめに

この記事は、Power Apps Advent Calendar 2022 12月21日担当分の記事です。

Power AppsでGraph APIを叩いてみたのでやり方まとめます。

参考文献

beaucameron.com

powerusers.microsoft.com

既にいくつかありますけど日本語verがないので日本語記事ということで。

この記事で触れないこと

  • Graph APIとは?
  • 投げているAPIの説明

Graph APIに関してはこちらのドキュメントご覧ください。

Microsoft Graph ドキュメント | Microsoft Learn

アプリの作成

まずは普通に投げてみる

Office 365 GroupsコネクタにはHTTP要求を送信できるHttpRequestがありますので、これを利用してGraph APIを投げます。

learn.microsoft.com

Microsoft Graph REST API リクエストを作成し呼び出します。 詳細情報: https://docs.microsoft.com/en-us/graph/use-the-api

Power AutomateでGraph APIを投げている人には親しみのあるコネクタですね。

さて、このアクションを用いてPower AppsからGraph APIを投げてみましょう。

例えば特定のSPOサイトのリスト一覧を取得するAPIを投げてみましょう。
以下のような式になりますね。

Office365グループ.HttpRequest(
    $"https://graph.microsoft.com/v1.0/sites/{siteId}/lists", 
    "GET", 
    "", 
    {ContentType:"application/json"}
)

この式をボタンなどに設定して、ボタンを押して実行してみましょう。

すると上記のようにブール値が返ってくることがわかります。

本来であればサイト一覧がJSONとかで返ってきて欲しいところですが、どうやらResponseがいけてないようですね。

コネクタの定義ファイルをみてみる

Responseがどのような設定になっているのか?定義ファイルの中身を覗いてみましょう!

定義ファイルの中身を覗くにはmsappファイルをコード変換してあげればいいので、まずはmsappファイルをローカルにダウンロードしましょう。

ローカルにmsappファイルをダウンロードするには、右上の保存アイコンの右にある「∨」から「コピーのダウンロード」を選択して「ダウンロード」を選択します。

すると、ローカルに「[アプリ名].msapp」ファイルがダウンロードされますので、任意の場所に移動します。

続いて、VSCodeを起動して以下の拡張機能をインストールします。

Power Platform Tools - Visual Studio Marketplace

この拡張機能をインストールすることでVSCodeのターミナルからアプリのパッケージアンパッケージができるようになります。

使い方は詳細に記載していますので見ておきましょう。

以下から新しいターミナルを開くか、「Ctrl + Shift + @」を押して新しいターミナルを起動します。

続いて先ほどmsappファイルを移動させたフォルダに移動します。

cd 'G:\DEMO\Power Apps'

続いてmsappファイルをunpackするわけですが、事前にunpack先のフォルダを作成しておく必要があるので適当なフォルダを作成しておきましょう。

msappファイルをunpackするコマンドを実行します。

pac canvas unpack --sources "AppSource" --msapp "GraphAPI.msapp"

ファイル名とフォルダ名は自身の環境に合わせて変更してください。

このコマンドを実行したあとに指定したフォルダの中身をみにいくとアプリが以下のようにコード化されていることが確認できます。

このファイルを見ていきましょう。

ファイル > フォルダーを開くから先ほどのフォルダーを選択して開くか、「Ctrl + K Ctrl + O」を押してフォルダーを選択しましょう。

ファイルを開いて中身をみてみると、アプリの定義がコードで書かれていることが確認できます。

コネクタの定義ファイルを編集する

コネクタの定義ファイルは以下にあります。

コネクタの定義はxmlでされているんですね。

HttpRequestの定義は592行目で行われています。

<resource path="/{connectionId}/httprequest">

これの中身を見てみると、Responseが定義されていませんね。

これが原因で、呼び出しに成功したか?失敗したか?のtrue or falseしか返ってこないわけです。

ということで、この定義ファイル上にResponseを自身で定義してあげることにします。

まずは、どのようなレスポンスが返ってくるのか?を定義します。
他アクションの定義ファイルを参考に記載してあげましょう。

記載するのは4行目以下の箇所です。

  <grammars>
    <jsonTypes targetNamespace="https://japan-001.azure-apim.net/apim/office365groups" xmlns="http://schemas.microsoft.com/MicrosoftProjectSiena/WADL/2014/11">

[ここに定義を追加する]

    </jsonTypes>
  </grammars>

リスト一覧を取得するGraph APIですが、以下のような結果が返ってきます。

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites('[domain].sharepoint.com%2C178 ... 5529249')/lists",
    "value": [
        {
            "@odata.etag": "\"592a ... dfb,48\"",
            "createdDateTime": "2022-05-20T15:24:20Z",
            "description": "",
            "eTag": "\"592a ... cbedfb,48\"",
            "id": "592a ... dfb",
            "lastModifiedDateTime": "2022-06-13T10:05:18Z",
            "name": "0521_Verify",
            "webUrl": "https://[domain].sharepoint.com/sites/development/Lists/0521_Verify",
            "displayName": "0521_Verify",
            "createdBy": {
                "user": {
                    "email": "[mail]@[domain]",
                    "id": "441 ... 30b",
                    "displayName": "korune コルネ"
                }
            },
            "parentReference": {
                "siteId": "[domain].sharepoint.com,178b3dbd ... 9249"
            },
            "list": {
                "contentTypesEnabled": false,
                "hidden": false,
                "template": "genericList"
            }
        },
        {
            "@odata.etag": "\"62806 ... 707,12\"",
            "createdDateTime": "2022-01-22T12:50:06Z",
            "description": "テスト",
            "eTag": "\"6280 ... 7707,12\"",
            "id": "62806 ... 2c7707",
            "lastModifiedDateTime": "2022-06-08T09:17:14Z",
            "name": "EmptyUpdate",
            "webUrl": "https://[domain].sharepoint.com/sites/development/Lists/EmptyUpdate",
            "displayName": "EmptyUpdate",
            "createdBy": {
                "user": {
                    "email": "[mail]@[domain]",
                    "id": "441b ... 730b",
                    "displayName": "korune コルネ"
                }
            },
            "lastModifiedBy": {
                "user": {
                    "email": "[domain]@[domain].onmicrosoft.com",
                    "id": "441b ... 730b",
                    "displayName": "korune コルネ"
                }
            },
            "parentReference": {
                "siteId": "[domain].sharepoint.com,178b3db ... 49"
            },
            "list": {
                "contentTypesEnabled": false,
                "hidden": false,
                "template": "genericList"
            }
        }
    ]
}

これをもとにxml定義を作成していきます。

まずは"value"の中身の定義を行います。
すべてを定義する必要はなく、Power Appsで取り扱いたいプロパティのみで大丈夫です。

例えば以下のようになりますね。

      <object name="HttpRequest_Response_value_def_items_def">
        <property name="id" type="string" />
        <property name="displayName" type="string" />
        <property name="webUrl" type="string" />
        <property name="name" type="string" />
        <property name="description" type="string" />
        <property name="createdDateTime" type="string" />
        <property name="lastModifiedDateTime" type="string" />
      </object>

"createdBy"はオブジェクト型で定義されているので、別途定義してあげる必要があります。
まずは一番深い階層の"user"の定義を行います。

      <object name="user_def">
        <property name="email" type="string" />
        <property name="id" type="string" />
        <property name="displayName" type="string" />
      </object>

"createdBy"は上記のオブジェクトが中にあるので、これを参照した定義とします。

      <object name="createdBy_def">
        <property name="user" typeRef="user_def" />
      </object>

同様に"parentReference_def"と"list_def"も定義してあげましょう。

      <object name="parentReference_def">
        <property name="siteId" type="string" />
      </object>

      <object name="list_def">
        <property name="contentTypesEnabled" type="string" />
        <property name="hidden" type="string" />
        <property name="template" type="string" />
      </object>

"createdBy"と"parentReference_def"と"list_def"の定義ができましたので、"value"の定義の中に追記してあげましょう。

        <property name="createdBy" typeRef="createdBy_def" />
        <property name="parentReference" typeRef="parentReference_def" />
        <property name="list" typeRef="list_def" />

最後に"value"自体の定義をしてあげます。
"value"はarray型なので以下のようになります。

      <array typeRef="HttpRequest_Response_value_def_items_def" name="HttpRequest_Response_value_def" />

      <object name="HttpRequest_Response">
        <property name="value" typeRef="HttpRequest_Response_value_def" />
      </object>

今回の例だと完成形は以下のようになります。

      <object name="HttpRequest_Response_value_def_items_def">
        <property name="id" type="string" />
        <property name="displayName" type="string" />
        <property name="webUrl" type="string" />
        <property name="name" type="string" />
        <property name="description" type="string" />
        <property name="createdDateTime" type="string" />
        <property name="lastModifiedDateTime" type="string" />
        <property name="createdBy" typeRef="createdBy_def" />
        <property name="parentReference" typeRef="parentReference_def" />
        <property name="list" typeRef="list_def" />
      </object>
      
      <object name="user_def">
        <property name="email" type="string" />
        <property name="id" type="string" />
        <property name="displayName" type="string" />
      </object>

      <object name="createdBy_def">
        <property name="user" typeRef="user_def" />
      </object>

      <object name="parentReference_def">
        <property name="siteId" type="string" />
      </object>

      <object name="list_def">
        <property name="contentTypesEnabled" type="string" />
        <property name="hidden" type="string" />
        <property name="template" type="string" />
      </object>

      <array typeRef="HttpRequest_Response_value_def_items_def" name="HttpRequest_Response_value_def" />

      <object name="HttpRequest_Response">
        <property name="value" typeRef="HttpRequest_Response_value_def" />
      </object>

これでResponseでどのような値が返ってくる想定か?の定義ができたので、Responseの定義をしてあげましょう。

    <resource path="/{connectionId}/httprequest">
      <param style="template" name="connectionId" type="xs:string" required="true" />
      <method siena:requiresAuthentication="true" name="POST" id="HttpRequest" actionName="HttpRequest" visibility="important" siena:isDeprecated="false" siena:family="HttpRequest" siena:revision="1" siena:status="Production">
        <doc title="HTTP 要求を送信します&#xA;呼び出す Microsoft Graph REST API 要求を構築します。詳細情報: https://docs.microsoft.com/en-us/graph/use-the-api" />
        <request>
        </request>
        
        [ここに記載]

      </method>
    </resource>

Responseとなるオブジェクトは"HttpRequest_Response"となりますので、以下のような記載となります。

        <response>
          <representation mediaType="application/json" element="service:HttpRequest_Response" />
        </response>

ファイルをパッケージ化してアプリに戻す

さて、定義ファイルの修正が完了したのでコードファイルをアプリに戻してあげましょう。

再度VSCodeでターミナルを開いてあげます。

ファイルを開いた状態でターミナルを開くと、そのフォルダ配下のディレクトリにいると思いますので、以下コマンドで1つ上の階層(フォルダのある階層)に移動します。

cd ..

続いてコードのあるフォルダをアプリに戻すコマンドを実行します。

pac canvas pack --sources "source" --msapp "Graph API_demo.msapp"

このとき先ほど作成したファイル名と被らないようにしましょう。

フォルダを確認するとmsappファイルが生成されています。

生成されたmsappファイルを読み込む

Power Appsの画面に戻り、作成 > その他のデータソース を選択します。

開く > 参照から先ほど生成したmsappファイルを選択してPower Appsで読み込みます。

もう一度Graph APIを実行してみる

特にPower Apps内でなにも変更することなく、もう一度HttpRequestを投げてみます。

ブール値ではなく、レコードが取れていますね。

コレクションに入れて中身を見てみましょう。

ClearCollect(
    myLists,
    Office365グループ.HttpRequest(
        $"https://graph.microsoft.com/v1.0/sites/{siteId}/lists", 
        "GET", 
        "", 
        {ContentType:"application/json"}
    )
)

コレクションの中身みてみると、それっぽい値取れていますね!

Power Appsからでも直接Graph APIが実行できました!

これJSONで返せば色々なAPIに対応できるのでは?

ここまで思われた方は、Responseの定義を一々かかずに"value"の内容をJSONで返してあげれば様々な形式のAPIに対応できるのでは?
と思われた方もいると思います。

私もそう思って試しました。

      <object name="HttpRequest_Response">
        <property name="value" type="string" />
      </object>

"value"をテキストで返すようにしてみました。

ただ、この結果は何も得られません。

型違いますからね。
そりゃそうだ。

複数種類のGraph APIを実行したい場合

複数種類のGraph APIを実行したい場合は、定義にて各APIで欲しい値をすべて定義してあげます。

ここの定義で存在しないプロパティを指定してアクションが実行されても、また、そのプロパティが存在しなくともエラーとはなりません。

よって、実行するAPIによっては邪魔なフィールドができてはしまいますが、複数のAPIを実行したい場合は、すべての定義をしてあげる必要がありそうです。

一応プロパティ名が同じだが型が違う。みたいなのは多分ないはずなので、これで対応はできそうです。

おわりに

最後に記載したような課題があるから、Power AppsでのHttpRequestはアクションは用意されてはいるけど、まだ利用できる形式では提供されてないのかなー?と開発者の気持ちがちょっとわかった気がしました。

こんなことしなくともいつか普通にPower Appsからでも直接Graph API実行できるようになるといいですね。

今の正攻法は、Power AppsからPower Automateを呼び出してPower AutomateからGraph APIを実行。
その結果をJSON値のままPower Appsに返して、ParseJSONJSONを解析してあげてApps内で利用することですね。

ParseJSONの使い方はここでまとめていますのでよかったら、参考にしてください。

koruneko.hatenablog.com

今回紹介したOffice 365 GroupsのHttpRequestですが、V2の公開が予定されています。
しかし、このコネクタでは/groupsに利用が制限されてしまいます。

このような更新はしないで欲しいというideasが以下で投稿されていますので、同感されるかたは以下でVoteをお願いします。
このVoteの数によって今後の更新が変わる可能性があります。

ideas.powerautomate.com


スポンサードリンク