未使用変数を見逃すな！lightning-flow-scannerでフローのレビューを効率化

この記事は[Ateam LifeDesign Advent Calendar 2023](https://qiita.com/advent-calendar/2023/ateam-life-design)の7日目記事です。

# はじめに

Salesforceのフローをレビューするのって大変ですよね？
特に、未使用の変数など、フローが大規模になると、人が確認するのが大変になります。
そんな時に、作業を効率化するために、SalesforceDXのプラグインである[lightning-flow-scanner](https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-sfdx/)が非常に役立ちます。

# やること

- lightning-flow-scannerを使って、以下の内容をチェックする
    - APIバージョンのチェック
    - ループ要素の中にあるDML操作チェック
    - フローに説明が記載されているかチェック
    - 未使用の変数チェック

- GitHub Actionsでプルリクエストに解析結果をコメントする

# 対象者

- SalesforceDXで開発している人
- 開発されたフローのレビューをする人

# lightning-flow-scannerとは

>Lightning Flow Scanner is a free and open-source SFDX Plugin that can perform static analysis on Salesforce Flows, Process Builders and Workflows to identify violations of industry best practices.

https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-sfdx/

フローのxml内容を静的解析して一定のルールに基づいて
解析結果を返してくれます。

返してくれる内容をいくつかピックアップすると以下のものがあります。

- APIバージョンのチェック
- ループ要素の中にあるDML操作チェック
- フローに説明が記載されているかチェック
- 未使用の変数チェック

SalesforceDXのプラグインとして利用することが一番多いかと思いますが、
VSCodeの拡張機能としても公開されているので、まずはどんなものかを確認するにはおすすめです。

https://marketplace.visualstudio.com/items?itemName=ForceConfigControl.lightningflowscanner

# どんな感じで実行するのか？

:::note
チェックしているフローは、別の記事でテスト的に作ったものをそのまま利用しています。
[Winter '23 待望のFlow Builderの機能を試してみる ~ In/Not In 演算子編 ~](https://qiita.com/takaHAL/items/fa228f5de923f9d137eb)
:::


最終的にどういうことをするのかのざっくりのイメージ

以下の２ステップで実行できます。


- 1\. `.flow-scanner.json`を作成し、解析対象ルールを記載
- 2\. `sfdx flow:scan`コマンドを実行して解析

```bash
# 複数のファイルを指定してチェックする場合はカンマ区切り
## force-app/main/default/flows/hoge.flow-meta.xml, force-app/main/default/flows/fuga.flow-meta.xml
sfdx flow:scan -p force-app/main/default/flows/hoge.flow-meta.xml -c ./.flow-scanner.json --json
## ディレクトリ全体でチェックする場合
sfdx flow:scan -d force-app/main/default/flows/ -c ./.flow-scanner.json --json
```

```json: .flow-scanner.json
{
  "rules": {
    "DMLStatementInLoop": {
      "severity": "warning"
    },
    "FlowDescription": {
      "severity": "warning"
    },
    "UnusedVariable": {
      "severity": "warning"
    }
  }
}

```

```json: 結果
{
  "status": 0,
  "result": {
    "summary": {
      "flowsNumber": 1,
      "results": 1,
      "message": "A total of 1 results have been found in 1 flows.",
      "errorLevelsDetails": {}
    },
    "status": 0,
    "results": [
      {
        "violation": {
          "metaType": "attribute",
          "name": "undefined",
          "subtype": "description",
          "expression": "!==null"
        },
        "name": "undefined",
        "metaType": "attribute",
        "type": "description",
        "details": {
          "expression": "!==null"
        },
        "ruleDescription": "Descriptions play a vital role in documentation. We highly recommend including details about where they are used and their intended purpose.",
        "rule": "Missing Flow Description",
        "flowName": "取引先ソースがWebの責任者を取得",
        "flowType": "AutoLaunchedFlow",
        "severity": "warning"
      }
    ]
  }
}
```

# セットアップ

## Salesforce CLI

詳しくはこの記事では説明しませんので、公式のドキュメントをご参照ください。

https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_setup.meta/sfdx_setup/sfdx_setup_install_cli.htm

## lightning-flow-scannerのインストール

```bash
# sfdxを使用
sfdx plugins:install lightning-flow-scanner
# npmを使用
npm install -g lightning-flow-scanner
```

https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-sfdx/

# 使い方

## 1. .flow-scanner.json を書く

今回は以下の4つのルールを検知できるように設定ファイルを書きます

- APIバージョンのチェック
- ループ要素の中にあるDML操作チェック
- フローに説明が記載されているかチェック
- 未使用の変数

以下のリポジトリを見ながら書きます。

https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-sfdx

こちらのリポジトリにはルールの詳細が書かれているのでこちらも確認するのがおすすめです。

https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-core

できたファイルがこちら

```json: .flow-scanner.json
{
  "rules": {
    "APIVersion": {
      "severity": "warning",
      "expression": "===58"
    },  
    "DMLStatementInLoop": {
      "severity": "warning"
    },
    "FlowDescription": {
      "severity": "warning"
    },
    "UnusedVariable": {
      "severity": "warning"
    }
  }
}


```

### 設定ファイルの説明

`severity`は`error,warning,note`と重要度が設定できます。目的に応じて設定しましょう。
今回は、`warning`で設定しています。

`APIVersion`はフローのAPIバージョンが58でない場合に、`warning`を出すように設定しました。

```json:APIVersion
"APIVersion": {
      "severity": "warning",
      "expression": "===58"
    },  
```


`FlowDescription`でフローの説明欄未入力チェック、`DMLStatementInLoop`でループ内のDML操作チェック、`UnusedVariable`で未使用の変数のチェックをしています。

```json: FlowDescription,DMLStatementInLoop, UnusedVariable
    "FlowDescription": {
      "severity": "warning"
    },
    "DMLStatementInLoop": {
      "severity": "warning"
    },
    "UnusedVariable": {
      "severity": "warning"
    }
```

## 2. 実行

```bash
# 複数のファイルを指定してチェックする場合はカンマ区切り
## force-app/main/default/flows/hoge.flow-meta.xml, force-app/main/default/flows/fuga.flow-meta.xml
sfdx flow:scan -p force-app/main/default/flows/hoge.flow-meta.xml -c ./.flow-scanner.json --json
## ディレクトリ全体でチェックする場合
sfdx flow:scan -d force-app/main/default/flows/ -c ./.flow-scanner.json --json
```

オプションは以下になります。
`-p`で解析対象のファイル指定、`-d`でフローが含まれるディレクトリの指定、`-c`で設定ファイルのパス、`--json`で出力結果をJSON形式にしています。

>  -c, --config <path>                                               provide a path to the configuration file.
  -f, --failon                                                      provide a threshold level for returning status 1
  -p, --sourcepath <C:\..\flow1.flow, C:\..\flow2.flow>             provide a comma-separated list of flow paths to scan.
  -u, --targetusername <username>                                   retrieve the latest metadata from the target before the scan.
  -d, --directory <C:\..\force-app\main\default\flows>              provide a directory to scan.
  --json                                                            set output format as json.
  --loglevel=(trace|debug|info|warn|error|fatal)                    [default: warn] logging level.
  https://github.com/Lightning-Flow-Scanner/lightning-flow-scanner-sfdx#options

テストのために以下の内容のフローを用意しました。
- APIバージョン59
- ループ内でDML操作あり
- 説明欄に記入なし
- 未使用の変数あり

![スクリーンショット 2023-12-07 0.12.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445600/1e93fdf6-d0f1-4e97-a423-acb4667f0d95.png)


```json: 結果
{
  "status": 0,
  "result": {
    "summary": {
      "flowsNumber": 1,
      "results": 3,
      "message": "A total of 3 results have been found in 1 flows.",
      "errorLevelsDetails": {}
    },
    "status": 0,
    "results": [
      {
        "violation": {
          "metaType": "attribute",
          "name": "59",
          "subtype": "apiVersion",
          "expression": "===58"
        },
        "name": "59",
        "metaType": "attribute",
        "type": "apiVersion",
        "details": {
          "expression": "===58"
        },
        "ruleDescription": "Introducing newer API components may lead to unexpected issues with older versions of Flows, as they might not align with the underlying mechanics. Starting from API version 50.0, the 'Api Version' attribute has been readily available on the Flow Object. To ensure smooth operation and reduce discrepancies between API versions, it is strongly advised to regularly update and maintain them.",
        "rule": "Outdated API Version",
        "flowName": "Test",
        "flowType": "AutoLaunchedFlow",
        "severity": "warning"
      },
      {
        "violation": {
          "metaType": "attribute",
          "name": "undefined",
          "subtype": "description",
          "expression": "!==null"
        },
        "name": "undefined",
        "metaType": "attribute",
        "type": "description",
        "details": {
          "expression": "!==null"
        },
        "ruleDescription": "Descriptions play a vital role in documentation. We highly recommend including details about where they are used and their intended purpose.",
        "rule": "Missing Flow Description",
        "flowName": "Test",
        "flowType": "AutoLaunchedFlow",
        "severity": "warning"
      },
      {
        "violation": {
          "element": {
            "name": [
              "Test"
            ],
            "dataType": [
              "String"
            ],
            "isCollection": [
              "false"
            ],
            "isInput": [
              "false"
            ],
            "isOutput": [
              "false"
            ]
          },
          "subtype": "variables",
          "metaType": "variable",
          "name": "Test",
          "dataType": "String"
        },
        "name": "Test",
        "metaType": "variable",
        "type": "variables",
        "details": {
          "dataType": "String"
        },
        "ruleDescription": "To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use.",
        "rule": "Unused Variable",
        "flowName": "Test",
        "flowType": "AutoLaunchedFlow",
        "severity": "warning"
      }
    ]
  }
}
```

ちゃんと設定したものが検知できていますね。

例えば、未使用変数であれば"name": "Test"という変数が未使用ということがわかります。もちろん複数の未使用変数があれば、その数分指摘してくれるのでとっても助かります。


```json
{
        "name": "Test",
        "metaType": "variable",
        "type": "variables",
        "details": {
          "dataType": "String"
        },
        "ruleDescription": "To maintain the efficiency and manageability of your Flow, it's advisable to avoid including unconnected variables that are not in use.",
        "rule": "Unused Variable",
        "flowName": "Test",
        "flowType": "AutoLaunchedFlow",
        "severity": "warning"
      }
```

# Gitで変更のあったフローファイルのみを対象にして実行する

GitHubのプルリク単位で自動で静的解析を実行したい場合に、変更したファイルのみに対して実行したくなると思います。

以下のスクリプトで実行できます。

```bash
sfdx flow:scan -p $(git diff --name-only origin/main HEAD --relative ./force-app/main/default/flows/ | tr '\n' ',' | sed 's/,$//') -c ./.flow-scanner.json --json
```

- 1\. 現在のブランチとmainブランチの差分で`./force-app/main/default/flows/`配下のファイル名をカンマ区切りで出す
- 2\. 1で出力したファイル名に対して解析を実行

という流れになります。

# GitHub Actionsでプルリクエストにコメントを残す

![スクリーンショット 2023-12-07 0.16.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445600/07a4165c-3499-c89b-4ebb-bfc4d2ccb21d.png)


```yml
name: Flow Scan

on:
  pull_request:
    branches:
      - main
    paths:
      - "force-app/main/default/flows/**"

permissions:
  contents: read
  pull-requests: write

jobs:
  flow-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Make temporary directory
        run: |
          mkdir ./tmp
      - name: Install Salesforce CLI
        run: |
          wget https://developer.salesforce.com/media/salesforce-cli/sfdx/channels/stable/sfdx-linux-x64.tar.xz
          mkdir ./tmp/sfdx-cli
          tar xJf sfdx-linux-x64.tar.xz -C ./tmp/sfdx-cli --strip-components 1
          echo "./tmp/sfdx-cli/bin" >> $GITHUB_PATH
      - name: Install lightning-flow-scanner
        run: echo y | sfdx plugins:install lightning-flow-scanner
      - name: Make PR comment body
        id: make-pr-comment
        run: |
          pr_comment_body=''
          changeset_output="$(sfdx flow:scan -p $(git diff --name-only origin/main HEAD --relative ./force-app/main/default/flows/ | tr '\n' ',' | sed 's/,$//') -c ./.flow-scanner.json --json)" 
          pr_comment_body=$(cat << EOF
          ${pr_comment_body}
          <details><summary><code>lightning-flow-scanner.json</code></summary> 

          \`\`\`json
          ${changeset_output}
          \`\`\`
          </details>
          EOF
          )

          pr_comment_body_file=/tmp/pr_comment_body.txt
          cat << EOF  > ${pr_comment_body_file}
          <strong>flow自動解析結果</strong>
          [ルールについて](https://github.com/Force-Config-Control/lightning-flow-scanner-sfdx#rule-overview)

          ${pr_comment_body}
          EOF
          echo "result=${pr_comment_body_file}" >> $GITHUB_OUTPUT
      - name: "PR comment on Updating CFn Change Set"
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          header: flow-scan
          path: ${{ steps.make-pr-comment.outputs.result }}

```

GitHubにコメントを残すやり方については以下の記事を参考にさせていただきました。

https://qiita.com/yokawasa/items/f7fee20fd4a4220f25f8

# さいごに

どうでしたか？

みなさんもぜひ「lightning-flow-scanner」を使ってフローのレビューを効率化させてみましょう。Salesforceのフローは年4回のリリースでますます機能が強化され、レビューの難易度も上がっています。できる限り品質を保ちつつ、Salesforce利用者に新しい機能を提供するためにも、効率化の取り組みが必要です！

今回は応用としてGitHub Actionsを紹介しましたが、コミット時にチェックを実行し、コミットした人に事前に気づかせる方法もあります。次回はフローのXMLをChatGPTなどを使ってうまく解析できないか、チャレンジしてみます。