俺が考えた最強のSOQL実行ツールを作りました

# LWC SOQL Builder

百聞は一見。こんなツールを作りました↓

![](https://lwc-soql-builder.github.io/images/lwc-soql-builder-animation.gif)

名前は「LWC SOQL Builder」です。Lightning Web Componentsのオープンソース版で作ったSOQL構築ツールなので、LWC SOQL Builder…
最初（仮）でつけてた名前なんですが他の名前が思いつかず、そのままリリースまで変わらずに来てしまいました。

社内ハッカソンで作っていたんですが、作っていたら楽しくなってしまって、自分が欲しい機能を詰めに詰め込んでしまいました。

## https://lwc-soql-builder.github.io/

ツールはこちら↑で公開しています。ソースコードもMITで公開しています。

せっかくだから海外の人にも使ってほしいなと（適当な）英語で作ったので、サイトにはあまり説明を書けませんでした（英語がおかしいところがあったら教えてもらえると非常に助かります）。
なので、この記事で思う存分日本語解説を書こうかと思います。

# 特徴

こんな特徴を持っています。

* オブジェクト/項目一覧をクリックするだけでSOQLが作れる！
* サブクエリ、親リレーションもクリックで挿入できる！
* 項目の入力補完にも対応！
* オブジェクト/項目はAPI名だけでなくラベルでも検索可能！
* CSVエクスポートできる！
* 削除済みレコードも検索できる！
* 管理パッケージ開発組織ではネームスペースを省略できる！
* PWAサポート！デスクトップアプリのように動く！
* SOQLを整形できる！Apexにきれいに貼れる。
* LWC OSSで作っているのでLWCのサンプルアプリとしても使える。

もう何が便利って、サブクエリや親リレーションの挿入はクソ便利ですよ。
あとラベル検索。もうこれだけでも俺はこのツールを作ってよかったと思う。
ISVベンダーしかわからないと思うけど、ネームスペース省略もめちゃ便利！

日本語ならいくらでも書けるので一つずつ説明していきましょう。

## クリックだけでSOQL構築

![jp-pointclick.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27267/1c94f8f5-75d5-aa2d-339a-397359d85750.gif)

シングルバイトの世界の人にはわからんのですが、ラベルで検索できるのがほんと便利なんですよ。
ダブルバイトの住民がSalesforceで適当にカスタム項目を作ると、 `Field1__c` とかなるんですよ。何の項目かわからんのですよ。

そしてサブクエリ！悪名高き子リレーション名ですよ。日本語でカスタムオブジェクトに参照項目作ると `X1` ですからね。子リレーション名！`Field1` よりひどい。しかも子リレーション名は意味がわかりにくいから、適当に付ける人も多いんですよ。しかも、設定画面からも項目一覧じゃ見れないから探すのが面倒。ともかくダブルバイト民にはラベル検索が必要なんですよ。

まあ普通に手でサブクエリ書くの大変なんでラベル検索を抜きにしてもすごく便利。

あと祖先オブジェクトの項目の追加も便利。SOQLでは５階層まで祖先を辿れるんですけど、その項目名を調べるのってなかなかに面倒です。それが、ポチポチポチで作れるんだから便利ですよ。

## 項目の入力補完

![jp-autocomplete.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27267/2906bf80-373e-f00c-8656-b83f37b1cc8d.gif)

入力補完もラベルで検索可です。`Field1__c`はほんと大変だけど、標準項目だって覚えられないんですよ。納入先住所がShippingAddressとかフェーズがStageNameとか覚えられんですよ。API名だけで入力補完できたとしても頭文字すら覚えてないんですよ。人類にはラベル検索が必須なんですよ。

入力補完は今はFROMのオブジェクトの項目しか補完できないので、機能追加したいなーと思っています。でも、それだけでもWHERE句の追加とか便利ですよ。

## CSVエクスポート

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27267/2962528b-7cce-242b-49f7-e8658407a314.png)

Salesforceは[データローダ](https://help.salesforce.com/articleView?id=loader_install_mac.htm&type=5)ってツールでCSVエクスポートができるんですが、このツールがまたいまいちでして、 Zulu OpenJDK バージョン 11 じゃないと動かないとか、CSVにBOMがついてないのでExcelで開けないとか、SOQL組み立てるUIが使いにくいとか…

LWC SOQL Builderでは、BOMをつけたのでExcelで開けるし、SOQL組み立てるのは上の機能でとっても簡単に組み立てられます。便利。

## PWAサポート

![](https://lwc-soql-builder.github.io/images/pwa.gif)

create-lwc-appでPWAサポートが組み込まれているおかげですが、LWC SOQL BuilderはPWAをサポートしているので、デスクトップアプリのようにインストールして使えます。
Spotlightからも起動できるし、Dockに追加することもできます。よく使うなら独立して動くと便利だし、たまに使うならインストールしておくと忘れなくて便利です。どっちでも便利！

## SOQLのフォーマット

![](https://lwc-soql-builder.github.io/images/format-soql.gif)

そもそもこのツールでSOQL作ったらフォーマットされたのができるんですが、手入力で作成したときにもこのボタンを押せばきれいに整形してくれます。
これは以下のようにSOQLをApexに貼るときに便利！サブクエリとか整形面倒だからね。

```java
Account[] acc = [
	SELECT Id, Name, Type, Phone, Rating, Website,
		(
			SELECT Id, LastName, FirstName, Phone, Fax, Email
			FROM Contacts
		)
	FROM Account
	WHERE Rating = 'Hot'
];
```

この機能は[paustint/soql-parser-js](https://github.com/paustint/soql-parser-js)におんぶに抱っこです。調べてみると便利なライブラリがあるものだ。フォーマット以外もこのライブラリがなかったらこのツールはできなかったと言っても過言ではない。感謝です。

まあこの機能は自分はそんなに使うかはわからないけど…

## デフォルトネームスペースの省略

この機能はAppExchangeアプリベンダーしかうれしくないと思うんですが、俺はすごく欲しいのです。
何ができるかと言うと、通常管理パッケージ開発組織ではカスタムオブジェクトに対するクエリは以下のように書かないといけません。

```
SELECT Id, matchingmap__sObjectName__c, matchingmap__LabelFieldName__c
FROM matchingmap__MM_ObjectDef__c
```

この`matchingmap`がネームスペースで、これをつけないとエラーになります。VSCodeのSOQL実行もネームスペース必須です。でも、Apexだと省略して書けるので、Apexに貼るときは通常ネームスペースを削除して書きます。逆にApexに書いているSOQLをVSCodeに持ってくるとネームスペースをつけないと実行できないのですよ。困る。

しかし！LWC SOQL Builderではこれがこう書けるし、クリックで作っていくと自動でネームスペースが省略されます。Apexにそのまま貼れるし、ApexのSOQLをそのまま実行できます。便利！

```
SELECT Id, sObjectName__c, LabelFieldName__c
FROM MM_ObjectDef__c
```

[Call Options ヘッダー](https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/headers_calloptions.htm)ってのを使って実現しています。ただdescribeはネームスペース付きで返ってくるので、省略処理がなかなか面倒だったんですが、自分のためにがんばりました。たぶんあんまり伝わらないけど、すごく便利！

## LWC OSSで作っているので、LWCのサンプルアプリとして参考になる

コード見てもらえればわかるけど、[Lightning Web Componentsのオープンソース版](https://lwc.dev/)で作っています。ターゲットユーザがSalesforceユーザなので、よりSalesforceユーザのためになるかなとLWC OSSで作ってみました。

Salesforce版LWCとOSS版LWCはだいたい同じように書けるので、たぶんほとんどそのまま移植できると思います。要修正箇所は下の２点くらいじゃないかと思う。

* Toast表示が`createElement`使っているのでそこを要修正
* Salesforce版LWCはREST APIを呼べないのでRemoteActionへの書き換えが必要

移植するメリットはありませんが、LWCのサンプルアプリとして実装の参考にはなるんじゃないかと思います。

Salesforceの中の人が作っているLWCサンプルアプリの[pmdartus/rcast](https://github.com/pmdartus/rcast)をだいぶ参考にさせてもらっています。redux連携とかはまんま使わせてもらいました。助かりました。

# 仕組み

## システム構成

まずできるだけお金をかけないことを方針としました。なので最初はGithub Pagesにホスティングして、SalesforceにCORS設定してもらう前提で、ブラウザから直接REST APIを叩くという構成で作っていました。しかし、さすがにそれだと使うハードルが高いなあと途中からCloud Functionsでプロキシを動かすようにしました。Cloud Functionsは無料枠が大きいので大丈夫でしょう。

最終的にはいわゆるサーバーレスSPA構成です。サーバーもDBも存在しなくて、フロントエンドはGithub Pagesで動いており、サーバーサイドはCORSのためにSalesforceへのAPIリクエストをプロキシするCloud Functionsがいるだけです。
クエリ履歴やトークンなどはローカルストレージに保存しています。

![lwc-soql-builder-arch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27267/695c9220-987e-7166-57c7-db555d785464.png)

## 利用技術

### ランディングページ

LPはGithub PagesのOrganizationサイト、ツールはProjectサイトで公開してただリンクしています。

LPは[Tivo](https://onepagelove.com/tivo)というテンプレートを使わせていただきました。元テンプレートのパーツだと文章量がある程度ないと収まらないので文章量が少なくても平気そうなレイアウトに修正（英語ができないからね…）させてもらったせいで元よりださくはなっています…

右上のかわいいGithubへのリンクは[GitHub Corners](http://tholman.com/github-corners/)を使っています。LWC OSSのサイトでもこれを使ってました。

### ビルド

基本的には[create-lwc-app](https://github.com/muenzpraeger/create-lwc-app)で作られたプロジェクトそのままです。create-lwc-appの設定は（TypeScriptやrollupが選べますが、）以下にしています。

* Webpack
* JavaScript
* PWA有効

Salesforce版LWCではTypeScriptが使えないのでサンプルとしてはJavaScriptがいいかなと。

Webpackは環境変数の置換処理周りの設定をいじっています。（参照:[lwc-soql-builder/webpack.config.js](https://github.com/lwc-soql-builder/lwc-soql-builder/blob/de21a1853667ae594a27de01a54694781653fd41/scripts/webpack.config.js)）

### デプロイ

[tschaub/gh-pages](https://github.com/tschaub/gh-pages)を使って、buildで生成されたdistディレクトリをgh-pagesブランチにpushしています。
`git subtree`だと.gitignore対象のディレクトリはpushできないようで、このツールは助かりました。

```
gh-pages -d dist
```

とするだけでgh-pagesブランチにpushできます。

### フロントエンド

* フロントエンドフレームワーク: [Lightning Web Components Open Source](https://lwc.dev/)
* ステートマネージメント: [Redux](https://redux.js.org/)
* CSSフレームワーク: [Lightning Design System](https://www.lightningdesignsystem.com/)

SLDS対応はLWCがShadow DOMなのですんなりはできず、[オープンソース LWC の ShadowRoot をポリフィルする - Qiita](https://qiita.com/hrk623/items/e8484acb8cd1e2c71833#%E3%81%8A%E3%81%BE%E3%81%91-lightning-design-system-%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B)の方法を使っています。

* PWA対応: [Workbox webpack Plugins](https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin)

PWA対応はGoogleのWorkbox使っていますが、create-lwc-appで生成されたコードをサブディレクトリで動くようにちょっと修正しただけです。

* Salesforce APIクライアント: [JSforce](https://jsforce.github.io/)
* SOQLパーサー: [SOQL Parser JS](https://paustint.github.io/soql-parser-js/)

いつもお世話になっているJSforceはもちろん使っています。これとSOQL Parser JSがなかったらこのツールは作れなかったですね。大感謝です。
JSforceは2.0を使おうかなあとも思ったんですが、まだαで使い方もよくわからなかったので見送りました。

### サーバーサイド

[jsforce/jsforce-ajax-proxy](https://github.com/jsforce/jsforce-ajax-proxy)を修正して、Cloud Functionsで動くようにしたものを用意しています。

[lwc-soql-builder/jsforce-ajax-proxy-cloud-functions](https://github.com/lwc-soql-builder/jsforce-ajax-proxy-cloud-functions)

`Sforce-Call-Options`ヘッダーを渡せるようにしたり、なぜかHerokuだと動くのにCloud Functionsだと動かないところを直したりしています。

# 今後の予定

とりあえず社内ハッカソンは４月で終わりなので一区切りではあるんですが、最低限、以下の機能は欲しいなと思ってます。

* サブクエリ/親リレーションの項目補完
* SOQL構文の入力補完
* 項目の全選択/全解除

あとは細かいところはいろいろ気になっているが、この辺は反響次第で。

* 検索フォームのクリア
* テストコード書きたい
* プロキシのリージョンを選べるようにする
* プロキシOFFモード
* 組織別にクエリ履歴持ちたいけど
* 各パネルのリサイズ
* 多態的な項目の対応

# 終わりに

皆さん使って感想もらえるとうれしいです！

# 参考リンク集

開発時に参考にしたURLを覚えてる範囲で貼っておきます。ありがとうございました。

* Githubリポジトリへのリンク
 * [GitHub Corners](http://tholman.com/github-corners/)
* PWAアプリのローカル開発時にキャッシュが再読込されない
 * [Troubleshoot and Debug  |  Workbox  |  Google Developers](https://developers.google.com/web/tools/workbox/guides/troubleshoot-and-debug)
 * PWAアプリの開発時にはUpdate on reloadとBypass for networkにチェックを入れる。
* LWC OSSでSLDSを使う
 * [オープンソース LWC の ShadowRoot をポリフィルする - Qiita](https://qiita.com/hrk623/items/e8484acb8cd1e2c71833)
* JavaScriptでSOQLの構文解析
 * [paustint/soql-parser-js: Javascript SOQL parser](https://github.com/paustint/soql-parser-js)
* LWC OSSサンプルアプリ
 * [pmdartus/rcast: 🎧 PWA podcast player written with LWC](https://github.com/pmdartus/rcast)
 * [snowforce/snowforce.io](https://github.com/snowforce/snowforce.io)
* 英語コミットログ
  * [[転載] gitにおけるコミットログ/メッセージ例文集100](https://gist.github.com/mono0926/e6ffd032c384ee4c1cef5a2aa4f778d7)
  * [GitHubで使われている実用英語コメント集 - Qiita](https://qiita.com/shikichee/items/a5f922a3ef3aa58a1839)
  * [オレオレコミットメッセージの生煮え書き方 - Qiita](https://qiita.com/yshr04hrk/items/39e54334b98f43544f22)
  * あとは普通にGithubを検索 https://github.com/search?q=fix&type=Commits
* combineReducerしているReducerで他のReducerのstateを参照する
  * [How do I share state between two reducers? Do I have to use combineReducers?](https://redux.js.org/faq/reducers#how-do-i-share-state-between-two-reducers-do-i-have-to-use-combinereducers)
  * 「stateの設計を見直す」「reduce-reducersなどを使う」「redux-thunkなどを使う」の３択。
  * 設計を見直すのがいいっぽいのでそれで対応。
* textareaのキャレット位置の座標を取得する
  * [component/textarea-caret-position: xy coordinates of a textarea or input's caret](https://github.com/component/textarea-caret-position)
  * 入力補完で使用
* サブディレクトリでPWAを動かす
  * 自分の記事だけど、[オープンソース版LWCのPWAをGithub Pages(サブディレクトリ)で動かす - Qiita](https://qiita.com/atskimura/items/02fbb61b0d13eb348e49)
* PWAの判定
  * [PWAで動いてる？isPWA(); - DEV Community 👩‍💻👨‍💻](https://dev.to/origamium/pwa-55j2)
* ビルド時に環境変数を渡す
  * lwc-servicesでprocess.env.NODE_ENVを渡す設定が書いてある。[create-lwc-app/lwc-services.config.js · muenzpraeger/create-lwc-app](https://github.com/muenzpraeger/create-lwc-app/blob/faf3074ceaba4ba9ab85b49a2499fdf854d36816/packages/lwc-services/example/lwc-services.config.js#L47)
  * しかし、渡ってこないのはwebpackを利用しているからだった。rollupだけこの処理が入っているので使える。[create-lwc-app/rollup.config.js · muenzpraeger/create-lwc-app](https://github.com/muenzpraeger/create-lwc-app/blob/e08417d126ce07072d8d8e9ec78ef8d71a6c692e/packages/lwc-services/src/config/rollup.config.js#L100)
  * でも、rollupだとうまくビルドできなかったので、結局[DefinePlugin | webpack](https://webpack.js.org/plugins/define-plugin/)を使用。
* 最新APIバージョンの取得
 * [使用可能な REST API バージョンをリストする | REST API 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/dome_versions.htm)
* APIコール数の表示
 * [Limit Info ヘッダー | REST API 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/headers_api_usage.htm)
* デフォルトネームスペースの省略
 * [Call Options ヘッダー | REST API 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/headers_calloptions.htm)