Power Automate の承認画面を別のWebサイトに埋め込む

Power Automate の承認画面 ってカスタマイズできないの？って話がわりとでるので調べてやってみました。
以下のドキュメントの通り、別のWebサイトに埋め込むことはできそうだけど、うまく動かないような部分もあるし、あまり過度な期待しないほうがいいかなという感じです。
何かの業務の一連の流れでどうしても埋め込みたいという場合以外には向かないかもしれません。

> Power Automate と Web サイトおよびアプリを統合する
> https://docs.microsoft.com/ja-jp/power-automate/developer/embed-flow-dev

# 出来上がりイメージ
承認を起動するカスタムリストの横に置いてみたり
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312282/ac2a13a4-b9c8-9a0a-9dfe-c19fd8a8ec94.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312282/437230d1-3523-0d14-b8f6-2f06ffd5c863.png)

プロパティに環境IDを設定して、別の環境の承認を表示したり
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312282/facaa3ad-1ffa-f97d-367d-64416dcb919a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312282/4aabc82a-f6c0-bdbf-611c-a82302de83a6.png)

フィルタしてみたり、と言いたいところですが、さもフィルタができそうなものがありますが、うまく動作しませんでした。フィルタの書き方がわかりません。

# 手順
Azure AD にアプリ登録をするのが面倒なので、SharePoint Framework を利用して実現しています。

1. SharePoint Framework での開発準備(省略)
2. 外部ライブラリ(msflowsdk-1.1.js)の設定(config.json)
3. Flow Service の設定(package-solution.json)
4. SharePoint Online への展開とAPIアクセス許可
5. 承認画面を埋め込む機能の開発

# 1. SharePoint Framework での開発準備(省略)
以下の記事などを参考にWebパーツが、gulp serve で起動できるところまで進んでください。

> https://qiita.com/nanoka/items/1acb335796965747a7f8
> https://qiita.com/nanoka/items/834ee5216a5437dc04bc

# 2. 外部ライブラリ(msflowsdk-1.1.js)の設定(config.json)
次に、承認画面を埋め込む処理を開発するための JavaScriptライブラリを読み込む設定を行います。

以下を参考に config.json を書き換えます。

> 外部ライブラリを SharePoint のクライアント側 Web パーツに追加する
> https://docs.microsoft.com/ja-jp/sharepoint/dev/spfx/web-parts/basics/add-an-external-library

``` JSON:config/config.json　の　externals
  "externals": {
    "MsFlowSdk": {
      "path": "https://flow.microsoft.com/Content/msflowsdk-1.1.js",
      "globalName": "MsFlowSdkJs"
    }
``` 
これで起動時に、外部のJavaScriptライブラリを読み込むことができるようになりました。

# 3. Flow Service の設定(package-solution.json)
それから、以下のように、package-solution.json を編集します。
solution の子要素として webApiPermissionRequests を追記します。

``` JSON:config/package-solution.json　の　webApiPermissionRequests
    "webApiPermissionRequests": [
      {
        "resource": "Microsoft Flow Service",
        "scope": "User"
      },
      {
        "resource": "Microsoft Flow Service",
        "scope": "Approvals.Manage.All"
      },
      {
        "resource": "Microsoft Flow Service",
        "scope": "Flows.Manage.All"
      },
      {
        "resource": "Microsoft Flow Service",
        "scope": "Flows.Read.All"
      }
    ]
``` 

# 4. SharePoint Online への展開とAPIアクセス許可
ここで一度、SharePoint Online にアプリを展開します。
> SharePoint Developer SharePoint Framework Web パーツ開発 その３：デプロイ 及川さんのブログ
> https://sharepoint.orivers.jp/article/10302

そうすると、以下のようにSharePoint管理センターで先ほど編集した webApiPermissionRequests の値が表示されます。
これを承認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312282/824b5a35-bd5b-beed-bca1-051fc5b98104.png)
※上記画像は承認済みです。
これで、SharePoint Framework から、Flow SDK を使用する準備ができました。

# 5. 承認画面を埋め込む機能の実装
準備が整ったので、機能の実装をしていきます。

## Webパーツのプロパティの設定
今回はWebパーツのプロパティとして、Power Automate の環境を指定するための envId と、承認データのフィルタリングができる filterParam を設定します。
以下6ファイルを、envId、filterParam の周辺に注意して更新してください。

``` typescript:src/webparts/spfxFlowSdkAppCenter/SpfxFlowSdkAppCenterWebPart.ts
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
  IPropertyPaneConfiguration,
  PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';

import * as strings from 'SpfxFlowSdkAppCenterWebPartStrings';
import SpfxFlowSdkAppCenter from './components/SpfxFlowSdkAppCenter';
import { ISpfxFlowSdkAppCenterProps } from './components/ISpfxFlowSdkAppCenterProps';

export interface ISpfxFlowSdkAppCenterWebPartProps {
  envId: string;
  filterParam: string;
}

export default class SpfxFlowSdkAppCenterWebPart extends BaseClientSideWebPart<ISpfxFlowSdkAppCenterWebPartProps> {

  public render(): void {
    const element: React.ReactElement<ISpfxFlowSdkAppCenterProps> = React.createElement(
      SpfxFlowSdkAppCenter,
      {
        envId: this.properties.envId,
        filterParam: this.properties.filterParam,
        webPartContext: this.context
      }
    );

    ReactDom.render(element, this.domElement);
  }

  protected onDispose(): void {
    ReactDom.unmountComponentAtNode(this.domElement);
  }

  protected get dataVersion(): Version {
    return Version.parse('1.0');
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('envId', {
                  label: strings.EnvIdFieldLabel
                }),
                PropertyPaneTextField('filterParam', {
                  label: strings.FilterParamFieldLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }
}

``` 

``` JSON:src/webparts/spfxFlowSdkAppCenter/SpfxFlowSdkAppCenterWebPart.manifest.json　の24、25行目を編集
  "preconfiguredEntries": [{
    "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
    "group": { "default": "Other" },
    "title": { "default": "SpfxFlowSdkAppCenter" },
    "description": { "default": "SpfxFlowSdkAppCenter description" },
    "officeFabricIconFontName": "Page",
    "properties": {
      "envId": "",
      "filterParam": ""
    }
  }]
``` 

``` typescript:src/webparts/spfxFlowSdkAppCenter/loc/mystrings.d.ts
declare interface ISpfxFlowSdkAppCenterWebPartStrings {
  PropertyPaneDescription: string;
  BasicGroupName: string;
  EnvIdFieldLabel: string;
  FilterParamFieldLabel: string;
}

declare module 'SpfxFlowSdkAppCenterWebPartStrings' {
  const strings: ISpfxFlowSdkAppCenterWebPartStrings;
  export = strings;
}

``` 

``` typescript:src/webparts/spfxFlowSdkAppCenter/loc/en-us.js
define([], function() {
  return {
    "PropertyPaneDescription": "EnvId",
    "BasicGroupName": "Group Name",
    "EnvIdFieldLabel": "EnvId Field",
    "FilterParamFieldLabel": "FilterParam Field"    
  }
});
``` 

``` typescript:src/webparts/spfxFlowSdkAppCenter/components/ISpfxFlowSdkAppCenterProps.ts
import { WebPartContext } from "@microsoft/sp-webpart-base";

export interface ISpfxFlowSdkAppCenterProps {
  envId: string;
  filterParam: string;
  webPartContext: WebPartContext;
}
``` 

``` typescript:src/webparts/spfxFlowSdkAppCenter/components/SpfxFlowSdkAppCenter.tsx
import * as React from 'react';
import { ISpfxFlowSdkAppCenterProps } from './ISpfxFlowSdkAppCenterProps';

import SpApp from './SpApp';

export default class SpfxFlowSdkAppCenter extends React.Component<ISpfxFlowSdkAppCenterProps, {}> {
  public render(): React.ReactElement<ISpfxFlowSdkAppCenterProps> {
    return (
      <>
        <SpApp {...this.props}/>
      </>
    );
  }
}
``` 

## 外部ライブラリの型設定
外部ライブラリは普通のJavaScriptライブラリなので、TypeScriptには対応していません。
そのため、暫定的に利用するための設定ファイルを配置します。

``` typescript:src/assets/js/vender/@types/msflowsdk-1.1.d.ts
interface Window {
  MsFlowSdk: any;
  WellKnownHostIds: any;
}
```

## 承認画面の呼び出し処理
次に承認画面の呼び出し処理を開発していきます。
iframe のサイズの調整用の scss と、承認画面を埋め込むためのSDKの初期処理を行います。

``` scss:src/webparts/spfxFlowSdkAppCenter/components/SpfxFlowSdkAppCenter.module.scss

@import '~office-ui-fabric-react/dist/sass/References.scss';

.spfxFlowSdkAppCenter {
  .container {
    width:100%;
    margin: 0px auto;

    iframe {
      width:100%;
      min-height: 500px;
      overflow: hidden;
      border-width: 0.5px;
      border-style: solid;
      border-color: grey;
    }
  }
}
``` 

``` typescript:src/webparts/spfxFlowSdkAppCenter/components/SpApp.tsx
import * as React from 'react';
import { useEffect } from 'react';
import styles from './SpfxFlowSdkAppCenter.module.scss';

import 'MsFlowSdk';
import { AadTokenProvider } from '@microsoft/sp-http';

import { ISpfxFlowSdkAppCenterProps } from './ISpfxFlowSdkAppCenterProps';

const SpApp = (props: ISpfxFlowSdkAppCenterProps) => {

  const initSdkWidget = async () => {

    //管理できない要素の削除
    const parent = document.getElementById('myFlowDiv');
    while (parent.firstChild) {
      parent.removeChild(parent.firstChild);
    }

    const sdk: any = await new window.MsFlowSdk({
      hostName: 'https://japan.flow.microsoft.com',
      locale: 'ja-jp',
      hostId: window.WellKnownHostIds.SHAREPOINT
    });

    //承認画面のオプション設定
    const widget: any = await sdk.renderWidget('approvalCenter', {
      container: 'myFlowDiv',
      enableOnBehalfOfTokens: true,
      environmentId: props.envId,
      debugMode: false,
      approvalCenterSettings: {
        approvalsFilter: props.filterParam,
        autoNavigateToDetails: true,
        hideFlowCreation: true,
        hideInfoPaneCloseButton: true,
        hideLink: false,
        showSimpleEmptyPage: true
      }
    });
    //Tokenの取得
    const tokenProvider: AadTokenProvider = await props.webPartContext.aadTokenProviderFactory.getTokenProvider();
    const myToken: string = await tokenProvider.getToken('https://service.flow.microsoft.com/');

    //Tokenの設定
    widget.listen("GET_ACCESS_TOKEN", (requestParam, widgetDoneCallback) => {
      widgetDoneCallback(null, { token: myToken });
    });

    //イベント発生時の処理
    widget.listen("WIDGET_READY", () => {
      console.log("The flow widget is now ready.");
    });
    widget.listen("WIDGET_RENDERED", () => {
      console.log("The flow widget is now rendered.");
    });
    widget.listen("RECEIVED_APPROVAL_STATUS_CHANGED", () => {
      console.log("received event.");
    });
    widget.listen("SENT_APPROVAL_STATUS_CHANGED", () => {
      console.log("sent event.");
    });

  };

  useEffect(() => {
    initSdkWidget();
  });

  return (
    <div className={styles.spfxFlowSdkAppCenter}>
      <div className={styles.container}>
        <div id="myFlowDiv" />
      </div>
    </div>
  );
};

export default SpApp;
``` 

以上で終了です。
再度 SharePoint Online 展開してご利用ください。


# 感想
上記手順で埋め込むことはできるのはできるんですが、UIはいじれないし、いまいち更新があんまりされていないように見えます。
あと、これの検証をしているとTeamsの通知が非常にうるさかったです。
もしかしたら、Teams の承認の機能周辺のほうが充実していくのかもしれません。

# おまけ
> 動作させたもの
> https://github.com/7o83/SPFxFlowSdkAppCenter