dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

follow us in feedly

[Salesforce]ワークフローの時間ベースのアクションについて

今さらながら時間ベースのワークフローを使うことがあったのでメモ。

時間ベースのワークフローとは

f:id:dackdive:20180817101627p:plain

ここのこと。

時間ベースのワークフローで何ができる?

時間ベースじゃない方のワークフローアクションだと、ルール条件に一致した場合レコードの作成または編集直後にアクションが実行される。
これに対し、時間ベースのアクションは文字通り、アクションの実行を将来のある時点に予約しておくことができる。

具体的なユースケースとして、たとえば
「金額が1000万円以上の大型商談について、完了予定日の7日前になった時点でクローズされていなければ、商談所有者にリマインドメールを送信する」
といったことができる。


将来のある時点、は何を基準に決定される?

レコードの作成日やルールが適用された日だけでなく、対象のオブジェクトの日付項目の値を基準にアクション実行のタイミングを制御することもできる。

タイミングの制御は基準となる項目の日付から数えて◯日前(後)、といった指定方法になる。

f:id:dackdive:20180817102652p:plain

↑では例として、商談オブジェクトのレコードに対し、「完了予定日」の7日前に実行されるアクションを設定している。


アクションが予約されたことをどうやって確認するの?

LEXであれば 環境>監視>時間ベースのワークフロー から、現在セットされている時間ベースのアクションを確認できる。

f:id:dackdive:20180817103443p:plain


アクションは一度予約されたら必ず実行される?

NO。
一度ルール条件に合致してアクションがセットされても、その後レコードが更新されてルール条件を満たさなくなれば、アクションは実行されない。

冒頭の例でいうと、

  • レコード作成時は金額1000万以上だったが、その後1000万未満になった
  • 完了予定日の7日前より以前に商談がロストした/クローズした

場合はリマインドメールは送信されない。

参考:FAQ - 時間ベースのワークフロー

キューの待機中のアクションは、必ず起動されますか?
いいえ。時間ベースのアクションは、プロセスまたはワークフロールールのルール条件が “false” と評価される限り、ワークフローキューに留まります。ルールが評価されたときに、レコードがルール条件に一致しなくなると、Salesforce はそのレコードについてキューにある時間ベースのアクションを削除します。


評価条件が「作成されたとき」か「作成されたとき、およびその後基準を満たすように編集されたとき」で挙動にどういう違いがあるの?

これは時間ベースじゃないアクションと同じ。
「作成されたとき」の場合はレコード作成時しか評価しないので、たとえば冒頭の例でいうと

  • 一度ロストした商談を再度 Prospecting に戻した(その時点ではまだ完了予定日の7日前より以前だった)

という操作をした場合も、リマインドメールの対象になるわけではない。

参考:FAQ - 時間ベースのワークフロー

レコードの待機中のアクションをキューに戻すことはできますか?
はい。レコードが更新され、評価条件をレコードが [作成されたとき、およびその後基準を満たすように編集されたとき] に設定した場合は、自動的にキューに戻されます (レコードは、入力された条件を以前に満たしていない必要があります)。

※これは余談だが、この直後にある例は日本語記事だとなぜか間違っている(1個前のと同じ例になっている)


プロセスビルダーでも同じことできないの?

できますね。「スケジュール済みアクション」という名前みたい。

f:id:dackdive:20180817105810p:plain

注意点として、プロセスの開始条件を「レコードを作成または編集したとき」にしている場合、スケジュール済みアクションを使えるようにするには

「レコードに指定の変更が行われた場合にのみアクションを実行しますか?」

にチェックを入れる必要がある。


リファレンス

Pipenvで仮想環境をプロジェクトディレクトリの下に作る(PIPENV_VENV_IN_PROJECT)

ちょいメモ。

久しぶりに Python を書くにあたって環境構築する際、 2018年のPythonプロジェクトのはじめかた - Qiita を見て Pipenv を使ってみた。

普通に pip shell で仮想環境を作成すると

 ~/workspace/Python/pipenv-sandbox $ pipenv shell
Creating a virtualenv for this project…
Using /Users/yamazaki/.pyenv/versions/3.6.5/bin/python3.6m (3.6.5) to create virtualenv…
⠋Running virtualenv with interpreter /Users/yamazaki/.pyenv/versions/3.6.5/bin/python3.6m
Using base prefix '/Users/yamazaki/.pyenv/versions/3.6.5'
New python executable in /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx/bin/python3.6m
Also creating executable in /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx
Spawning environment shell (/bin/zsh). Use 'exit' to leave.
. /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx/bin/activate
 ~/workspace/Python/pipenv-sandbox $ . /Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx/bin/activate
(pipenv-sandbox-9fl74ZVx)  ~/workspace/Python/pipenv-sandbox $

というように、プロジェクトディレクトリとは別のグローバルな場所( ~/.local/share/virtualenvs/ )に仮想環境が作られる。

これを、

$ virtualenv venv

したときと同じように、各プロジェクトディレクトリの下に作成したい。

Configuration With Environment Variables を読むと

  • PIPENV_VENV_IN_PROJECT — If set, use .venv in your project directory instead of the global virtualenv manager pew.

とあり、 .zshrc

export PIPENV_VENV_IN_PROJECT=true

を追加したところ、プロジェクトディレクトリの下に仮想環境が作成されるようになった。

 ~/workspace/Python/pipenv-sandbox $ pipenv shell
Creating a virtualenv for this project…
Using /Users/yamazaki/.pyenv/versions/3.6.5/bin/python3.6m (3.6.5) to create virtualenv…
⠋Running virtualenv with interpreter /Users/yamazaki/.pyenv/versions/3.6.5/bin/python3.6m
Using base prefix '/Users/yamazaki/.pyenv/versions/3.6.5'
New python executable in /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv/bin/python3.6m
Also creating executable in /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv/bin/python
Installing setuptools, pip, wheel...done.

Virtualenv location: /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv
Spawning environment shell (/bin/zsh). Use 'exit' to leave.
. /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv/bin/activate
 ~/workspace/Python/pipenv-sandbox $ . /Users/yamazaki/workspace/Python/pipenv-sandbox/.venv/bin/activate
(pipenv-sandbox) ~/workspace/Python/pipenv-sandbox $ 

# .venv ディレクトリが作られている
(pipenv-sandbox) ~/workspace/Python/pipenv-sandbox $ ls -al
total 8
drwxr-xr-x  4 yamazaki  staff  136  5 16 02:33 ./
drwxr-xr-x  8 yamazaki  staff  272  5 16 02:08 ../
drwxr-xr-x  5 yamazaki  staff  170  5 16 02:33 .venv/
-rw-r--r--  1 yamazaki  staff  138  5 16 02:08 Pipfile

.venv というディレクトリ名は変更できないのかな。
個人的にはこちらの方が好みだけど、両者にメリットデメリットあるのかは不明。

ちなみに

仮想環境が作られた場所を確認するには --venv オプションを使う。

(pipenv-sandbox-9fl74ZVx)  ~/workspace/Python/pipenv-sandbox $ pipenv --venv
/Users/yamazaki/.local/share/virtualenvs/pipenv-sandbox-9fl74ZVx

React 16.3.0で追加されたStrictModeコンポーネントについて

2018-04-01のJS: TypeScript 2.8、React 16.3.0、TensorFlow.js - JSer.info を読んで。

React 16.3.0 から StrictMode コンポーネントというものが追加されたらしい。
公式ドキュメントを読んでみます。


StrictMode とは

StrictMode はアプリの潜在的な問題を検出するために追加されたコンポーネントコンポーネントだが Fragment などと同じく UI として画面に表示されるものはない。
<StrictMode>...</StrictMode> で囲まれた子孫コンポーネントに対し、いくつかのチェックを行う。

また development モードでのみ動作し、 production build 時には影響を与えない。


ScrictMode がチェックしてくれること

今のところ以下。今後のリリースで機能は追加予定とのこと(Additional functionality will be added with future releases of React.)

  1. 安全でないライフサイクルメソッドの使用(Identifying components with unsafe lifecycles)
  2. レガシーな string ref の使用(Warning about legacy string ref API usage)
  3. 予期せぬ副作用の検出(Detecting unexpected side effects)
    • (検出するために、特定のライフサイクルメソッドを二度実行する)


1. 安全でないライフサイクルメソッドの使用

背景として、v16.3.0 以降は非同期レンダリングなどのサポートのために一部のライフサイクルメソッド

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

が今後削除予定となった。
参考:Update on Async Rendering - React Blog (こっちはまだ読んでない)

<StrictMode> の子孫コンポーネントでこれらのライフサイクルメソッドを使用しているものがあれば、ブラウザのコンソールで warning が出力される。

(サンプル)

import React, { Component, StrictMode } from 'react';

class UnsafeComponent extends Component {
  componentWillMount() {
    console.log('componentWillMount');
  }

  componentWillReceiveProps(props) {
    console.log('componentWillReceiveProps');
  }

  render() {
    return <div>Unsafe Component</div>;
  }
}

export default function App() {
  return (
    <StrictMode>
      <UnsafeComponent />
    </StrictMode>
  );
}

(結果) f:id:dackdive:20180404015423p:plain

Warning: Unsafe lifecycle methods were found within a strict-mode tree:
    in App
    in AppContainer

componentWillMount: Please update the following components to use componentDidMount instead: UnsafeComponent

componentWillReceiveProps: Please update the following components to use static getDerivedStateFromProps instead: UnsafeComponent

Learn more about this warning here:
https://fb.me/react-strict-mode-warnings

該当のコンポーネント名と使っているライフサイクルメソッドが表示されている。


2. レガシーな string ref の使用

ref を使ってコンポーネントを参照するための方法はこれまで2通りあって

  1. ref="input" のように文字列で指定する
  2. ref={(element) => this.input = element} のように callback 関数で指定する

このうち 1 の文字列で指定する方にはいくつか問題があったらしく、ドキュメントでも 2 の方法を推奨していた。
<StrictMode> の子孫コンポーネントで 1 の string ref を使っている箇所があると、こちらも同様にブラウザのコンソールでwarning が出る。

(サンプル)

import React, { Component, StrictMode } from 'react';

class LegacyRef extends Component {
  handleClick = () => {
    const name = this.refs.name.value;
    console.log('LegacyRef', name);
  };

  render() {
    return (
      <div>
        <input ref="name" />
        <button onClick={this.handleClick}>click me</button>
      </div>
    );
  }
}

class NewRef extends Component {
  constructor(props) {
    super(props);

    this.nameRef = React.createRef();
  }

  handleClick = () => {
    const name = this.nameRef.current.value;
    console.log('NewRef', name);
  };

  render() {
    return (
      <div>
        <input ref={this.nameRef} />
        <button onClick={this.handleClick}>click me</button>
      </div>
    );
  }
}

export default function App() {
  return (
    <StrictMode>
      <LegacyRef />
      <NewRef />
    </StrictMode>
  );
}

(結果) f:id:dackdive:20180404021000p:plain

Warning: A string ref, "name", has been found within a strict mode tree. String refs are a source of potential bugs and should be avoided. We recommend using createRef() instead.

    in div (created by LegacyRef)
    in LegacyRef (created by App)
    in App
    in AppContainer

Learn more about using refs safely here:
https://fb.me/react-strict-mode-string-ref


余談: createRef() を使った新しい ref の方法

上のサンプルで、 NewRef コンポーネントがやっているのは v16.3.0 から追加された新しい ref の実装方法で、 createRef() という関数を使う。
従来の string ref のような書き方で、かつ string ref のときの問題点は解決されている。らしい。

参考:React v16.3.0: New lifecycles and context API - React BlogcreateRef API の項

なお createRef() の導入後も 2 の callback を使った方法はサポートされるので、置き換える必要はない。


3. 予期せぬ副作用の検出

背景として、React の動作時には大きく2つのフェーズがある。

  • render フェーズ:DOM に適用する必要のある変更を決定する。 render メソッドが呼ばれ、結果を直前の render の結果と比較する
  • commit フェーズ:React がすべての変更を DOM に適用する。 componentDidMountcomponentDidUpdate などのライフサイクルメソッドもこのフェーズで呼ばれる

一般に commit フェーズは速いが render は遅い。そのため、非同期レンダリングによってレンダリング処理を複数の小さな処理に分割し、ブラウザをブロックしないように停止と再開をしながらレンダリングを行う。
これにより、commit 前に render フェーズのライフサイクルメソッドが複数回実行される可能性が生じる。
(このあたりはよくわかっていない)

ので、これらのライフサイクルメソッドに副作用がないことが重要となる。
これらのライフサイクルメソッドとは具体的には以下。

  • Class コンポーネントconstructor メソッド
  • render
  • setState の第一引数に関数を渡したときの関数(updater と呼ぶらしい)
  • getDerivedStateFromProps (v16.3.0 から componentWillReceiveProps の代替として追加)

ただ、これらのメソッドに副作用がないことを自動的に検出することは難しいため、StrictMode ではこれらのメソッドを2回ずつ実行する。

(サンプル)

class SideEffect extends Component {
  constructor(props) {
    super(props);

    this.state = { count: 0 };
    console.log('SideEffect constructor');
  }

  increment = () => {
    this.setState((prevState, props) => {
      console.log('SideEffect updater', prevState);
      return {
        count: prevState.count + 1,
      };
    });
  };

  render() {
    console.log('SideEffect render');
    return <div onClick={this.increment}>{this.state.count}</div>;
  }
}

export default function App() {
  return (
    <StrictMode>
      <SideEffect />
    </StrictMode>
  );
}

(結果)

f:id:dackdive:20180404023839p:plain


コード

一応上げておく。

https://github.com/zaki-yama/react-strict-mode-example

Node.js製CLIフレームワークoclifを試す

はじめに

Heroku が oclif という CLI フレームワークオープンソースとして公開したという記事を読みました。

Heroku CLISalesforce DX のベースにもなっているらしい。
どんなもんか触ってみます。

(oclif は (The) Open CLI Framework の略のようです。読み方がわからない。。。)


oclif の特徴

手を動かす前に、どういった特徴があるのか公式ドキュメントに目を通してみます。
Features · oclif: The Open CLI Framework

  • Super Speed
    • コマンド実行時のオーバーヘッド(?)がほとんどなく、また依存パッケージもほとんどない
    • 実行されるコマンドだけ require されるので、たくさんのコマンドからなる巨大 CLI でも単一コマンドの CLI と速度が変わらない
  • CLI Generator
    • コマンド一発で scaffold が生成できる generator がある
  • Testing Helpers
    • テストが書きやすい。 stdout/stderr を簡単にモックできる
    • generator がテストの scaffold も自動生成する
  • Auto-documentation
    • --help オプションで表示するヘルプテキストが自動生成される
    • ↑は CLI が publish されるときに README にも自動的に記載される(※最後で軽く触れる)
  • (未確認)Plugins
  • (未確認)Hooks
    • コマンド実行時などのライフサイクルイベントや独自にカスタムイベントを定義し、そこにフックする処理を書ける
  • TypeScript
    • TypeScript と JavaScript 両方をサポート
    • oclif 自体も TypeScript で書かれている
  • Coming soon: man pages, Autocomplete

テストしやすく、ドキュメントが自動生成されるのはいいですね。


Single-command と Multi-command

oclif で作成できる CLI には大きく分けて 2 種類あります。
Single-command とは lscurl のように、コマンド自体は1つで引数やオプションを取るものです。
Multi-command とは githeroku のように、後にサブコマンドが続くものです。


試してみる

今回は、自分が過去に Node.js で作った parse-salesforce-object という CLI に oclif を導入してみます。
Salesforce 開発で使うメタデータファイル(XML)をパースしてよしなに表示してくれるという、ごくごく一部の方にしか需要がないやつです)
この CLI は先ほどの分類で言うと Single-command です。


generator で CLI のひな形(scaffold)作成

Quickstart を参考に、generator を使って必要なファイルを生成します。

$ npx oclif single [コマンド名]

を実行すると途中で色々聞かれるので適宜入力します。

$ npx oclif single parse-salesforce-object
npx: 293個のパッケージを17.261秒でインストールしました。

     _-----_     ╭──────────────────────────╮
    |       |    │      Time to build a     │
    |--(o)--|    │  single-command CLI with │
   `---------´   │   oclif! Version: 1.7.9
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

? npm package name parse-salesforce-object
? command bin name the CLI will export parse-salesforce-object
? description
? author Shingo Yamazaki @zaki-yama
? version 0.0.5
? license MIT
? node version supported >=8.0.0
? github owner of repository (https://github.com/OWNER/repo) zaki-yama
? github name of repository (https://github.com/owner/REPO) parse-salesforce-object
? optional components to include
❯◉ yarn (npm alternative)
 ◉ mocha (testing framework)
 ◉ typescript (static typing for javascript)
 ◉ tslint (static analysis tool for typescript)
 ◯ semantic-release (automated version management)

最後のオプションで yarn, mocha, TypeScript を使うかどうかは好みです。

インストールが完了すると、 [コマンド名]ディレクトリが作成され、その下に必要なファイルが揃っています。
npm install (or yarn )も実行されているため、必要なパッケージもインストール済みです。

$ tree -I node_modules parse-salesforce-object
parse-salesforce-object
├── README.md
├── appveyor.yml
├── bin
│   ├── run
│   └── run.cmd
├── package.json
├── src
│   └── index.ts
├── test
│   ├── helpers
│   │   └── init.js
│   ├── index.test.ts
│   ├── mocha.opts
│   └── tsconfig.json
├── tsconfig.json
├── tslint.json
└── yarn.lock

bin/run を実行するとコマンドを実行できます。

$ cd parse-salesforce-object
$ ./bin/run
hello world from /Users/yamazaki/workspace/nodejs/parse-salesforce-object/src/index.ts!


コマンドファイルの構成

src/index.ts がコマンド本体です。中身を見てみます。
((1) ~ (3) は便宜的にこちらで番号を振りました)

import {Command, flags} from '@oclif/command'

class ParseSalesforceObject extends Command {
  // (3)
  static description = 'describe the command here'

  // (3)
  static examples = [
    `$ parse-salesforce-object
hello world from ./src/parse-salesforce-object.ts!
`,
  ]

  // (2)
  static flags = {
    // add --version flag to show CLI version
    version: flags.version({char: 'v'}),
    // add --help flag to show CLI version
    help: flags.help({char: 'h'}),

    // flag with a value (-n, --name=VALUE)
    name: flags.string({char: 'n', description: 'name to print'}),
    force: flags.boolean({char: 'f'}),
  }

  // (2)
  static args = [{name: 'file'}]

  // (1)
  async run() {
    const {args, flags} = this.parse(ParseSalesforceObject)

    const name = flags.name || 'world'
    this.log(`hello ${name} from ${__filename}!`)
    if (args.file && flags.force) {
      this.log(`you input --force and --file: ${args.file}`)
    }
  }
}

export = ParseSalesforceObject
  • (1) コマンドの実処理は run() メソッドに記述します
  • (2) 引数やオプション(flags)はこのように static 変数として定義します。この後ここをカスタマイズしてみます
  • (3) description, examples も同様に static 変数として定義すると、ヘルプテキストに反映されます

最後の (3) について、実際にコマンドを --help オプションつきで実行すると

$ ./bin/run --help
describe the command here

USAGE
  $ parse-salesforce-object [FILE]

OPTIONS
  -f, --force
  -h, --help       show CLI help
  -n, --name=name  name to print
  -v, --version    show CLI version

EXAMPLE
  $ parse-salesforce-object
  hello world from ./src/parse-salesforce-object.ts!

のように、description および examples に記述した文字列がヘルプの先頭と EXAMPLE セクションに記載されているのがわかります。


引数を処理する

さて、ここからひな形をベースに元の CLI としての機能を実装していきます。
まずは引数の処理から。
参考:Command Arguments · oclif: The Open CLI Framework

元の CLI では

const argv = require('minimist')(process.argv.slice(2));
const filePath = argv._[0];

...

if (!filePath) {
  console.log(chalk.red('ERROR: You must specify a path to .object file.'));
  process.exit(1);
}

fs.readFile(filePath, (err, data) => {
  ...
});

のように、とあるファイルへのパスを必須の引数として受け取るようになっていました。
またその処理のために minimist というライブラリを使っていました。

oclif だと以下のように書けます。

class ParseSalesforceObject extends Command {

  ...

  static args = [{
    name: 'path',
    description: 'path to .object file',
    required: true,
  }]

  async run() {
    const {args, flags} = this.parse(ParseSalesforceObject)

    fs.readFile(args.path, (err, data) => {
      ...
    })
  }

引数には { name: 'foo' } という形で名前を付けておくことができ、run() メソッド内で(パース後に) args.foo でアクセスできます。

その他のオプションは https://oclif.io/docs/args.html を参照するといいです。
必須かどうかもオプションで指定できるようになったので判定処理が不要になりました。

# 引数なしで実行するとエラーになる
$ ./bin/run
 ›   Error: Missing 1 required arg:
 ›   path  path to .object file
 ›   See more help with --help


フラグ(オプション)を処理する

続いて、いくつかのフラグを受け取れるようにします。
フラグとは -f foo--file=foo のようなものを指します。
参考:Command Flags · oclif: The Open CLI Framework

フラグは version と help 以外は

static flags = {
  force: flags.boolean({char: 'f'}),
  file: flags.string(),
}

のように、

  • 引数を受け取るもの: flags.string()
  • 引数を受け取らず、boolean として使うもの: flags.boolean()

の 2 種類あります。

また両者に共通して、{char: 'f'} のように char オプションを指定すると短縮形も扱えるようになります。

その他のオプションは https://oclif.io/docs/flags.html を参照します。

元の CLI には、-f xxx または --format=xxx オプションで出力フォーマットを指定でき、その選択肢は markdown, csv, soql のいずれかとなっていたので

format: flags.string({
  char: 'f',
  description: 'output format',
  options: ['markdown', 'csv', 'soql'],
  default: 'markdown',
}),

のように optionsdefault を利用しました。便利。

# -f で許可されているフォーマット以外を指定するとエラー
$ ./bin/run objects/Expense__c.object -f foo 
 ›   Error: Expected --format=foo to be one of: markdown, csv, soql
 ›   See more help with --help


ヘルプを出力してみる

引数やオプションを一通り定義した後で、 --help によりヘルプを表示してみます。

$ ./bin/run --help
USAGE
  $ parse-salesforce-object PATH

ARGUMENTS
  PATH  path to .object file

OPTIONS
  -f, --format=markdown|csv|soql  [default: markdown] output format
  -h, --help                      show CLI help
  -n, --namespace=namespace       namespace prefix (for SOQL format)
  -v, --version                   show CLI version

EXAMPLE
  $ parse-salesforce-object src/objects/Expense__c.object
  | label       | fullName      | type     | required |
  | ----------- | ------------- | -------- | -------- |
  | Amount      | Amount__c     | Number   | false    |
  | Client      | Client__c     | Text     | false    |
  | Date        | Date__c       | DateTime | false    |
  | Reimbursed? | Reimbursed__c | Checkbox | null     |

ARGUMENTS および OPTIONS のセクションのところが、定義した引数・フラグの内容から自動生成されました。


GitHub

今回 oclif での置き換えを試した CLIリポジトリはここにあります。

PR は https://github.com/zaki-yama/parse-salesforce-object/pull/6


まとめと TODO

今回は oclif の導入手順と基本構成についてなんとなくわかったという程度ですが、個人的には

  • npx oclif single/multi foo で scaffold から始められるのは楽
  • 引数やオプションからヘルプ自動生成は便利

といった点が、フレームワークというだけあって良いなと思いました。
また将来的に Autocomplete もサポートしてくれるのは期待したい。

テストを書くところや Plugins、Hooks については試せてないので、今後の TODO ということで。


おまけ:Auto-documentation について

Features#Auto-documentation には

This information is also automatically placed in the README whenever the npm package of the CLI is published. See the multi-command CLI example

と記載がありますが、自動生成したヘルプを README に埋め込む方法はドキュメントに記載がありませんでした。
リンクされてるリポジトリを見ると oclif-dev という CLI を使ってる っぽいので、これかな...?


あわせて読みたい

Salesforce のエンジニアブログにも記事があった。

Vim+ALEでファイル保存時にPrettierを実行する

メモ。
Prettier という JavaScript のフォーマッターをファイル保存時に自動的に実行する、というのを Vim でやりたい。
特に自分は ESLint や Flow のチェックに ALE というプラグインを使っているため
(参考:VimでESLintとFlowを使うためにNeomakeからALEに乗り換える - Qiita
Prettier も同じように ALE で設定できないのか調べた。

すると、ちゃんと Prettier の公式ドキュメントに ALE での設定方法が載ってた。

Prettier 単体で使う場合は上記を読むのが一番早いが、自分は ESLint と併用するために prettier-eslint-cli)を使っているのでその前提で手順を記載する。


設定手順

Vim

ALE の設定として以下を追加する。

let g:ale_fixers = {}
let g:ale_fixers['javascript'] = ['prettier-eslint']

" ファイル保存時に実行
let g:ale_fix_on_save = 1

" ローカルの設定ファイルを考慮する
let g:ale_javascript_prettier_use_local_config = 1

プラグイン管理に dein.vim を使っており、toml で管理している場合は以下のようになる

# rc/dein.toml

[[plugins]]
repo = 'w0rp/ale'
hook_add = '''
let g:ale_statusline_format = ['E%d', 'W%d', 'OK']

nmap <silent> <C-w>j <Plug>(ale_next_wrap)
nmap <silent> <C-w>k <Plug>(ale_previous_wrap)

let g:ale_fixers = {}
let g:ale_fixers['javascript'] = ['prettier-eslint']

" ファイル保存時に実行
let g:ale_fix_on_save = 1

" ローカルの設定ファイルを考慮する
let g:ale_javascript_prettier_use_local_config = 1
'''

let g:ale_fixers['javascript'] = ['prettier-eslint'] の部分、prettier 単体では ['prettier'] でいいが ESLint と併用している場合は prettier-eslint-cli を使うため上記の設定になる。


プロジェクト側

prettier-eslint-cli をインストールしておくだけ。

$ yarn add -D prettier-eslint-cli


様子

f:id:dackdive:20180330013445g:plain

あんまり早くない。。。
非同期で行われるためそこまで気にならないかもしれないけど。
VSCode とかでもこんなもんなんだろうか。


GitHub

以上を反映した Redux アプリ用テンプレートリポジトリがこちらです。
Prettier 導入の Issue は https://github.com/zaki-yama/redux-express-template/issues/16


リファレンス

冒頭の Prettier のドキュメントのほか、ALE の README でも fixer については記載があった。
https://github.com/w0rp/ale#2ii-fixing

[Salesforce]ApexでPermissionSetの"PermissionsXXX"項目の一覧を取得

ちょいメモ。

参考:PermissionSet | SOAP API 開発者ガイド | Salesforce Developers

f:id:dackdive:20180324030725p:plain

Permissions... から始まる項目がいくつかあるらしいので、Apex で項目一覧を取得してみる。

調べ方

以下を開発者コンソールの Execute Anonymous で実行。ログをダウンロードして USER_DEBUG だけ抽出。

Map<String, Schema.SObjectField> M = Schema.SObjectType.PermissionSet.fields.getMap();

SObjectType t = Schema.getGlobalDescribe().get('PermissionSet');
Map<String,Schema.SObjectField> fields = t.getDescribe().fields.getMap();

for (Schema.SObjectField field : fields.values()) {
    Schema.DescribeFieldResult dfr = field.getDescribe();
    if (dfr.getName().contains('Permissions')) {
        System.debug(dfr.getName() + ' | ' + dfr.getLabel());
    }
}

結果

API 参照名 ラベル
PermissionsEmailSingle メールの送信
PermissionsEmailMass 一括メール送信
PermissionsEditTask ToDo の編集
PermissionsEditEvent 行動の編集
PermissionsExportReport レポートのエクスポート
PermissionsImportPersonal 個人データのインポート
PermissionsDataExport ウィークリーデータのエクスポート
PermissionsManageUsers ユーザの管理
PermissionsEditPublicFilters 公開リストビューの管理
PermissionsEditPublicTemplates 公開テンプレートの管理
PermissionsModifyAllData すべてのデータの編集
PermissionsManageCases ケースの管理
PermissionsMassInlineEdit リストからの一括編集
PermissionsEditKnowledge 記事の管理
PermissionsManageKnowledge Salesforce ナレッジの管理
PermissionsManageSolutions 公開ソリューションの管理
PermissionsCustomizeApplication アプリケーションのカスタマイズ
PermissionsEditReadonlyFields 参照のみ項目の編集
PermissionsRunReports レポート実行
PermissionsViewSetup 設定・定義を参照する
PermissionsTransferAnyEntity 所有権の移行
PermissionsNewReportBuilder レポートビルダー
PermissionsActivateContract 契約の有効化
PermissionsActivateOrder 注文の有効化
PermissionsImportLeads リードのインポート
PermissionsManageLeads リードの管理
PermissionsTransferAnyLead リード所有者の移行
PermissionsViewAllData すべてのデータの参照
PermissionsEditPublicDocuments 公開ドキュメントの管理
PermissionsViewEncryptedData 暗号化されたデータの参照
PermissionsEditBrandTemplates レターヘッドの管理
PermissionsEditHtmlTemplates HTML テンプレートの編集
PermissionsChatterInternalUser Chatter 内部ユーザ
PermissionsManageTranslation 翻訳の管理
PermissionsDeleteActivatedContract 有効契約の削除
PermissionsChatterInviteExternalUsers Chatter に顧客を招待する
PermissionsSendSitRequests 登録情報照会要求の送信
PermissionsManageRemoteAccess 接続アプリケーションを管理する
PermissionsCanUseNewDashboardBuilder ドラッグアンドドロップ ダッシュボードビルダー
PermissionsManageCategories カテゴリの管理
PermissionsConvertLeads リードの取引の開始
PermissionsPasswordNeverExpires パスワード無期限
PermissionsUseTeamReassignWizards チーム再割り当てウィザードの使用
PermissionsEditActivatedOrders 有効化された注文の編集
PermissionsInstallPackaging AppExchange パッケージのダウンロード
PermissionsPublishPackaging AppExchange パッケージのアップロード
PermissionsChatterOwnGroups 新規 Chatter グループの作成および所有
PermissionsEditOppLineItemUnitPrice 商談商品の販売価格の編集
PermissionsCreatePackaging AppExchange パッケージの作成
PermissionsBulkApiHardDelete Bulk API の物理削除
PermissionsSolutionImport ソリューションのインポート
PermissionsManageCallCenters コールセンターの管理
PermissionsManageSynonyms シノニムの管理
PermissionsViewContent ポータルのコンテンツの参照
PermissionsManageEmailClientConfig メールクライアント設定の管理
PermissionsEnableNotifications アウトバウンドメッセージの送信
PermissionsManageDataIntegrations データインテグレーションの管理
PermissionsDistributeFromPersWksp コンテンツ配信の作成
PermissionsViewDataCategories データカテゴリの表示
PermissionsManageDataCategories データカテゴリの管理
PermissionsAuthorApex Apex 開発
PermissionsManageMobile モバイル設定を管理する
PermissionsApiEnabled API の有効化
PermissionsManageCustomReportTypes カスタムレポートタイプの管理
PermissionsEditCaseComments ケースコメントの編集
PermissionsTransferAnyCase ケース所有者の移行
PermissionsContentAdministrator Salesforce CRM Content の管理
PermissionsCreateWorkspaces ライブラリの作成
PermissionsManageContentPermissions コンテンツ権限の管理
PermissionsManageContentProperties コンテンツプロパティの管理
PermissionsManageContentTypes ファイルのレコードタイプおよびレイアウトの管理
PermissionsManageExchangeConfig Lightning Sync を管理
PermissionsManageAnalyticSnapshots レポート作成スナップショットを管理
PermissionsScheduleReports レポートのスケジュール
PermissionsManageBusinessHourHolidays 営業時間の休日の管理
PermissionsManageDynamicDashboards 動的ダッシュボードの管理
PermissionsCustomSidebarOnAllPages すべてのページにカスタムサイドバーを表示
PermissionsManageInteraction フローの管理
PermissionsViewMyTeamsDashboards 私のチームのダッシュボードの参照
PermissionsModerateChatter Chatter のモデレート
PermissionsResetPasswords ユーザパスワードのリセットおよびユーザのロック解除
PermissionsFlowUFLRequired フローユーザ機能ライセンスが必要
PermissionsCanInsertFeedSystemFields Chatter フィードにシステム項目値を挿入
PermissionsManageKnowledgeImportExport ナレッジ記事のインポート/エクスポートの管理
PermissionsEmailTemplateManagement メールテンプレートの管理
PermissionsEmailAdministration メール管理
PermissionsManageChatterMessages Chatter メッセージとダイレクトメッセージを管理
PermissionsAllowEmailIC メールベースの ID 検証オプション
PermissionsChatterFileLink 公開リンクの作成
PermissionsForceTwoFactor ユーザインターフェースログインの 2 要素認証
PermissionsViewEventLogFiles イベントログファイルを参照
PermissionsManageNetworks コミュニティを作成および設定
PermissionsManageAuthProviders 認証プロバイダの管理
PermissionsRunFlow フローを実行
PermissionsCreateCustomizeDashboards ダッシュボードの作成とカスタマイズ
PermissionsCreateDashboardFolders ダッシュボードフォルダを作成
PermissionsViewPublicDashboards 公開フォルダのダッシュボードを参照
PermissionsManageDashbdsInPubFolders 公開フォルダのダッシュボードを管理
PermissionsCreateCustomizeReports レポートの作成とカスタマイズ
PermissionsCreateReportFolders レポートフォルダを作成
PermissionsViewPublicReports 公開フォルダのレポートを参照
PermissionsManageReportsInPubFolders 公開フォルダのレポートを管理
PermissionsEditMyDashboards 私のダッシュボードを編集
PermissionsEditMyReports 私のレポートを編集
PermissionsViewAllUsers すべてのユーザの参照
PermissionsAllowUniversalSearch Knowledge One
PermissionsConnectOrgToEnvironmentHub 環境ハブに組織を接続
PermissionsWorkCalibrationUser Work.com 調整を有効化
PermissionsCreateCustomizeFilters リストビューを作成およびカスタマイズ
PermissionsWorkDotComUserPerm Work.com を有効化
PermissionsGovernNetworks コミュニティを管理する
PermissionsSalesConsole セールスコンソール
PermissionsTwoFactorApi API ログインの 2 要素認証
PermissionsDeleteTopics トピックを削除
PermissionsEditTopics トピックを編集
PermissionsCreateTopics トピックを作成
PermissionsAssignTopics トピックを割り当てる
PermissionsIdentityEnabled Identity 機能を使用
PermissionsIdentityConnect Identity Connect を使用
PermissionsAllowViewKnowledge ナレッジの参照を許可
PermissionsContentWorkspaces ライブラリへのアクセス
PermissionsManageSearchPromotionRules 昇格済み検索語の管理
PermissionsCustomMobileAppsAccess カスタムモバイルアプリケーションにアクセス
PermissionsViewHelpLink ヘルプリンクを参照
PermissionsManageProfilesPermissionsets プロファイルおよび権限セットを管理
PermissionsAssignPermissionSets 権限セットの割り当て
PermissionsManageRoles ロールを管理
PermissionsManageIpAddresses IP アドレスを管理
PermissionsManageSharing 共有を管理
PermissionsManageInternalUsers 内部ユーザを管理
PermissionsManagePasswordPolicies パスワードポリシーを管理
PermissionsManageLoginAccessPolicies ログインアクセスポリシーを管理
PermissionsManageCustomPermissions カスタム権限を管理
PermissionsCanVerifyComment Chatter の質問への回答の確認
PermissionsManageUnlistedGroups 「リストに記載しない」グループを管理
PermissionsModifySecureAgents セキュアエージェントを変更
PermissionsManageTwoFactor API で 2 要素認証を管理
PermissionsChatterForSharePoint Chatter For SharePoint へのアクセス
PermissionsLightningExperienceUser Lightning Experience ユーザ
PermissionsConfigCustomRecs カスタムおすすめの設定
PermissionsSubmitMacrosAllowed ユーザが元に戻せないマクロを管理
PermissionsBulkMacrosAllowed 複数のレコードに対してマクロを実行
PermissionsShareInternalArticles 内部のナレッジ記事を外部と共有
PermissionsManageSessionPermissionSets セッション権限セットの有効化を管理
PermissionsSendAnnouncementEmails お知らせメールを送信
PermissionsChatterEditOwnPost 自分の投稿を編集
PermissionsChatterEditOwnRecordPost 自分の所有レコードへの投稿を編集
PermissionsImportCustomObjects カスタムオブジェクトのインポート
PermissionsDelegatedTwoFactor ユーザインターフェースで 2 要素認証を管理
PermissionsChatterComposeUiCodesnippet UI からのコードスニペットの挿入を許可
PermissionsSelectFilesFromSalesforce Salesforce からファイルを選択
PermissionsModerateNetworkUsers コミュニティユーザモデレート
PermissionsMergeTopics トピックのマージ
PermissionsSubscribeToLightningReports レポートを登録
PermissionsManagePvtRptsAndDashbds すべての非公開レポートおよびダッシュボードを管理
PermissionsAllowLightningLogin Lightning Login ユーザ
PermissionsCampaignInfluence2 キャンペーンインフルエンス
PermissionsViewDataAssessment データ評価の参照アクセス権
PermissionsRemoveDirectMessageMembers ダイレクトメッセージから人を削除
PermissionsCanApproveFeedPost フィード投稿とコメントを承認可能
PermissionsAddDirectMessageMembers ダイレクトメッセージに人を追加
PermissionsAllowViewEditConvertedLeads 取引開始済みのリードを表示および編集
PermissionsShowCompanyNameAsUserBadge コミュニティロールとして会社名を表示
PermissionsAccessCMC コミュニティ管理にアクセス
PermissionsViewHealthCheck 状態チェックを表示
PermissionsManageHealthCheck 状態チェックを管理
PermissionsPackaging2 第二世代パッケージの作成と更新
PermissionsManageCertificates 証明書を管理
PermissionsCreateReportInLightning レポートビルダー (Lightning Experience)
PermissionsPreventClassicExperience Salesforce Classic に切り替えるオプションを非表示
PermissionsHideReadByList [表示先] リストを非表示
PermissionsListEmailSend リストメールの送信を許可
PermissionsFeedPinning フィードでの投稿の固定
PermissionsChangeDashboardColors ダッシュボードの色を変更
PermissionsIotUser IoT ユーザ
PermissionsUseWebLink カスタマイズしたアクションへのアクセスを許可
PermissionsViewAllActivities すべての活動を表示
PermissionsSubscribeReportToOtherUsers レポートを登録: 受信者を追加
PermissionsLightningConsoleAllowedForUser Lightning コンソールユーザ
PermissionsSubscribeReportsRunAsUser レポートを登録: 実行ユーザを設定
PermissionsSubscribeToLightningDashboards ダッシュボードへの登録
PermissionsApexRestServices Apex REST サービス
PermissionsEnableCommunityAppLauncher コミュニティでアプリケーションランチャーを表示
PermissionsManageSurveys アンケートを管理
PermissionsViewRoles ロールおよびロール階層を表示

いっぱいある。

[Salesforce]代理承認者機能の使い方

まとまってるドキュメントが見つからなかったのでメモ。


代理承認者とは

  • Salesforce の承認プロセス機能において、承認者の代わりに申請を承認/却下できる人を設定できる機能
  • ユーザに対して一人だけ代理承認者を設定しておくことができる
  • また承認プロセスの各承認ステップごとに代理承認者による承認を許可するかどうかが設定できる

承認プロセスそのものについては以下の Trailhead モジュールをやるといい。


設定手順

ユーザ設定より代理承認者を設定する

代理承認者を設定したいユーザのユーザ設定画面を開き、下の方の「承認者の設定」セクションに移動する。
「代理承認者」項目に代理承認者にしたいユーザを検索してセットする。

f:id:dackdive:20180317025929p:plain:w320

また、代理承認者自身のユーザ設定で、「承認申請メールを受信」項目を「自分が承認者である場合のみ」から「承認者または代理承認者である場合」に変更しておくとよい。理由は後述。

f:id:dackdive:20180317025925p:plain:w320


承認プロセス設定より、承認ステップで代理承認を許可する

今度は承認プロセスの設定画面を開き、任意の承認ステップの編集画面を開く。

f:id:dackdive:20180317031300p:plain

ステップ 3/3 で、一番下に「代理承認者もこの申請を承認可能にする」という項目があるのでチェックする。

f:id:dackdive:20180317031303p:plain


結果

自分が代理承認者になっている承認ステップに申請が回ってきたとき、承認者同様にメールが届く。
メールの件名には「(◯◯の代理承認者)」と書かれているよう。

f:id:dackdive:20180317032513p:plain


制限事項

自身が代理承認可能な申請は今のところ「未承認申請一覧」には表示されない。メール以外に確認する手段がないのでつらい。
これについてはずいぶん前から Idea に投稿されてるよう。

また、以下のヘルプ記事ではメール以外のワークアラウンドも紹介されてる(英語)。

Why can't a delegated approver see anything in the Items to approve list?

どうやら「レコードが現在承認プロセスの申請中かどうか」を表すチェックボックス項目をオブジェクトに追加し、それを条件にリストビューを作るという方法らしい。
チェックボックスの ON/OFF は承認プロセスの開始時や最終承認/却下時のアクションとして項目自動更新により自動的に制御できる。

が、これは別に「自身が代理承認になっているものリスト」ではないし、もっと言うと自分が承認者じゃないやつも含まれるのでは。。。

(2018/3/23追記)

と思ったら、標準レポートに「代理承認申請一覧」というのがあった。。。

f:id:dackdive:20180323234404p:plain

これで一覧表示できます。

f:id:dackdive:20180326143254p:plain

※ただし、標準フォルダは LEX では表示されないため、コピーする必要あり
(参考:レポートおよびダッシュボード: Lightning Experience で使用できない機能とその新機能


「代理承認者」に設定する以外に、他人の申請を承認/却下する方法

「代理承認者」はユーザにつき一人しか指定できない。もう少し柔軟に代理承認可能なユーザを増やすにはどうするか。

代理承認ではないが、申請を行うオブジェクトに対して「すべての編集」権限があれば任意の申請を承認/却下することができる。
(「すべてのデータの編集」権限もそう)

参考:承認の管理に関する考慮事項

管理者権限
次の権限のいずれかを持つユーザは承認管理者とみなされます。

  • 指定のオブジェクトに対するオブジェクトレベルでの「すべての編集」権限
  • 「すべてのデータの編集」ユーザ権限

承認管理者には次の権限があります。

  • 承認プロセスの一環としてではなく、未承認申請を承認または却下
  • 承認がロックされたレコードの編集


わかっていないこと

  • 承認者としてキューやグループを指定したときにどういう挙動になるのか