patorashのブログ

方向性はまだない

「その仕事、全部やめてみよう」を読んだ

TLで見かけていて、面白そうだから買っていたのに積読していたので、消化した。

著者はクレディセゾンのCTOの小野和俊さん。プログラマ出身の経営者ならではの視点で、プログラマには非常に面白い読み物だった。若手にも中堅にもベテランにも刺さる内容だろうと思う。

タイトルは刺激的だけれど、言ってるのは「後戻りできないと思い込んでいるようなものをちゃんと見つめ直して、止めれるのであれば止めよう」というところだった。

手段の目的化になっていないか?

失敗しやすいパターンの話など、あるある過ぎて唸ってしまう。顧客を見ずに無理やり差別化とか、上司の思い付きをねじ込むとか。

また、新技術を使いたいから、製品を作るなど…。新技術は枯れていないため、問題が発生したときに解決し辛かったりするので、すぐに投入するのは危ない。 あくまでも、その技術がどう課題解決に役立つのか?という視点が重要である。

技術には触れておいて、本当に投入するべきときに投入できるようにしておくのがいい。

正しいDXとは

DX(デジタルトランスフォーメーション)は、CX(顧客体験)かEX(従業員体験)の改善のどちらかが必ずセットでなければならない、とあった。デジタル技術を入れればDXではない。めっちゃ頷いた。

現実的に対応するには?

未知のものにはDCAP

未知のものには、PDCAではなく、DCAPという話があった。未知なので、体験から学習していかなければ効率よく進められない。例え話で、「サッカーをやるのに最初にルールブックを読むところからは始めない。まずボールを蹴ってみる。ゲームに参加してみるところから、こういうものなのかと学んだほうが早い」とあったが、だいたいのゲームなどはそうだ。

新技術の習得などはまず使ってみると便利さが分かってくるものだし、逆に課題も見つかりやすい。

読んでいて、アジャイル開発との相性がよい考え方だと思った。

PDCAが向いている領域

ミスが許されなかったり、要件が変わることが許されない、関係者が多い場合などはPDCAが向いているというか、DCAPが向いていない。ウォーターフォールと似ている。

製品も成長戦略も「谷を埋めるな、山を作れ」

欠点は指摘されやすい

製品にしても人間にしても、どうしても欠点に目が行きがち。欠点を指摘することは簡単で、修正することも、やるべきことが明白なので比較的やりやすいけれど、本当にそれが顧客の価値に直結するのか?谷は他の製品が世に示した価値なので、その製品の特長にはなりにくい。

本の中では、大手が競合商品を出してきたときに、自社製品と比較してしまうと、明らかに劣ってしまう部分があったけれど、その谷を埋めるのは最小限に抑えて山を作ることに注力した結果、多くのユーザーを獲得することができたという話があった。山がなければ、ブランド勝負・価格勝負に陥ってしまう。

ラストマン戦略

ラストマン戦略とは、そのグループ内でのスペシャリストを目指す成長戦略のこと。この技術について、この人に聞いてもわからないのならば仕方ない、というところまで極める。これもまた山を作る戦略だなと思う。 この人はこういう人!というブランディングにも役立つだろうし、頼りにされるので成長を実感しやすくなる。新人であっても、役に立っていると感じられるようになるので、何かしらに照準を絞ってスペシャリストになっておくのは、いいことのようだ。それに、お互いに足りない部分を学習しあえるので、相対的に仕事のレベルの底上げができそう。

誰がどの分野のラストマンになるのか?というのは、話し合っておいて競合しないようにしておくとよさそうだった。 もし、競合する場合でも、更に細分化して一位を目指せばいい。

キレる人も大事

士気を下げるほどキレ散らかしている人は流石に問題ではあると思うが、キレる人は何故キレるのか?を考えると、更なる高みを目指しているからで、その熱量で熱暴走している状態だという。そういう人の熱量を使って、更によい方向にもっていくようにしたい。温和な空気は、ぬるま湯になっていないか?ということでもあり得そうだ。

効率化の鍵

繰り返し処理を攻略せよ、という節があったのだが、そこに書いてあることが非常によかった。繰り返し処理のパフォーマンスチューニングをすると、小さな改善であっても何回も繰り返すため、物凄い効果を発揮する。 ハードウェアを替えれば効率化できるものは、さっさとハードウェアを交換する。常にルーチンワークを自動化できないか、少しでも効率を上げられないかを考えていきたい。

ルーチンワークを遅くするものには敏感になっておかないと、どんどん時間を奪われる。

ペアプロの話

ペアプロの話があったが、これもまた面白かった。fukabori.fmのt_wadaさんの回を思い出した。

fukabori.fm

人は見られるとカッコつける、集中する、ノウハウを提供する、柔軟になれるという点が書いてある、本当にそうだなぁ~と思う。もっとペアプロやったほうがいいかな…と思えてきた。

楽しさに触れる。頑張るキッカケを作る。

序盤にあった「体験会」の話が、すごくよかった。暗号通貨になんとなく懐疑的だった人たちに対して、ビットコインを配布する体験会をやったことで、一気に暗号通貨を理解してもらったり、身近なものとして認識してもらうことができたとあった。それと同じような感じでクラウド体験会をインフラエンジニアに向けて開催したり。参加者は新しい技術に触れてやる気が出てきたとのこと。

これはDCAPのDを準備するということだけれど、Dまで進むと人によっては一気に道が開けると思う。

あとがきでも、勉強ができなかった筆者についた家庭教師が、勉強そのものだけではなく、勉強した先にある世界を見せてくれたのが、勉強を頑張るキッカケになったという話があった。勉強した先にある世界を、今まで見せられてきただろうか?と自問自答すると、まだそこまでできてなかったかもな…と思う。それぞれがもっと積極的に得意分野のハンズオンを開催していけるようにしていきたいと思った。

今年の目標

大らかな気持ちで生きていきたい。

目先の利益に囚われがちだし、人の成功には嫉妬しがちだし、流行りの技術とかのキャッチアップができてないから焦燥感ありがち。

だいたいそんなことはどうでもいいのに、いつの間にかそういう価値観をインストールされてしまっている。そういうものから解放されたい。

もう持って生まれてきたミッションとか別にないなって気持ちだし、自分の周りだけでも幸せにできていければ御の字…。

仕事面

とはいえ、仕事でやりたいことはあるので、それはしっかりこなしていきたい。

  • 某企画を成功させること
  • 後輩氏の育成で得た気付きをアウトプットする
  • 今期目標にしてある例のやつを完成させてリリースする

全然伝わらないだろうけれど、こんな感じ。

リモートワーク環境の改善

去年エアコンを付けたのでだいぶマシになったけれど、もうちょっと改善できたらなぁ~という気持ちはある。とりあえず、フットレストほしい。 それにしてもこんな時代がくるんだったら、家を建てるときに小さくてもいいから書斎を作っておくべきだった…。

家庭面

来年度から次男氏も幼稚園に行き始めるので、色々変わるだろうし、長男氏は就学先の見学とかがあるので、当面忙しそう。 家事負担を減らすためのこととかには取り組んでいきたい。あと断捨離したい。

なるべく規則正しく生活したい~。でも子供が寝ない~!

自身

インプットが減ってるからインプット貪りたい。そう言ってるくせしていざ時間ができるとぼーっとしがち。疲れてるのか?

1ヶ月に1冊は積読を消化していこう。

総括すると

ゆるく生きていきたい。

RSpecでモデルのエラー確認はof_kind?が便利

Rails 6からだけれど、モデルのエラー確認にof_kind?メソッドが使えるらしい。

最初は、伊藤さんのQiitaの記事を見て、be_addedメソッドを知った。これも便利そうだなぁと思っていたのだが、そのコメント欄にRails 6からof_kind?が加わったと書いてあった。

qiita.com

qiita.com

どちらもめっちゃ便利じゃないですか。

今までどうしていたか?

すごく昔は、エラー数だけカウントして済ましていたけれど、最近はcontain_exactlyメソッドを使っていた。

expect(subject.errors.details[:name]).to contain_exactly({ error: :blank })

これでもまぁ割と簡潔には書けているようには見える…けれど、contain_exactlyは完全一致しないといけないので、他の値が入ってくるようなケースだと辛い。

# valueとかcountとか書くのが、かなり面倒…
expect(subject.errors.details[:code_name]).to contain_exactly(
  { error: :blank },
  { count: 3, error: :too_short },
  { error: :invalid, value: nil },
)

今後はどうするか?

これを、of_kind?で書き換えると…

# valueやcountが不要になって簡潔!
expect(subject.errors).to be_of_kind(:code_name, :blank)
expect(subject.errors).to be_of_kind(:code_name, :invalid)
expect(subject.errors).to be_of_kind(:code_name, :too_short)

コード量が増えているようには見えますが、行コピーして最後だけ書き換えるだけなので楽です。

既存のコードを書き換える正規表現

いちいち手で書き換えていくのが面倒なので、正規表現を作りました。RubyMine上で置換するのに使ったらちゃんと動きました。

# 検索条件
expect\(subject.errors.details\[(.+?)\]\)\.to contain_exactly\(\{.*?error: ([:\w]+)(.*?) \}\)
# 置換する文字列
expect(subject.errors).to be_of_kind($1, $2)

単純なやつ

Before

これが…

expect(subject.errors.details[:name]).to contain_exactly({ error: :blank })
After

こう置換されます。

expect(subject.errors).to be_of_kind(:name, :blank)

ちょっと複雑なやつ

Before

これが…

expect(subject.errors.details[:name]).to contain_exactly({ error: :invalid, value: nil })
After

こう置換されます。

expect(subject.errors).to be_of_kind(:name, :invalid)

まとめ

of_kind?メソッドを使うと検証が簡潔になっていい!ただし、Rails 6から!

あと、kind_ofと間違えがちになるので気をつけたいところ…。

CircleCIでresource_classをsmall、parallelismを増やして高速化・節約

副題:CircleCIでdocker-composeを使うのをやめた。

1年前くらいには、CircleCIでdocker-composeを動かす方法についての記事を書いてた。

patorash.hatenablog.com

しかし、これが遅い。まぁ自分のやり方が悪いというのはあったのだけれど。ライブラリのキャッシュを1コンテナでやってから、次のジョブで複数コンテナに配布するようにしたほうが無駄がなくていいかなと思って、そうしていたのだけれど、docker-volume内にあるからキャッシュするためには一旦取り出さなければならないし、キャッシュを反映するにはdocker-volume内にインポートしなければならず、これがすごく遅かった。

最近ではresource_classがmedium、parallelismが4で、parallel_testsを使っても40分くらいかかっていた。遅すぎる。

長いことプロダクトバックログに積んでいたのだけれど、いい加減耐えられなくなってきたのと、チーム内でもテストの費用が上がっていて、下げる施策を探らなければならないという状態だったので、手をつけた。うちのプロジェクトは実験台にするにはちょうどいいのだ。

何をしたか?

やりたかったのは、スタディストさんのところのブログにあったやつ。

このブログにあった通りで、docker-composeを使う限りは速くならない(金をかければ多少速くなるけれど、コストと見合わなさそう)。

executorをdockerに変更

CircleCIのexecutorをmachineからdockerに変えるところからやった。

開発ではdocker-composeを使い続けたいが、CIではやめたいので、ENV['CI']があればという条件を加えていく。 例えば、Elasticsearchの接続条件をいじる場合はこんな感じ。

Elasticsearch::Model.client = case
                              when Rails.env.development?
                                Elasticsearch::Client.new(host: 'elasticsearch:9200/', log: true)
                              when Rails.env.test?
                                if ENV['CI']
                                  Elasticsearch::Client.new(host: 'localhost:9200/')
                                else
                                  Elasticsearch::Client.new(host: 'elasticsearch:9200/')
                                end
                              else
                                raise 'SEARCHBOX_URL not found.' unless ENV['SEARCHBOX_URL']
                                Elasticsearch::Client.new(host: ENV['SEARCHBOX_URL'], http: { port: 443, scheme: 'https' })
                              end

似たような感じで、どんどんif ENC['CI']を付けていったら、テストは動くようにはなったが、Elasticsearchが落ちるようになった。

落ちているElasticsearchを動かす

Elasticsearchはエラーコード137を出した落ちてた。つまりはOut Of Memoryなので、ES_JAVA_OPTS: -Xms256m -Xmx256mとか付けてみたり、増やしてみたりもしたが、どうにも不安定。machineの頃はCPU 2つ、メモリ7.5GBだったが、dockerになるとCPU 2つ、メモリ4GBになってるのを思い出した(デフォルトのmediumの場合)。

Configuring CircleCI - CircleCI

parallel_testsの並列数を4にしているのがマズいのかも…と思い、2に減らしたところ、Elasticsearchは安定して動くようになった。この時点でまだ落ちるテストはあったものの、24分程度に終わるようになった。

assets:precompileの結果をキャッシュ

webpackerを使っていると、env RAILS_ENV=test bin/rails webpacker:compileをしてからでないとテストが実行できなかったのだが、こいつが遅い。2分くらいかかる。事前準備のジョブの時点で、assets:precompileを行うようにして、./public/pack-testディレクトリをキャッシュするようにした。しかしJSファイルやライブラリの更新などがあった場合はキャッシュを破棄したいので、JS系ファイルのハッシュ値を集めたテキストファイルのハッシュ値を使うようにした。

やり方に関しては、この記事を参考にさせてもらった。

md5sum でディレクトリ単位のチェックサム計算等 - clock-up-blog

これをCircleCIのコマンドにしたら、こんな感じ。

commands:
  restore_packs_test:
    steps:
      - run:
          name: JavaScript Checksum
          command: |
            find app/javascript -type f -exec md5sum {} \; | sort -k 2 > javascript_checksums.txt
            find config/webpack -type f -exec md5sum {} \; | sort -k 2 >> javascript_checksums.txt
            md5sum package.json >> javascript_checksums.txt
            md5sum yarn.lock >> javascript_checksums.txt
            md5sum postcss.config.js >> javascript_checksums.txt
            md5sum babel.config.js >> javascript_checksums.txt
            md5sum .browserslistrc >> javascript_checksums.txt
            md5sum config/webpacker.yml >> javascript_checksums.txt
      - run:
          name: cat javascript_checksums.txt
          command: |
            cat javascript_checksums.txt
      - restore_cache:
          name: Restore ./public/packs-test
          key: packs_test-{{ arch }}-v{{ .Environment.YARN_CACHE_KEY }}-{{ checksum "javascript_checksums.txt" }}

  save_packs_test:
    steps:
      - save_cache:
          key: packs_test-{{ arch }}-v{{ .Environment.YARN_CACHE_KEY }}-{{ checksum "javascript_checksums.txt" }}
          paths:
            - ./public/packs-test
          when: always

jobs:
  generate_cache:
    executor: default
    parallelism: 1
    steps:
      # 色々あるけど省略
      - restore_packs_test
      - run:
          name: assets:precompile
          command: |
            if [ ! -d ./public/packs-test ]; then
              bin/rails assets:precompile
            fi
      - save_packs_test
      # 続く

これにより、JS系の変更がない場合はテストが2分近く短縮されるようになった。この時点で22分(とはいえ、テストはまだ落ちてたので本来のスピードではない)

resource_classをsmallに変更

落ちる原因が掴みきれずにいたのだが、ふと思い出して対応できた。

patorash.hatenablog.com

docker-composeの頃はdocker imageの時点で対応済みだったが、今はそうではないので、対応する処理を追加したらテストも通るようになった。

最初にこれの変更の時間を測っておけばよかったのだが、なかなか気づかずにとりあえずresource_classをsmallにするのをやりたかったので先にやってしまっていた。

resource_classをsmallにすると、CPUが1つ、メモリが2GBになる。しかし、使用クレジットは5になる(mediumは10)。 最初のほうで参考にしたスタディストのブログにもあったけれど、Railsのテストは大体I/Oが遅いので、マシンパワーが貧弱でも台数が多い方が速度が上がりそうだなと思っていた。

そこで、resource_classをsmallにして、parallelismを4から倍の8に増やした。これで、1分あたりのクレジット使用量は変わらない。

Elasticsearchが落ちる

するとまたElasticsearchが落ち始めた…。メモリが2GBになったせいか…。parallel_testsの並列数を1にしてみたら、安定した。しかし、もうそれはparallelではない!でも一応parallel_tests経由でテストを実行したところ、全部通った(他にもちょこちょこ直してはいたが)。これで、22分だった。全部通るようにはなったけれど、速度はあんまり変わらず。

あと、色々とelasticsearchのイメージの環境変数を設定していたので、それを晒しておく。不要な物もあるかもしれない…。

- image: patorash/elasticsearch-kuromoji:7.9.1
  environment:
    node.name: es01
    cluster.name: es-docker-cluster
# メモリがカツカツなのでスワップを有効にしたいのでコメントアウト
#     bootstrap.memory_lock: true
    bootstrap.system_call_filter: false
    ES_JAVA_OPTS: -Xms256m -Xmx256m
    TZ: /usr/share/zoneinfo/Asia/Tokyo
    transport.host: localhost
    network.host: 127.0.0.1
    http.port: 9200
    xpack.security.enabled: false
    discovery.type: single-node
    mem_limit: 256m
    memswap_limit: 1g

parallel_testsをやめる

parallel_testsの並列数を1にしてしまったので、もう並列じゃないし、外そうと思って、直接knapsack_pro経由でrspecを呼び出すように修正した。 parallel_testsを起動するオーバーヘッドがなくなる分、多少は速くなるだろうけれど、まぁ誤差の範囲だろうなぁとタカを括っていたら、かなり速くなった。17分!🚀

knapsack_proをやめてCircleCIのsplit-by=timingに戻したら、knapsack_proの代金を浮かせることができるから、久々にtimingに戻してみるかーと思って実験してみたけれど、案の定、22分〜24分かかるようになってしまったので、knapsack_proを使うように戻した。knapsack_proよくできてんな…😇😇😇

knapsack_proをご存知ない方はこちらの過去記事をどうぞ。

patorash.hatenablog.com

まとめ

executorでmachineを使ってのdocker-composeを使ったテストはあまり速度が出なかったのだが、dockerに戻したらかなり速くなった。

また、resource_classをsmallにしてparallelismを2倍にしたほうがトータルでは高速化できた。前処理時間が全部のコンテナにかかるので、どこかで頭打ちになるとは思うが、大量にメモリを消費するような処理がない場合は、デフォルトのmediumからsmallに変えてparallelismを倍にするだけで高速化出来そう。

そして、CIにおいて高速化=節約に繋がる。40分かかっていたのが17分になったので、半分以上の高速化で、その分コストカット💵できた。CircleCIを使っているRailsプロジェクトであれば、resource_classを下げて並列数を増やすのが得策と思われる。早速社内でも横展開していく!

WSLでLocalhostForwardingが効かない場合は高速スタートアップをオフにしよう

タイトルで全てを語ってしまいましたが、これです。

WSL2でRailsアプリの動作確認をしようとbin/rails sを実行後、 http://localhost:3000 にアクセスしたのですが、何故か表示されず…。pumaも起動していますが、ポートフォワーディングに失敗しているようです。.wslconfigには、LocalhostForwarding=Trueを設定してあるのに…。

ググったところ、close済みのissueにたどり着きました。

github.com

このコメントの途中に、「高速スタートアップをオフにしろ」と書いてありました。やり方は以下のサイトにありますが、英語です。

https://www.tenforums.com/tutorials/4189-turn-off-fast-startup-windows-10-a.html

高速スタートアップをオフにする方法

まず、コントロールパネルを開きます。検索から「コントロールパネル」と書けば出てきますからクリックしましょう。因みに私はタスクバーを上に持ってくる派です。(Macと同じようにしたい)

f:id:patorash:20210115022209p:plain

コントロールパネルを開いたら、システムとセキュリティをクリックします。

f:id:patorash:20210115022400p:plain

電源オプションの、電源ボタンの動作の変更をクリックします。

f:id:patorash:20210115022514p:plain

シャットダウン設定の、「高速スタートアップを有効にする(推奨)」にチェックが入っていますが、このままだと変更できないので、「現在利用可能ではない設定を変更します」をクリックします。

f:id:patorash:20210115022644p:plain

「高速スタートアップを有効にする(推奨)」のチェックを外して、変更の保存をクリックします。

f:id:patorash:20210115022917p:plain

これで完了です。PCを再起動してから、WSL2のUbuntuを起動し、rails sをしたところ、ちゃんと表示されました🎉

f:id:patorash:20210115023359p:plain

よかったよかった。

AWS SDK for Rubyでminio上のバケットを削除するときのTips

自分のための備忘録です。

seed-fuを使ってデータ投入していたのだが、データを作り直そうと思ってseed-fuを再び実行したところ、minioのバケットを作るところでコケた。原因は、既にバケットがあったからだった。

require 'aws-sdk-s3'

storage_yml = YAML.safe_load(ERB.new(File.read(Rails.root.join('config', 'storage.yml'))).result).fetch('minio')
bucket_name = storage_yml.delete('bucket')

# 既にバケットがある場合はNGだった…
Aws::S3::Client.new(
  access_key_id: storage_yml['access_key_id'],
  secret_access_key: storage_yml['secret_access_key'],
  region: storage_yml['region'],
  endpoint: storage_yml['endpoint'],
  force_path_style: storage_yml['force_path_style']
).tap { |s3_client| s3_client.create_bucket(bucket: bucket_name) }

なので、AWS SDK for Rubyを使ってバケットを削除しようとしたのだが、またコケた…。バケットにファイルが残っていたらバケットは消せないらしい。ファイルごと消せるかと思いきや、現時点ではそんなオプションはなかった。

# 省略
).tap do |s3_client|
  # バケットにファイルがある場合は削除できず…
  if s3_client.list_buckets.buckets.any? { |bucket| bucket.name == bucket_name }
    s3_client.delete_bucket(bucket: bucket_name)
  end
  s3_client.create_bucket(bucket: bucket_name)
end

なので!バケットにあるファイルを全部削除してからバケットを削除しようとしたのだが、またコケた!

# 省略
).tap do |s3_client|
  if s3_client.list_buckets.buckets.any? { |bucket| bucket.name == bucket_name }
    # バケット内のファイルを削除
    s3_client.list_objects(bucket: bucket_name).contents.each do |object|
      s3_client.delete_object(bucket: bucket_name, key: object.key)
    end
    # 何故かまだバケットが削除できない!?
    s3_client.delete_bucket(bucket: bucket_name)
  end
  s3_client.create_bucket(bucket: bucket_name)
end

削除できない原因

原因はシンプルで、まだバケットにファイルが残っていたからだった。list_objectsメソッドで取得できるオブジェクトの数の上限が1,000だったので、1,000個以上ファイルがバケットにある場合は消しきれない。ということは、ファイルを全部消すまでループするようにしなければならない。

削除できるように直す

最初の実装

シンプルにこうした。

# 省略
).tap do |s3_client|
  if s3_client.list_buckets.buckets.any? { |bucket| bucket.name == bucket_name }
    # コンテンツが有る限り削除し続ける
    while s3_client.list_objects(bucket: bucket_name).contents.present?
      s3_client.list_objects(bucket: bucket_name).contents.each do |object|
        s3_client.delete_object(bucket: bucket_name, key: object.key)
      end
    end
    s3_client.delete_bucket(bucket: bucket_name)
  end
  s3_client.create_bucket(bucket: bucket_name)
end

しかし、これだとファイルが大量にある場合はminioに都度ファイルの有無を確認する通信が発生するので、ちょっとダサいなと感じた。

リファクタリング

begin .. end whileを使って書き直した。これならば、minioに通信する回数は最小限となって効率がよい。

# 省略
).tap do |s3_client|
  if s3_client.list_buckets.buckets.any? { |bucket| bucket.name == bucket_name }
    contents = nil
    begin
      contents&.each do |object|
        s3_client.delete_object(bucket: bucket_name, key: object.key)
      end
      contents = s3_client.list_objects(bucket: bucket_name).contents
    end while contents.present?
    s3_client.delete_bucket(bucket: bucket_name)
  end
  s3_client.create_bucket(bucket: bucket_name)
end

まとめ

  • バケットを消すにはファイルを全削除しなければならない
  • list_objectsメソッドは1,000件しか取れないので注意
  • AWS CLIだったらファイルごとバケットを削除可能らしい(同僚に教えてもらった)

バケットを削除するか空にする - Amazon Simple Storage Service

ちなみにminioでAWS CLIを使う時の情報はこちら

MinIO | AWS CLI with MinIO - Cookbook/Recipe

PowerAutomateで毎月第2水曜日にTeamsで通知するやつを作った。

まだちゃんと動くか検証できてないですけど、これをやったので、その実装の話を書いておこうと思う。まぁメモです。

PowerAutomateの繰り返しは、しょぼい

Teamsの会議の予定は、繰り返しでカスタムを選択すると、毎月第3水曜日に会議を予約することができる。いいですね。

f:id:patorash:20210106132254p:plain
Teamsの予定表は第3水曜日の指定が可能

しかし、PowerAutomateの繰り返しは、それができない…。

f:id:patorash:20210106132437p:plain
PowerAutomateでは、第3水曜日とか指定できない

毎週水曜日とか、隔週水曜日とか3週間毎とかはできるんだけれど、第3水曜日とかそういう指定ができない。

f:id:patorash:20210106132611p:plain
できても、何週間毎とか…。

しかし、やりたい。

ロジックを考える

やりたいことは、「第3水曜日に勉強会を行うので、その1週間前に勉強会の開催の告知をしたい」なのだが、どうすればいいかわからなかったので、ググったら、良いヒントが。

[Power Automate] 月末(の最終営業日)にリマインダーを送るmatkjin8.wordpress.com

この記事の中で

[遅延]アクションを使うと、指定した期間フローを待機(一時停止)させることができる

というのがあり、これだ!!と思い、計算に入ります。

第2水曜日になりうる日付を抽出する

月初の1日がどの曜日になるかで決まります。

  • 日曜日だった場合、11日
  • 月曜日だった場合、10日
  • 火曜日だった場合、9日
  • 水曜日だった場合、8日
  • 木曜日だった場合、14日
  • 金曜日だった場合、13日
  • 土曜日だった場合、12日

という感じで考えると、8〜14日になります。そのため、PowerAutomateの起動日は毎月8日に設定します。

f:id:patorash:20210106140520p:plain
毎月8日の12:00に起動。

次に、何日待つかを扱う変数を初期化します。

f:id:patorash:20210106140811p:plain

次に、月初を取得して変数startDayofMonthに格納します。値には、式にして startOfMonth(convertFromUtc(utcNow(), 'Tokyo Standard Time')) を入れます。 f:id:patorash:20210106140854p:plain

次に、月初の曜日を取得して変数weekdayに格納します。値には、式にして、 dayOfWeek(variables('startDayOfMonth')) を入れます。

f:id:patorash:20210106141040p:plain

変数weekdayには、日曜日ならば0、月曜日ならば1という感じで、最大6までの整数が入ってます。では、何日待てばいいでしょうか?

月初の曜日 weekday 第2水曜日 何日待つか
日曜日 0 11日 3日
月曜日 1 10日 2日
火曜日 2 9日 1日
水曜日 3 8日 0日
木曜日 4 14日 6日
金曜日 5 13日 5日
土曜日 6 12日 4日

単純計算で出せるかと思ったけれど、なんかいいのが思いつかなくて、

  • 日〜水曜日までならば、 3 - weekday = 待つ日数
  • 木〜土曜日までならば、10 - weekday = 待つ日数

でいいかなと思ったので、条件を追加。

f:id:patorash:20210106142600p:plain

はいの場合は、3 - weekdayにするので、sub関数を使って変数daysToWaitに値を設定。式には、 sub(3, variables('weekday')) を入れます。

f:id:patorash:20210106142723p:plain

いいえの場合は、10 - weekdayにするので、sub関数を使って変数daysToWaitに値を設定。式には、 sub(10, variables('weekday')) を入れます。

f:id:patorash:20210106142829p:plain

これで何日待つかは決まったので、処理を遅延させます。変数daysToWaitの日数だけ遅延させます。

f:id:patorash:20210106142959p:plain

2022-01-24 追記:2021-12に、変数daysToWaitが0のまま、「待ち時間」に設定したら、エラーが起きました。「待ち時間」には1以上を設定しなければなりません。

f:id:patorash:20220124140023p:plain
daysToWaitが0の場合を考慮して条件分岐させる

あとは、Teamsでメッセージを投稿するようにしました。

まだ動いてないのでなんとも言えませんが、今月でいえば13日になって通知が来たら成功です。

ちゃんと動いてくれています。よかった!

まとめ

遅延っていうやつは便利そう。