OmniAICreator/Qiita-1.07M huggingface dataset:
HubspotとSendgrid連携話

# 初めに
株式会社エアークローゼットでエンジニアをしているCloudと申します。

この記事は[エアークローゼットアドベントカレンダー](https://qiita.com/advent-calendar/2022/air-closet)2022の14日目の記事になります。

今回はHubspotとSendgridの連携方法を紹介したいと思います。
メールを管理する時、役に立つかもしれないので、ぜひ最後までご覧ください。

# 現状
現在エアークローゼットはメールサーバとしてsendgridを利用しています。
ただsendgridの送信履歴ではメールの本文の確認ができないため、今回送信したメール内容をhubspotに連携させることになりました。

本題に入る前に、hubspotとSendgridについてちょっと説明します。

# 概念
### Hubspot
Hubspot（ハブスポット）とは、2005年にアメリカで開発されたインバウンドマーケティングのプラットフォームです。
Hubspotで顧客との関係性を管理し、ユーザーデータも管理します。特に、メールを送信したり、メールログが確認できるので、お客様管理課題が解決できます。
以下の図を見たら、詳しく理解できます。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323935373337322f64353432646566662d633563362d643930652d363963332d6336356461656431336361652e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957372/48fd0afa-5035-f242-6c8f-b451745a52c6.png)

### Sengrid
SendGridは全世界で利用されているメール配信サービスです。クラウドサービスのためアカウントを作成するだけで即日メールを送信でき、面倒でコストのかかるメールサーバの構築は不要です。
![Screenshot 2022-12-13 at 11.26.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957372/02a0b716-25a6-bf30-7e5d-ae5bc5d9ebfc.png)

# 実装

現在Sendgridのメールテンプレート機能を使ってメール本文を生成していますが、SendgridのAPIではメール送信がセットになっており、メール本文のみを取得することができません。
そのため、今回はSendgridからメールテンプレート自体を取得し、それをSendgridと同様の方法でコンパイルすることでメール本文を生成することにしました。
Sendgridのメールテンプレートはhandlebarsのため、handlebarsを使うことで同じメール本文を作ることができます。
メールテンプレートの例えば：
```html
<html>
  <head>
    <title>エアークローゼット</title>
  </head>
  <body>
   {{#each dataTemplates}}
     {{ this.name }}様
     {{ this.desc }}<br/>
     {{#equals this.name "c"}}
     {{/equals}}
   {{/each}}
  </body>
</html>
```
### HubSpotへの連携
HubSpotに連携を行えばその他のお客様のやり取りと合わせてシステムメールの履歴閲覧も行うことができるため、SendGridから送信しているメール本文をHubSpotのカスタムタイムラインイベントAPIを利用して登録します。

イベントを作成するNode.jsのコード：
```javascript
try {
  const apiResponse = await hubspotClient.crm.timeline.templatesApi.create(appId, TimelineEventTemplateCreateRequest);
  console.log(JSON.stringify(apiResponse.body, null, 2));
} catch (e) {
  e.message === 'HTTP request failed'
    ? console.error(JSON.stringify(e.response, null, 2))
    : console.error(e)
}
```
##### Handlebars
Handlebars.jsはMustacheのスーパーセットとして誕生したJavaScriptで使用されるテンプレートエンジンです。シンプルで分かりやすく軽量であることからストレスなく使用できるということで人気を集めています。またクライアントサーバ、サーバサイドの両方で対応しているという特徴があります。
Handlebarsの使い方
```javascript
// Handlebars.jsを読み込む
// jQueryとかライブラリを読み込む

const values = {
  title: 'Hello Handlebars!',
  img: {
    url: 'http://example.com',
    alt: 'Something..'
  },
  text: 'My first Handlebars!'
};
const template = Handlebars.compile($('#input').html());

$('#output').html(template(values));
```

### 解決
最初はHubspot側の設定を行います。
次にメール本文を取得する処理も追加します。
最後にお客様にメール送信する時、タイムラインイベントを通じてhubspotに登録する処理をコードに追加します。

そうすることでSendGridとHubSpotの連携をすることができました。
 #### 注意
Hubspotとhandlebarsのライブラリを使うためノードバージョンはv14.13.0以上必要です。
Hubspotを送信する時、TEN_SECOND_ROLLINGエラーが発生する可能性があります。
# あとがき
最後までご覧いただきありがとうございました。
エアークローゼット [Advent Calendar 2022](https://qiita.com/advent-calendar/2022/air-closet)はまだ続きますので、興味あれば、ぜひ他のエンジニア、PMの記事もご覧いただければと思います。
【O】Ō←これを入力したい

僕はローマ字テーブルに入れてます。

## これは何

マクロン付き文字とか言われるらしいです。
駅名などの地名表記でよく使われているイメージ

## どこにある

|  | 小文字+マクロン | 大文字+マクロン |
|:----:|:----:|:----:|
| a | U+0101 | U+0100 |
| i | U+012B | U+012A |
| u | U+016B | U+016A |
| e | U+0113 | U+0112 |
| o | U+014D | U+014C |

## 出し方

- Google IMEなら日本語入力状態で`U+XXXX`で変換すると出せます。他のIMEは知らん
- 「マクロン」で変換すればほとんどの環境で出ると思います。
[VSCode]同じ単語を一括編集する技をお前に授ける

# 基本
検索したい単語を囲い、`ctrl`+`D`でひとつずつ選択できる
この時カーソルは単語の後ろにつくため、選択するときは**前→後ろ**にカーソルを移動させるとよいことをお前に教える

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883146/4615e00e-348d-99e0-befd-025331c2d64e.png)

# 全選択
単語をまとめてすべて編集したい場合の方法をお前に教える
このとき、**変数名**とそれ以外（文字列など）で使う方法を分けると効果的だぞ

## 変数名
このようなテストコードを想定する
```リネーム前.py
hoge = "hogehuga"

print(hoge)
print("hoge")
```

変数にカーソルを合わせて`F2`でリネームができる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883146/9fc11051-eb78-d809-fe5e-898b9a09ea63.png)

F2でリネームした場合は、同じ文字列を**変数名として利用している**部分のみを置き換えることをお前に教える

```リネーム後.py
huga = "hogehuga"

print(huga)
print("hoge")
```

# 文字列
検索したい文字列を範囲指定して`ctrl`+`F`を押すと、単語検索機能が立ち上がる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883146/7dfa7c08-f44c-f6fe-5aef-322aeda2fddb.png)

文字列と変数の区別方法は（全世界インターネットを探し回ったけど）見つからなかったため、ここでは視認で区別する方法をお前に授ける
置き換えたい部分の後ろにカーソルを置き`Alt`+`LClick`でカーソルを追加する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883146/4f5fc6e3-1f3b-e62e-d178-7ecda75de4cc.png)

あとは適宜編集すればいいことをお前に教える
terraformで複数リソースを作成し、そのリソースを取得するメモ

## 概要
- 複数リソースの作成と、作成したリソースへのアタッチを行う
    - サンプルとして複数のサブネットを作成し、そのサブネットに対してnatゲートウェイをアタッチする

## コード
```main.tf
# VPCのCIDR設定 (default のIPアドレスを設定している)
variable "vpc_cidr" {
  default = "10.0.0.0/16"
}

# Subnet
variable "public_subnet_cidrs" {
  default = ["10.0.0.0/24","10.0.1.0/24"]
}

#vpc
resource "aws_vpc" "vpc" {
  cidr_block = var.vpc_cidr
  enable_dns_hostnames = true  # DNS解決を有効化
  enable_dns_support   = true  # DNSホスト名を有効化
  instance_tenancy     = "default" #インスタンスがホスト上で共有されるように

  tags = {
    Name = "sample-vpc"
  }
}

#subnet(public)
resource "aws_subnet" "public" {
  #複数作れるようにしとく
  count = length(var.public_subnet_cidrs)

  cidr_block = var.public_subnet_cidrs[count.index]
  vpc_id     = aws_vpc.vpc.id

  tags = {
    Name = "sample-public-${count.index}"
  }
}

#Public NAT
resource "aws_nat_gateway" "public_nat" {
  count = length(aws_subnet.public)
  connectivity_type = "public"
  subnet_id = element(aws_subnet.public.*.id, count.index)
}
```
```.tf
count = length(aws_subnet.public)
subnet_id = element(aws_subnet.public.*.id, count.index)
```
この辺のコードが今回のポイントとなる部分ですね。
terraformむずいなぁ
リモートサーバ（パスワード認証）でVSCodeのDev Containersの利用を試みた際に遭遇した問題

# はじめに
リモートアクセスした先のサーバでVSCode拡張機能のDev Containersでコンテナにアクセスしようとしたのですが、上手くアクセスできない現象が発生しました。その時の対応の記録です。


![ss004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724847/a1c5e146-d78a-a625-13ba-f86f5548ec1e.png)



# 問題発生状況
環境としては以下になります。
- サーバ: Ubuntu Server 20.04 LTS
- PC: Windows 11

通常、サーバのSSHアクセスには公開鍵認証が設定されるのが一般的かと思いますが、この環境はとある事情でパスワード認証が使われていました。この環境に対して、VSCodeのRemote - SSHを使ってサーバにアクセスし、そこからDev Containersでコンテナにアクセスしようと試みたら正しく動いてくれませんでした。

具体的には、Dev Containersの`Dev Containers: Reopen in Container`を呼び出すと、いつまでたっても処理が先に進みません。`Setting Up Dev Containers (show log)`でログを確認すると、下図のようなメッセージのみが表示され、そこからずっと待ちの状態でした。


![ss001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724847/625e8775-862c-4be7-6679-4b371a34d609.png)


# 問題回避方法
これは、Dev Containerのバージョンの問題でした。上記の問題が発生したときのDev Containers拡張機能のバージョンはv0.255.4でしたが、これをv0.262.3に上げることで解消できました。バージョンを上げて実行すると、下図のようにパスワードを入力するコマンドパレットが表示されるようになります。


![ss002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724847/bc24c07f-9071-cd01-9b9a-b1a41af7b74c.png)


処理が先に進まなかったのは、認証（パスワード入力）が必要なポイントでパスワード入力するコマンドパレットが表示されず何もできない状態だったと考えられます。


# 後日談
単にバージョンアップすればよいのかと思っていたのですが、これには続きがあります。つい先日、拡張機能の新しバージョンが出たのですが、それを使ったら類似の現象が発生しました。  
現象としては、`Dev Containers: Reopen in Container`を呼び出すと、`Setting Up Dev Containers (show log)`の表示も消えてしまします。そして、再度`Dev Containers: Reopen in Container`を呼び出すとポップアップでエラーメッセージが表示されました。


![ss003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724847/a3ce7d12-e1e4-a217-c8ed-0507a8cb5c81.png)


ポップアップの最後の方に`Permission denied (publickey,passowrd).`というエラーメッセージが記載されていたので、原因は同じと考えられます。

これに関しては、拡張機能のバージョンをダウングレードすることで対応できました。現時点（2022年12月）では、**v0.262.3 ～ v0.265.0** で問題が起きないことを確認しています。


# おわりに
この現象は、パスワード認証を行っているサーバにリモートアクセスして、そこでDev Containersを利用しようとした場合に見られるものです。公開鍵認証の場合にはこの現象は発生しません。
Windows 11 で言語切り替えショートカットを無効にする方法【Windows 11 22H2版～】

毎度、ググっても出てこない小ネタを取り扱っております。
本記事は個人的な見解であり、筆者の所属するいかなる団体にも関係ございません。

言語切り替えショートカットとは、「ctrl+shift」でIMEが切り替わるショートカットのことです。

Windows 10 で言語切り替えショートカットを無効にする方法【windows10 1803版】 - Qiita
https://qiita.com/ynott/items/5d8fde54e7c3a66b7af3

Windows 10 で言語切り替えショートカットを無効にする方法【Windows 10 1810版～】 - Qiita
https://qiita.com/ynott/items/5c97453fcc9bdf845636

Windows 10 で言語切り替えショートカットを無効にする方法【Windows 10 1909版～】 - Qiita
https://qiita.com/ynott/items/eca99ed763752316a128

Windows 10 で言語切り替えショートカットを無効にする方法【Windows 10 21H1版～】 - Qiita
https://qiita.com/ynott/items/d2233c79bfd40854a3ec

# 0. 前提条件

Windows 11は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/9377c52b-47c9-dea8-03ae-3c7e2f8157bf.png)

## 1. コントロールパネルを開いて「時刻と言語」を選択(変更なし)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/52bf7ebc-f677-f1dc-dd44-ee337c32f6e8.png)

## 2. 時刻と言語で「入力」を選択(変更あり)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/347f4759-48c3-1cd3-e4a1-c957976a3e41.png)

## 3. 「キーボードの詳細設定」を選択(変更あり)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/75cfe887-191e-f0ce-b615-6a2971dc193b.png)

## 4. キーボードの詳細設定で「入力言語のホットキー」を選択(変更なし)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/df38c7c2-b171-7ed3-15e7-15d00cfa4e93.png)

## 5. キー詳細設定で「キーシーケンスを変更」をクリック(変更なし)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/daccaeec-1772-86d7-1dba-96f43d1aff3b.png)

## 6. キーシーケンス変更で「割り当てなし」をクリック(変更なし)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15176/6dde0fea-b36d-18d7-e956-684cee5e3c97.png)

OKを押して終了

## 7. まとめ

2と3が変わりました。
PythonエンジニアがSwiftでググれないエラーに悩まされた話

結論から言うと、言語仕様の違いによるうっかりミスでした。
同様の事象をググるのが難しかったので、一例として残しておきます。

# 発生した問題
```swift
Observable.just("太郎")
  .flatMap { [weak self] fristName -> String? in
      guard let self = self else { return }
      self.getLastName(of: firstName)
  }
  .filter { $0 != nil }
  .subscribe(onNext: { lastName in
      print(lastName)
  })
```
上記のようなコードをXCodeでビルドすると、`Cannot convert return expression of type '()' to return type 'String'` というエラーが、**このコードを含む箇所以外で大量に発生**しました。
エラーとして掲示されている箇所は、共通して戻り値が定義されていない関数のようでした。
ビルドエラーが出ている箇所をエディターで開くと、何事もなくビルドの項目から何事もなく消えました。
しかし、再度ビルドすると、消えたはずのエラーが復活してしまいました。
該当のコードをコメントアウトすると、ビルドが成功するので、ビルドエラーの原因はこのコードであることは間違いなさそうでした。

エラーメッセージをググると構文エラーであるのは分かりましたが、同様の事象を見つけることはできませんでした。

# 原因

```swift
guard let self = self else { return }
```
を以下のように修正しました。

```swift
guard let self = self else { return nil }
```

Swiftでは戻り値の指定がない場合は`Void()`が返るのを失念していました。
普段はPythonを書くことが多く、 Pythonでは戻り値の指定がない場合は`nil`に相当する`None`が返ることに慣れているのでなかなか気づけませんでした。
`return`したら`nil`が返るから、戻り値の型`String?`に合致すると高を括っていました。
エラーメッセージに`()`があるので冷静に考えれば、`Void()`に関係することは気付けたかもしれません。

とはいえ、ビルドエラーの発生箇所をコンパイラには正しく示してほしいものです。
Swiftに日頃から慣れ親しんでいる人であれば、こんなエラーにあたふたすることはないのでしょう。
PythonだけでなくRubyなどの言語になれたエンジニアも同様なので、参考になれば幸いです。
Growiにlocalhost以外からアクセスできるようにする方法

# はじめに
https://qiita.com/yuu__dee/items/324051d4814433dc8d76
この記事ではCentOSにGrowiのv5.xを構築する手順を紹介しましたがこのままではローカルホストからしかアクセスできないでのローカルホスト以外からでもアクセスできるようにしたいと思います。

# ポートの開放
Growiでは3000番ポートでつながるようになっているので3000番ポートを開ける必要があります。
やり方は簡単でfirewallの設定から3000番ポートを開けるだけです.

```
firewall-cmd --zone=public --add-port=3000/tcp --permanent
```
これで永続的にtcp/3000ポートが解放される状態になります。

最後にfirewallの変更後の状態を確認
```
firewall-cmd --list-all

ports: 3000/tcp
```
となっていることを確認しましょう.
リモートアクセスで困った話 (2)

謎のアドベントカレンダー、第 17 話です。

前日の、

- **[リモートアクセスで困った話 (1)](https://qiita.com/hmatsu47/items/84f23351fb21e89bae70)**

https://qiita.com/hmatsu47/items/84f23351fb21e89bae70

に続き、今度は割と最近の話です。

AWS の Client VPN エンドポイントを 2 回削除して 3 回作った話です。

（テストを含めるとそれぞれ +1 回かな？）

## 1 回目 : 手作業で作成

筆者の職場では、AWS 上の開発環境のマシンがあります。

そして、（当初は）ここに、障害対応時に自宅などから接続することを考えました。

（というか、AWS に引っ越す前のオンプレ時代にも似たような構成で運用していて、AWS に引っ越すのを機に接続方法を考えた、という流れです）

もちろんここに SSH なり RDP なりで接続しても良いのですが（いまなら「ブラウザから接続」もありますね。Windows だと日本語入力が不自由ですけど）、パブリックアクセス大開放！というのもちょっとアレなので、Client VPN を噛ませて接続することにしました。

Client VPN が出た当初は CloudFormation 未対応で、本運用に入るギリギリまで待ちましたが対応しなかったので諦めてマネジメントコンソールからポチポチやって作りました。

そして概要を **[JAWS-UG 名古屋](https://jawsug-nagoya.doorkeeper.jp/)** で発表しました。

<script async class="speakerdeck-embed" data-id="54a2f84784924ed88509f74813c3653c" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>

で、本運用に入ろうとしたまさにそのとき、CloudFormation 対応に。

## 2 回目 : CloudFormation で作成

手作業で作成したエンドポイントを削除しつつ CloudFormation で作成しました。

おかげで、メンバー用に用意した接続用のファイルを、すべて再ダウンロード→一部加筆修正→再配布する羽目に。

この時点では、通信料金が高額になるのを恐れて VPN 接続時のインターネットへのルーティングは無効にしました。

（スプリットトンネルはまだサポートされていませんでした）

VPN 接続時に手元のパソコンから直接インターネットに出ていけないので多少不便ですが、VPN は障害対応時ぐらいしか使わないので「このままで良いか」となりました。

そんなこんなで月日は流れ…コロナがやってきました。

自宅から仕事をするために、Client VPN の利用者を「障害対応する人たち」から「開発メンバー全員」に拡大しました。

その際、試しに既設のエンドポイントでスプリットトンネルを有効にしてみたのですが、肝心の開発環境のマシンに繋がらなくなったので、一旦諦めました。

ちょっと不便でしたが「手元で直接インターネットにアクセスしたいときは一旦 VPN を切る」ルールにしました。

（運用でカバー）

…まあ、面倒ですね。

というわけで、

## 3 回目 : CloudFormation で作成（スプリットトンネル有効化）

また作り直しました。

当然、メンバー用に用意した接続用のファイルを、すべて再ダウンロード→一部加筆修正→再配布する羽目に（2 回目）。

手間は掛かったものの、みんな喜んでくれました。

## めでたしめでたし…ではなかった

後日、消さずに残しておいたスプリットトンネル未対応のエンドポイントを消すタイミングで、何気なく

- スプリットトンネル有効化
- 接続用のファイルを再ダウンロード→一部加筆修正

して VPN 接続してみたところ、 **インターネットにも開発環境マシンにもあっさり繋がって通信できました。**

要は、 **エンドポイントを作り直さずとも、スプリットトンネル有効化後に接続用ファイルを再ダウンロードすれば良かった** わけです。

そのあたりについては、[こちらの記事](https://qiita.com/hmatsu47/items/ce0cc8b7f5747dbb3190)にも後から加筆修正する形で残しています。

- **[AWS Client VPN 設定メモ](https://qiita.com/hmatsu47/items/ce0cc8b7f5747dbb3190)**

https://qiita.com/hmatsu47/items/ce0cc8b7f5747dbb3190

まあ、いずれにしてもメンバーへの接続ファイル再配布は必要だったわけで、無駄だったのは「新規エンドポイントを生やすこと」ぐらいではあったのですが。

o...rz

---

18 日目の記事に続きます。

- **[インターネット接続で困った話](https://qiita.com/hmatsu47/items/0c4ac2e364ec03eda530)**

https://qiita.com/hmatsu47/items/0c4ac2e364ec03eda530
Windows10にSSHサーバー設定

# 概要

Windows10の検証機がある。
その検証機の操作をするのだが、いちいちリモートデスクトップするのが面倒。
SSH接続したい。

# やり方（接続される方）

## Open SSHサーバーインストール

アプリ 。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/1060957c-97ec-0ae6-c201-5a8d9c0a6b94.png)

アプリと機能 ⇒ オプション機能 。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/22fadf29-df76-46c9-9a11-e46082fda746.png)

機能の追加 。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/7a7a07b7-9cd9-fa46-9e8a-9bfbd7bb6b2e.png)

Open SSHサーバー にチェック入れてインストール。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/06f17138-04ce-83cc-c721-02531fc49ed6.png)

## オプション機能が見つからない時は

下記にあるかも。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/7e9a3c9f-194b-c947-c767-fa0dfb8cdaf3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/d0cdb8c9-0996-da2b-8531-6d0ea9fc3d23.png)

## サービス設定

Windows10起動した時にSSHサーバーのサービスも起動するように設定します。

サービス管理ツールは下記経由で行くのが覚えやすい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/ba6b9933-7fac-9f98-86dd-8bcce44253bd.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/36fa7552-9c1a-1ee9-0d90-8979749c9bc8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/8f90ea2c-60a7-9d5f-f31f-af6fb718a2c9.png)

# 接続確認

PowerShell開いて…

```ssh 接続ユーザー名@端末名 or IPアドレス```

『接続ユーザー名』はいつもリモートデスクトップしているユーザー名でよし。

```powershell:コマンド例
ssh akkarin@192.168.10.10
```

パスワード求められる。
（リモートデスクトップする時と同様）

ドメインAdministratorで入れない時はユーザー名を ```ドメイン名\administrator``` に。
（administratorはローカルとドメインの2種類あるので、どちらなのかをコマンドで指定してあげる）

SSH接続切る時は ```exit``` コマンド。

# 参考サイトさん

http://ttm.jimba.ddo.jp/adiary.cgi/teraterm/windows/w10sshsv

# バージョン

Windows 10 Pro 21H2 OSビルド 19045.2311
【AWS】ECRでリポジトリをつくったものの、プッシュコマンドでエラーが出た件について

ECRを触っていたらプッシュコマンドで全然進まなかったのでメモ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884542/e3ca12be-b600-abc4-a370-379848a81c1f.png)

まずここの1番、powershellで入力してもエラーで動かないです

どうやら`(Get-ECRLoginCommand).Password`というコマンドがそもそも使えないらしい？
そもそもAWSPowerShellを入れないとたぶん実行できない？

[公式のページ](https://docs.aws.amazon.com/ja_jp/powershell/latest/userguide/pstools-getting-set-up-windows.html#ps-installing-awswindowspowershell)と[こちら](https://yoshinorin.net/articles/2018/08/19/aws-tools-for-windows/)の記事を参考に進める


1. `Install-Module -Name AWSPowerShell`でモジュールのインストール
1. スクリプトの有効化 `Set-ExecutionPolicy RemoteSigned`
1. インポート `Import-Module AWSPowerShell`

私は1番目のインストールでいろいろなことを試しすぎて、PowerShellフォルダ内がぐちゃぐちゃになっていて、エラーが出力されていました。
中身を一度消してから、1番から再度実行したところうまく動きました。

あとは認証をしていきましょう。
1.`Set-AWSCredential -AccessKey {key} -SecretKey {secretKey} -StoreAs {name}`
アクセスキーとシークレットキーを入力、名前は一意にします。
※アクセスキーとシークレットキーはIAMのユーザから作成することができます
　シークレットキーに関しては一度なくすとめんどくさそう（たぶん？）なので大切に保管しましょう

2. `Set-AWSCredentials -ProfileName {name}`
1番で入力した名前を入れていきます。

そして、最初の`(Get-ECRLoginCommand).Password`を実行すると
```
Login Succeeded
```
と出力されました。

ここまで来るのに結構時間がかかってしまいました。いろいろな可能性を潰してやっていたので途中めちゃくちゃになってしまいましたが、とりあえずうまくいって良かったです。
Azure Administrator(AZ-104)：Azure管理者向けの前提条件「PowerShellスクリプト(ループ：Do)」

Microsoft Learnを用いたMicrosoft Azure Administrator(AZ-104)の試験内容をまとめる投稿です。
この投稿ではMicrosoft Learnで学ぶ内容をできるだけ簡潔にまとめております。
Azureの勉強を一から始め、AZ-900取得後の資格としてAZ-104取得を目指している方の参考になれば幸いです。
※AZ-900の内容は本投稿では省略しております。再度復習したい方は下記をご参照ください。
[Azure Fundamentals(AZ-900)：試験合格に向けた学習内容のまとめ](https://qiita.com/fsd-jume/items/e4ec76959adb547ac5e8)

## 今回の学習内容
**◆「PowerShellスクリプト(ループ：Do)」**
◇対応するMicrosoft Learn ラーニングパス
[AZ-104:Azure 管理者向けの前提条件](https://learn.microsoft.com/ja-jp/training/paths/az-104-administrator-prerequisites/)
◇対応するモジュール
[Azure PowerShell でスクリプトを作成して保存する](https://learn.microsoft.com/ja-jp/training/modules/automate-azure-tasks-with-powershell/7-create-resource-using-script)

※前回の投稿は下記から確認できます。
[Azure Administrator(AZ-104)：Azure管理者向けの前提条件「PowerShellスクリプト(ループ：For)」](https://qiita.com/fsd-jume/items/62f907ab1dc2506dee69)

## PowerShellスクリプト(ループ：Do)
### Do-While文
**処理の実行後に条件式で判定**
・True：処理を再実行して再判定
・False：ループ終了
例)VMを3つ作成する際に、それぞれのリソース名に連番をつけたい場合
```powershell:PowerShell
$i = 1

do {
    $vmName = "ConferenceDemo" + $i
    Write-Host "Creating VM: " $vmName
    $i++
} while ($i -le 3)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2851723/f453457c-d325-ae3b-86ef-093438a40b1e.png)
**※コマンドの意味**
**・do {`Statement list`} while (`Condition`)**
⇒`Statement list`の処理後、`Condition`で判定を行い、結果がTrueの間はループ
　今回の場合はループするごとに変数`$i`が1ずつ加算され、`$i`が4になったときに判定結果が**False**になりループが終了
　-処理1回目：コンソールに出力 → `$i`が2になる → `$i`が3以下なのでループ
　-処理2回目：コンソールに出力 → `$i`が3になる → `$i`が3以下なのでループ
　-処理3回目：コンソールに出力 → `$i`が4になる → `$i`が3以下ではないのでループ終了

### Do-Until文
**処理の実行後に条件式で判定**
・True：ループ終了
・False：処理を再実行して再判定
例)VMを3つ作成する際に、それぞれのリソース名に連番をつけたい場合
```powershell:PowerShell
$i = 1

do {
    $vmName = "ConferenceDemo" + $i
    Write-Host "Creating VM: " $vmName
    $i++
} until ($i -ge 4)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2851723/8fa4f224-0085-90f1-d7db-bcf19884e3cb.png)
**※コマンドの意味**
**・do {`Statement list`} until (`Condition`)**
⇒`Statement list`の処理後、`Condition`で判定を行い、結果がTrueになるまでループ
　今回の場合はループするごとに変数`$i`が1ずつ加算され、`$i`が4になったときに判定結果が**True**になりループが終了
　-処理1回目：コンソールに出力 → `$i`が2になる → `$i`が4以上ではないのでループ
　-処理2回目：コンソールに出力 → `$i`が3になる → `$i`が4以上ではないのでループ
　-処理3回目：コンソールに出力 → `$i`が4になる → `$i`が4以上なのでループ終了

## 次の学習内容
**◆PowerShellスクリプト(パラメーター)**

https://qiita.com/fsd-jume/items/dba1485f0334610aa54a

## 学習内容をまとめたページの一覧

https://qiita.com/fsd-jume/items/6eda24728c9c03c36bc3
ESLintについて調べた内容

# ESLintとは何か
一言で言えば、EcmaScriptの財団で明示した仕様に反していないかをチェックしてくれるツールのこと。
ESLintは、コードのスタイルや規則に反するところを知らせてバグを産まないようにすることができる。
なお、コードのスタイルや規則は個人によって、または会社によって異なるケースもあるため、`.eslint`ファイルをもってカスタムすることも可能。

# 設定方法
やり方としては`package.json`に直接eslintの設定をする方法あり、`.eslint`ファイルに別途設定する方法がある。
もし、`.eslint`と`package.json`が両方存在する場合、`.eslint`が適用される。（内部的には`.eslint`が`package.json`にオーバーライディングされるらしい）

# `.eslint`の配置場所
監視したいファイルのルートディレクトリに保存すればOK。
```
your-project
├── .eslint
├── lib
└── source.js
```
上記のように`.eslint`ファイルを置くことで、`source.js`ファイルをeslintが見ることができる。
また、場合によっては`.eslint`ファイツを２箇所に置くことも可能。
```
your-project
├── .eslint1
├── lib
│ └── source.js
└─┬ tests
  ├── .eslint2
  └── test.js
```
上記の方法だと、`tests`フォルダーにある`test.js`ファイルは、`.eslint1`と`.eslint2`の両方の規則が適用され、重複するルールがある場合、一番近い`.eslint`の設定に従うことになる。

# `eslint`のインストール
基本的には以下のコマンドでインストールすることができる。
```
npm install -g eslint
```
すると、以下のようにインストールされる。
```terminal
$ npm install -g eslint
/usr/local/bin/eslint -> /usr/local/lib/node_modules/eslint/bin/eslint.js
+ eslint@7.15.0
added 109 packages from 64 contributors in 3.087s
```

インストールが終わったら、初期設定が必要である。
`eslint --init`のコマンドを叩くと、以下のようにいくつかの質問に答えることになるが、自分のプロジェクトに合うルールに答えて設定を完了させる。
```terminal
$ eslint --init
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · vue
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ What format do you want your config file to be in? · JSON
The config that you've selected requires the following dependencies:
...

eslint-plugin-vue@latest
✔ Would you like to install them now with npm? · No / Yes
Installing eslint-plugin-vue@latest
```

上記の設定が完了されると、以下のようにeslintの設定ファイルが生成される。
![sample_eslint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2785890/8c70a50d-deea-254f-a859-77bc4ec7268a.png)

- `env`: 使用する環境を意味する。
- `ectends`: 使用する拡張機能について記載する部分。
- `parserOptions`: バージョンとモジュールの内容を記載する部分
- `plugins`:使用するプラグインの記載する部分
- `rules`:ルールの詳細の設定を記載する部分。

# ESLintのルール
ESLintのルールは[こちらの公式サイト](https://eslint.org/docs/latest/)で確認することができる。
まず、公式ドキュメントにあるルールを適用させるためには、`extends`にある`exlint:recommended`の属性が追加されている必要である。
ルールはオブジェクトの形式でキーは属性名になり、値は`off`,`warn`,`error`の３つになる。内容としては以下の通りである。

- `"off"`: 該当のルールを適用しない
- `"warn"`: 該当のルールに反すると、警告を出す。（コードには影響を与えない）
- `"error"`: 該当のルールに反すると、エラーを吐き出すようにする

#### 例
```
{
    "rules": {
        "eqeqeq": "off",
        "curly": "error"
    }
}
```
jestとvitestではspyOnの振る舞いが違う？

jest.spyOn + esbuild/swcの相性問題を前回 [jest.spyOnでTypeError: Cannot redefine property](https://qiita.com/nakazavva/items/a1552527ce54a53c5c88) でお話しましたが、今回はjest + ts-jest → vitest移行でハマった話をしようと思います。

## TL;DR
- https://github.com/vitest-dev/vitest/issues/1329
- spyOnの振る舞いが違うのではなく、今回もesm→cjs環境におけるトランスパイラの違い
- クラスにするか、ファイルを分けるか、諦める

## 同一ファイルの別関数をモックできない
早速見ていきましょう。問題のコードはこれだ！わんつっすり（言ってみたかっただけ

```typescript
    // example.ts
    export const message = () => {
      return 'hi';
    };
    
    export const greet = () => {
      return message();
    };
    
    // example.test.ts
    import * as example from './example';
    
    vi.spyOn(example, 'message').mockImplementationOnce(() => 'hello');
    
    it('spyon', () => {
      expect(example.greet()).toBe('hello');
    });
```

このテストjest + ts-jestなら問題なく通ります。しかしながらvitestだと `example.greet()` は `hi` を返却するためコケます。期待に反して `message` をモックできていません。

ちなみにjest + esbuild/swcだと前回お話したように `TypeError: Cannot redefine property` になります。


## 何故か

https://github.com/vitest-dev/vitest/issues/1329#issuecomment-1129708816 に回答がありました。


> This is not about `spyOn`, but about how babel transforms typescript files.

コメントでvitest(esbuild)がどうトランスパイルしモックできない理由を書いていますが、tsc/babelのesm → cjsトランスパイル結果と比較してみたいと思います。

#### tsc v4.9.4 (babelも大体同じ)

    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.greet = exports.message = void 0;
    const message = () => {
        return 'hi';
    };
    exports.message = message;
    const greet = () => {
        return (0, exports.message)();　// exports.messageをコールしてる
    };
    exports.greet = greet;

#### esbuild v0.16.4 (swcも大体同じ)

    $ /private/tmp/esbuild/node_modules/.bin/esbuild --format=cjs index.ts
    ...省略
    var esbuild_exports = {};
    __export(esbuild_exports, {
      greet: () => greet,
      message: () => message
    });
    module.exports = __toCommonJS(esbuild_exports);
    const message = () => {
      return "hi";
    };
    const greet = () => {
      return message(); // standaloneのmessageをコールしている
    };

greetに注目してもらうと良くわかりますが、tsc/babelはexports.messageをコールしているのに対し
esbuild/swcではstandaloneのmessageをコールしています。spyOnはjestだろうがvitestだろうがexportされたものをモックに置き換えるだけなので、後者のケースではモックのしようがないことがわかります。


## じゃあどうするか
モックしたくなる対象として外部依存が多いと思います。なので引き続きモックが必要であればそれはファイルを分けたほうが適しているものだと考えて良いと思います。あとはgithubのコメントにもあったようにクラスにするのも一つの方法だと思いますが、モックのためにそこまでという気がします。


## おわりに
事業系のタスクをこなす日々で、こういった事象はとりあえず動くからいいやの状態にして回避することが多いですが、記事の題材とすると調べるモチベーションも湧くので内容の濃さはさておき良い機会だと思います。これからも知的欲求には従順でいたい。後一つ記事を担当するので今度は流行りのchatGPTに全部書いてもらいたいと思います。
BFFってなにそれ？美味しいの？


トレタのフロントエンドエンジニア武市です。

トレタではマイクロサービスを採択していることにより高い設計力が求められます。品質の高いソフトウェアを開発をする為に、求められる技術レベルがかなり高くなりました。前職ではモノリシックアーキテクチャだったこともあり、日々勉強する毎日です。

今回の記事ではトレタでも採択しているBackends For Frontends（以後BFF）について理解が不十分だったので勉強した内容を書いていきたいと思います。

## BFFって美味しいの？
先に結論を述べさせていただきますと、すごく美味しいです。
BFFを一度召していただくとBFFなしの開発は考えられない、もうBFFなしでは生きられない。
そう思うことでしょう。

## そもそもBFFとは

BFFとはアーキテクチャパターンの1つです。
フロントエンドとバックエンドの中間に[フロントエンド専用のサーバー]を配置するアーキテクチャ設計パターンです。
BFFの役割としては大きく分けると二つあります。

- 複数のサービスへのリクエストを集約する
- バックエンドから取得したデータを加工してフロントエンドに返す

このアーキテクチャを採用することで双方の複雑さが緩和されます。

## BFF登場までの背景
BFFはマイクロサービスアーキテクチャ化における課題を解決する為に生まれたアーキテクチャです。
1つのプロダクトを運用していると、どんどん機能や要件が増えていきます。サービスが増えるにつれAPIが増加、エンドポイントも増えることになります。Webアプリ,iOSアプリなどクライアントの端末が増えたことでレスポンスも複雑になります。

本来バックエンドとしての役割とはデータベースからデータの取得、更新、削除があり、
フロントエンドはユーザー体験を向上させる役割があります。
データの加工やAPIのルーティングなどはバックエンド、フロントエンドの本来の役割としては責務外となります。BFFを採用することでこれらの問題が解決されます

## BFFのメリット

- **初期表示の高速化**
    - クライアントのデバイス性能によっては大きく変わってしまう時間の掛かってしまう処理をBFFに持たせることで初期表示を高速化することができます。
- **エラーハンドリング**
    - アプリに不正なデータが入ってこないようBFFで制御することができます。
- **データの整形**
    - BFFにadapterを配置してフロントエンドに整形された状態で渡すことによりユーザー体験を向上させる役割だけに集中することができます。
- **APIの集約**
    - マイクロサービスアーキテクチャなど複数のバックエンドへリクエストをする際Proxyとしての役割を持たすことができるので、　クライアントからのエンドポイントは一つに集約することができます。

## まとめ

各プラットフォームが増える毎にBFFは増えていくのが基本的には望ましいが、冗長になります。そもそも役割が違うのでそれは許容するべきかなと感じるが。一つの体験につき一つの BFF (one experience, one BFF) という考え方もあるのでケースバイケースだなと感じました。

これからは日々勉強していることを発信していきたいと思います。
ありがとうございました。
.gitignoreまとめ

# .gitignoreとは？

・gitignoreとはGitで管理したくないファイル、ディレクトリを指定するもの
・本番環境と開発環境で値が変わる環境変数を持ったファイル（.env）や、自動生成されるもの（scssからコンパイルされたcssファイル等）、ログファイル等が主な除外対象

# .gitignoreの書き方
```
# ＃で始まる行はコメント

# 特定の拡張子ファイルを除外する
*.env
**/css/style.css
**/img/**/*.min.webp
**/img/**/*.min.svg
*.htaccess
*.htpasswd

# 特定のフォルダを全て除外する
node_modules/

# 特定のパターンを除外しない
!/.gitignore
```

# .gitignoreが反映されない時の対処法
#### 既にコミットされたことがあるファイルが対象になっている場合が多い
キャッシュにインデックスが残っているので、キャッシュを削除して上げれば解決します。
```
$ git rm -r --cached . //全体キャッシュ削除 
$ git rm -r --cached [ファイル名] //ファイル指定してキャッシュ削除
```
gitにいろいろ履歴が出てきますが実際にファイルが削除されるわけではないので問題ありません。
（例）キャッシュ削除して`node_modulesディレクトリ内全体`を除外した場合
![スクリーンショット 2022-12-12 17.07.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2987027/ad7a5b7a-1ef0-b779-fc4c-d6d723f07066.png)
辞書ソースのつかっていないカラムの話 その3

## はじめに

[Sudachi](https://github.com/WorksApplications/Sudachi) の辞書ソースにはつかわれていない、あるいは何につかわれているかがあまり説明されていないカラムがいくつかあります。何回かにわけてそういったカラムについて説明していきます。今回は小ネタです。([その1](https://qiita.com/kazuma-t/items/59f5ea7a3ff5e5fb41d1)、[その2](https://qiita.com/kazuma-t/items/c6b07b9f8b823324a71f))

## 14カラム目の話

今回は14カラム目 (0はじまりで) のお話です。このカラムはユーザー辞書の[ドキュメント](https://github.com/WorksApplications/Sudachi/blob/develop/docs/user_dict.md)には「分割タイプ」とあり、A、B、C、アスタリスク (*) いずれかの値を記述するように書かれています。このカラムのあと15カラム目が A 単位分割情報、16カラム目が B 単位分割情報になっています。それぞれじぶんがA単位に分割されたときの A 単位語のリスト、B 単位に分割されたときの B 単位語のリストです。

Sudachi の解析は分割されない状態でおこなわれます。べつのいい方をすると TRIE に登録されている語[^1]だけで辞書引きし、ラティスを作成、最適なパスが選ばれます。この最適パスとしてえられた形態素列が C 単位モードの出力結果になります[^2]。

[^1]: 左連接 ID に-1を指定すると TRIE から除外されます。
[^2]: 厳密にいうと最適パスにたいして `PathRewritePlugin` で書き換えがおこなわれたあとの形態素列です。

B 単位モード、A 単位モードで出力する際は、この C 単位モードの形態素列にふくまれるそれぞれの語にたいして、B 単位分割情報、A 単位分割情報を参照して分割がおこなわれます ([LatticeNodeImpl.java](https://github.com/WorksApplications/Sudachi/blob/7ba2b9933bb5e5714f3cf5f6429273c4dcc4be2c/src/main/java/com/worksap/nlp/sudachi/LatticeNodeImpl.java#L142))。つまり、自分が A、B、C どの単位の語であるかに関係なく、B 単位分割情報や A 単位分割情報をもっていればその形態素列に分割されるわけです。

あらためてかんがえると A 単位分割情報や B 単位分割情報があれば分割タイプの情報は不要です。そのため分割タイプの情報は辞書の内部構造には記録されていません。というわけで、このカラムは辞書ソースをパーズするときのエラーチェックのみにつかわれています[^3]。

[^3]: 分割タイプに A が指定されているのに A 単位分割情報や B 単位分割情報があるとエラーになります。([CsvLexicon.java](https://github.com/WorksApplications/Sudachi/blob/7ba2b9933bb5e5714f3cf5f6429273c4dcc4be2c/src/main/java/com/worksap/nlp/sudachi/dictionary/build/CsvLexicon.java#L123))

ではよい Sudachi life を。
MariaDB／CONVERT関数でUSING utf8mb4指定時の照合順序（collation）

## はじめに
今回は、MariaDB（MySQL）のかなりマニアックな話です。
テーマは、「CONVERT関数でUSING utf8mb4指定時に適用される照合順序（collation）はなんなのか？」です。
確認はWSL2：UbuntuのMariaDB10.9で行いました。
DB名は適当に「bokudb」とか書いてます。
## 確認に使った環境
テストのために設定変更をします。
まず、データベースを「文字コード：utf8mb4、照合順序：utf8mb4_unicode_ci」にします。
```
ALTER DATABASE bokudb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
上記をrootで実行して、うまくいったか以下で確認します。
```
SELECT @@character_set_database, @@collation_database;
```
データベースが「文字コード：utf8mb4、照合順序：utf8mb4_unicode_ci」になったと確認したら、今回使う「test」テーブルを同じ文字コード・照合順序に変更します。
```
ALTER TABLE test COLLATE 'utf8_unicode_ci';
```
さらに、「test」テーブルの「str」というカラムを変更します。
```
ALTER TABLE cript_test MODIFY str VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
これで、データベース、テーブル、カラムとも「utf8_unicode_ci」の照合順序（collation）環境ができました。
## CONVERTはどの照合順序を返すのか
テストのため、strカラムに以下の文字列をいれました。
・ABCDEFGHIJKLM（英語の大文字）
・abcdefghijklm（英語の小文字）
半角英字の大文字・小文字ですが、「utf8_unicode_ci」は「全角・大文字・小文字を区別しない」ので、以下のようにlike条件に全角の「ＡＢＣ」を指定してもマッチします。
```
SELECT str FROM test WHERE str LIKE 'ＡＢＣ%';
```
カラムの照合順序（collation）は「utf8_unicode_ci」なので、上記の2行にマッチします。
さて、ここからが本番です。
この環境で、CONVERT関数に「USING UTF8MB4」を適用したときの照合順序（collation）は、データベース・テーブル・カラムの設定値である「utf8_unicode_ci」なのか、MariaDBのデフォルトである「utf8mb4_general_ci」なのか？・・以下のSQLで確認します。
```
SELECT str FROM test WHERE CONVERT(str USING UTF8MB4) LIKE 'ＡＢＣ%';
```
strをCONVERTで囲って「utf8mb4」を指定して、データが取得できたら「utf8_unicode_ci」、取得できなかったら「utf8mb4_general_ci」だと判断できます。
結果。
取得できたのは「0件」でした。
つまり、全角を区別している＝utf8mb4_general_ciだと思われます。
次に条件を半角英小文字にしてみます。。
```
SELECT str FROM test WHERE CONVERT(str USING UTF8MB4) LIKE 'abc%';
```
これだと2行とも取得できました。
間違いなく、CONVERTは、データベース・テーブル・カラムの照合順序（collation）を無視して、MariaDBのデフォルトである「utf8mb4_general_ci」を返しています。
## この仕様が大問題を引き起こす場合がある訳
なんで、こんなことを気にしているかというと、照合順序が「utf8mb4_general_ci」と「utf8_unicode_ci」のカラム同志を比較すると、以下のようなエラーがでるからです。

:::note alert
Illegal mix of collations
:::
例えば、上記のように「utf8_unicode_ci」定義されている「str」「str1」という２項目があった場合、以下のように片方にCONVERTをかまして
```
SELECT str FROM test WHERE CONVERT(str1 USING UTF8MB4) = str;
```
なんてすると、「utf8_unicode_ci」であるカラム「str」と、CONVERTが返す「utf8mb4_general_ci」がぶつかって「Illegal mix of collations」になります
CONVERTは「AES_ENCRYPT・AES_DECRYP」など暗号化カラムを使う場合に必須の関数です。
なので。
データベースの文字コードを「utf8mb4」にしても、照合順序を「utf8mb4_unicode_ci」で作ってしまったら「暗号化データを複合化するときにutf8mb4を指定できない」ことになり、エラーを回避するために「USING　utf8」として、暗号化した大事なデータだけは4バイト漢字が化けてしまいます・・という、実に間抜けな制約を容認しないといけなくなります。
## まとめ
僕はCONVERT関数の仕様がおかしいと思っています。
データベースで定義されている照合順序（collation）を返さないと絶対まずいと思います。
とはいえ。
バージョン10.9でも、その仕様は変わってません。
なので、後々で苦しまないよう、よほどの理由がない限り、デフォルト以外の照合順序（collation）に変更しないようにすべきだと思ってます。
ではでは。
育児中の睡眠不足を解消した話

# 概要
 - 1日8時間以上寝ないとキツいエンジニアが出産育児してる話
 - プログラミングやビジネスは関係ないポエム記事です

# はじめに

- 去年[「ロングスリーパーのエンジニアが生き残る方法」](https://qiita.com/cabbit/items/e0becbb20df8813346ca)を書きました
- 2022年に第一子を出産しました
- 現在育休中です

# 寝られてる？
- 皆様に心配されていますが、今のところ**寝られています！！** 

# なぜ育児中は睡眠不足になるのか
- 産まれたばかりの新生児は昼夜の区別がなく、一日中浅い眠りと覚醒を繰り返しています。まとまって眠ることができません
- 胃の容量も小さいため3時間おきに授乳が必要になります
- これだけ見ると「親も新生児に合わせて昼寝すれば大丈夫じゃん！」と錯覚しますが、（皆様ご存じの通り）ミルクの準備して、汚れた服を洗って、風呂に入れて……と世話をしているとなかなか眠れません。
- さらに女性の場合は出産、男性の場合は仕事＋家事で疲れた身体のままノンストップで突入するので、非常に辛いです

# 見込みが甘かったこと

### 帝王切開+出血で産後の回復が遅れた
- 無痛分娩の予約をしていまたが、最終的に帝王切開となりました。
（予定帝王切開で予め心の準備ができていたのは幸い）
- 1リットル近く出血し、術後しばらく貧血に悩まされました。

### 母乳過多で搾乳が必要になった
- 完全母乳は目指さずミルクとの混合で行く予定でしたが、母乳過多のため家では完全母乳になりました
- 母乳の出は出産してみないとわかりません
- 体質によりますが、授乳中は絶えず母乳が体内で作られ続けるため、定期的に授乳/搾乳が必要になります。
- 私の場合、子供が寝ていても搾乳のために3時間おきに起きる必要がありました。
- 乳腺炎は腫れて痛み、高熱が出るため辛いです。全力で回避しましょう。特にコロナ禍中に発熱すると助産師外来も行きづらくなり大変です。


# どうやって睡眠不足を解消したのか
### 人手を確保
- 我が家は里帰りをせず、**夫が退院日から育児休暇を取得しました。** 
- 仕事の関係で夫が一時的に仕事復帰する際は、母や父にヘルプに来てもらいました。
- 出産前は「1日くらい私ひとりでもなんとかなるんじゃないかなぁ」と思っていましたが、結果的には「常にツーオペ体制」にしておいて良かったです。

### 育児と睡眠に専念
- 前述の通り産後体調が悪かったため、産後1か月は育児と睡眠に専念し、家事は夫に丸投げしました。
- 「食事は夫婦揃って食べる」「昼は起きて、夜は寝る」「風呂は夜に入る」という従来の生活スタイルも捨てました。食べられるときに食べて、寝られるときに寝て、風呂は入れるときに（昼でも）入る。すぐに食べられるもの（おにぎり等）を冷蔵庫に常備していました。

### 睡眠の質を量でカバー
- 授乳の関係で強制的に細切れ睡眠になるため、どうしても睡眠の質は落ちます
- 睡眠の質が落ちた分、2時間睡眠×6～7回 とり、質を量でカバーしました。
- 起きている時間より寝ている時間が多いことに自分でも引きましたが、しょうがない。

# おわりに
来年に職場復帰するので、来年のAdventCalendarでは仕事との両立についてなにか書けたらいいなあと思います。がんばろう。
WiresharkでSourceとDestinationの名前解決するかどうか切替

# 概要

下記のSourceとDestinationについて…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/75364fb4-045a-7c90-d888-91fb438546fe.png)

* MACアドレス or IPアドレスで表示（名前解決しない）
* 端末名などで表示（名前解決する）

の切替。

# やり方

編集 ⇒ 設定 。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/9a42a055-f6af-6d0d-c6f8-9f321ea38286.png)

ここ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/1481d42f-2055-8b0f-2657-c6d7d732d326.png)

# バージョン

Windows 10 Pro 21H2 OSビルド 19045.2311
Wireshark Version 4.0.1
SQLAlchemy を使用して IBM Db2 を操作する

## 初めに
以前の投稿で、[「Db2 on IBM Cloud に Pythonからつなぐ（Select編）」](https://qiita.com/ac_qiita/items/5c81b794944534e3845f)という投稿をし、Pythonから「ibm_db」モジュールを使用して、SQL文でデータを取得する記事を投稿しましたが、今回はORMライブラリであるSQLAlchemyでデータを取得する方法をご紹介します。

## 前提
Db2上に以下のテーブルがあることを前提としています。
テーブル名：users
|id |name      |email              |
|---|----------|-------------------|
|1  |Taro      |taro@test.local    |
|2  |Hanakano  |hanako@test.local  |


## 手順
### 1. 必要なモジュールのインストール
```
$ pip3 install ibm_db　ibm_db_sa sqlalchemy
```
※環境によってはpip3ではなく、pipの場合もあります。

### 2. ソースファイルの作成
ソースの中身はコメント行を参考にしてください。
```python:db2.py
import sqlalchemy
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
import ibm_db_sa

# Db2 接続情報定義
# xxxxxは環境に合わせて置き換えてください。
user = "xxxxxx"
password = "xxxxx"
host = "xxxxx"
port = "xxxxx"
database = "xxxxx"

# Engineの作成
engine = create_engine(f'ibm_db_sa://{user}:{password}@{host}:{port}/{database};Security=SSL')

# Sessionの作成
session = scoped_session(
    sessionmaker(
        autocommit = False,
        autoflush = False,
        bind = engine
    )
)

# モデル定義
Base = declarative_base()
Base.query  = session.query_property()

class User(Base):
    __tablename__ = 'users'
    id = Column('id', Integer, primary_key = True)
    name = Column('name', String(50))
    email = Column('email', String(50))

# main関数
def main():
    # データ取得
    users = session.query(User).all()
    for user in users:
        print(f'user: {user.name}, email: {user.email}')

if __name__ == "__main__":
    main()
```

### 3. ソースファイルの実行
```
$ python3 db2.py
user: Taro, email: taro@test.local
user: Hanakao, email: hanako@test.local
```
※環境によってはpython3ではなく、pythonの場合もあります。
S3 にファイルアップロードしたら Invalid character in header content って怒られた

S3 に、ユーザがアップロードしたファイルを格納している。

Node.js aws-sdk を使って以下のように実装した。

```node.js
const r = s3.putObject({
    Bucket: bucket,
    Key: key,
    Metadata: {
      fileName,
    },
    Body: fs.readFileSync(filePath),
  }, (err, data) => {
    console.log('success');
  }
);
return r;
```

ファイル名 `ほげほげ.jpg` をアップロードしたら、サーバで以下のエラー

```text
    NetworkingError [NetworkingError]: Invalid character in header content ["x-amz-meta-fileName"]
      at ClientRequest.setHeader (node:_http_outgoing:647:3)
      at new ClientRequest (node:_http_client:284:14)
      at Object.request (node:https:357:10)
      at features.constructor.handleRequest (/home/kuro/app/node_modules/aws-sdk/lib/http/node.js:45:23)
      at executeSend (/home/kuro/app/node_modules/aws-sdk/lib/event_listeners.js:418:29)
      at Request.SEND (/home/kuro/app/node_modules/aws-sdk/lib/event_listeners.js:432:9)
      at Request.callListeners (/home/kuro/app/node_modules/aws-sdk/lib/sequential_executor.js:102:18)
      at Request.emit (/home/kuro/app/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
      at Request.emit (/home/kuro/app/node_modules/aws-sdk/lib/request.js:686:14)
      at Request.transition (/home/kuro/app/node_modules/aws-sdk/lib/request.js:22:10) {
    code: 'NetworkingError',
.....
```

どうやら、メタデータにもマルチバイトは許容されていないようだ。
encodeURI とか BASE64 とかにしたらエラー解消しました。

せめてメタデータくらい、マルチバイトを許してくれると助かるんだけどなぁ
SONiCにP4Runtimeを組み込む

# はじめに

そんなに長くもなければややこしくもないネタないかなーと思って、ふとまだやってなかったなと思い、書いてみます。

# P4Runtime?

まずP4言語というものがありまして、これはデータプレーンをプログラミングするための言語です。プログラマブルASICだとかFPGAだとかSmartNICだとかeBPF/XDPだとかDPDKだとかで、パケットをどう処理するかを記述するとその通りに動くというものです。たとえばルーティングテーブルを定義して、パケットのdstipを参照し、テーブルのdestination IP addressとLongest Prefix Matchして、というルックアップ処理などをP4言語で思うままにプログラムとして書けます。P4言語のコンパイラを通すと、ターゲットに合わせたアウトプットが得られるのでそれを組み込んだりします。

では、テーブルに値を書き込むにはどうすればいいのでしょう?

このテーブルへのアクセスを実現するのがP4Runtimeです。
P4言語で定義された、あるいはP4言語(のテーブル定義)で操作できるデータプレーンに対する、ベンダ非依存のAPI仕様となります。[P4Runtime Specification](https://p4.org/p4-spec/p4runtime/main/P4Runtime-Spec.html)できっちり定義されています。protobufで定義されます。リファレンス実装ではトランスポートは大抵のものと同じようにgRPCが使われます。

# SONiCのP4Runtime実装

SONiC-P4RTというのが、SONiC向けP4Runtimeアプリケーションの実装です。Googleの方がやられてるそうですが、[Googleの公式プロダクトではない](https://github.com/google/sonic-p4rt/blob/master/README.md)とのことです。

[PINS (P4 Integrated Network Stack)](https://opennetworking.org/pins/)というプロジェクトのアーキテクチャに沿った実装 SONiC-PINSとして組み込まれます。

PINSについては上記ページから辿れる[スライド(PDF)](https://opennetworking.org/wp-content/uploads/2022/05/PINS-Update-Final-Slide-Deck.pdf)がわかりやすいと思います。
スライドに書かれている内部動作について軽くまとめると、
- SAIに沿ったP4テーブル定義
- P4Runtime Appがコントロールプレーンと通信し、APPL_DB(P4RTテーブル)に書き込む
- `orchagent`は`P4RT`テーブルを読んで、ASIC_DBに書き込む
- あとは通常のSONiCと同様にASIC操作

基本構造はTofinoとか関係なく、どのASICのスイッチでも動くもの、と言えそうです。P4プログラミングによるデータプレーン拡張はさすがにTofinoじゃないと無理な気がします。

# 組み込んだらどうなるのか

SONiC上でp4rt dockerが起動され、P4Runtime serverとしてRPCを受け付けるようになります。それにより、P4Runtime仕様に沿ったAPIが提供され、外部(コントロールプレーン)から操作できるようになります。なるはずです。

コントロールプレーンはどうやって用意するの? テーブル情報は?
[Google P4 Programs](https://github.com/sonic-net/sonic-pins/tree/main/sai_p4/instantiations/google)にprotobufやC++ライブラリがまとめられてるので、これを使ってくださいということのようです。少し眺めてみるとactionsとかも定義されてるのですが、データプレーン定義として使われるわけではないようです。

# 組み込み方

1. `git clone https://github.com/sonic-net/sonic-buildimage`
1. `cd sonic-buildiamge`
1. `echo INCLUDE_P4RT=yes > rules/config.user`
1. ビルド

なのですが、手元の都合でビルドにかなり時間がかかっていて、実際に動かしてみるのはまた後日、ということにさせてください。

構造上、CLIやREST API(management framework)とは併用しない前提だと思います。(整合性がとれなくなる)

# おわりに

半端な紹介になってしまってすみませんが、理解の一助になれば幸いです。
ITエンジニアを採用しよう～実際に転職フェア参加してみた～

シリーズ：最近のトレンドで「セキュリティ強化」や「内製化」が挙げられます。私の会社のようにIT会社ではない会社がどうやってIT人材を集めるのかを考えていきたいと思います。
※私の個人的意見です。

転職フェアは企業側として参加しました。最近は転職サイトが増え、SNSなどによる引き込みなんかもあります。そんな中、いわゆる従来型の転職フェアで話をする形式で採用活動をしてきました。
これから転職フェアで採用を考えている方の参考になればと思います。

## 某転職フェア～大阪編～
　初めて転職フェアに参加しました。今回はIT系を大学の頃の就活を思い出します…
　弊社は大阪の企業で知名度はそこそこかと思っているのですが、今回はIT系の人材ということで中々連想しにくいのではと身構えていました。
　ブースも広くはなく、2+2で向かい合わせのレイアウトです。どの企業さんもタペストリーや旗などでアピールをしていました。（カーペットまで敷いているところも！）
　
　　～開場～　

　予想通り非IT系の方が来られるのですが、意外と人が来て混雑気味でした。
　とはいえ、某フェアの冊子にもITと一応記載があるので「何を見てきたんだろ？」という方も多くいらっしゃいました。
　また、初ということもあり、時間のかけ方など丁寧にしすぎて行列ができたタイミングもありました。
　開場からお昼すぎ、お昼過ぎから閉場1時間前までがピークでした。前半と後半のイメージで層も若干入れ替わっていた印象です。
　流石に16時(17時終了)頃は人もいなくなり、ちょっとずつ撤収作業などを始めました。
　
　　～てんやわんやでフェアは終了～

## 振り返り
　・一人にかける時間が長すぎた（20分over）
　　→説明資料の簡略化などで15分経ったら閉めにかかる。
　　→最初に興味あることを聞いてからにする。
　・誰が誰か、対応者は誰か分からない
　　→チェックやメモなどの自分のペースをきちんと作る。
　・フリーペーパー設置場所があった
　　→フリーペーパーはないが、冊子や新卒向けの漫画などを手配して持っていく。

　大きくはこれらを意識して2戦目の東京を迎えることになりました。

## 某転職フェア～東京編～
　コロナの影響もあって久しぶりの出張です。（しばらく経ってこの記事読み返すと「コロナの時か～」ってなるんでしょうね・・・）
　基本的には前回の大阪と同様です。ターゲットだけは「IT」と絞ってもらっているので、関係ない人はあまり来ないだろうと思いつつ設営しました。

　　～開場～

　関東には馴染みのない企業のせいか人は全く来ませんでした！
　Uターンや職種より業種に興味のある方が来られましたが、大阪のような行列はできることなく、終始落ち着いていました。
　他の企業さんは結構並んでいたりしたので、仕事の内容や働く場所というのはハードルがあがるようですね。
　ちなみに並んでいるところに視察に行くと、「社長在席中」や「決裁者(?)在席」みたいな札を掲げていました。（なるほどなるほど。。。
　人の流れは大阪と同様で、午前と午後に人は分かれている感じでした。特に入れ替わりの時間帯は人が全く来ませんでした。
　閉場1時間前には人がいなくなったので片付け始めました。

　　～落ち着いた雰囲気で閉場～


## 結果
　その後応募があって面談をした方は少なかったです。最近流行りの「コスパ」という観点だと良くなかったです。（やり方の問題も多少あると思いますが・・・
　次あるかと言われたらないです！やはり、土曜日出社や人数を割かないといけないのはきついです。
　雰囲気見てみるとか、学生の文化祭的なノリで気分転換する分にはいい意味で刺激になるかもしれません。

長々と採用活動について取り組んできましたが、一筋縄ではいかないのが現状です。
ITエンジニアが減っていることもありますし・・・
個人的な意見としては新卒とか中の人をしっかり「教育」していく環境を作っていくのが今やることだと思いました。
そしたらおのずと企業価値も上がっていくのかなと長期プランを考えています。

↓過去作

https://qiita.com/Waddy675/items/bdfceda843f909831bdb

https://qiita.com/Waddy675/items/d394698fd0810835a135

https://qiita.com/Waddy675/items/2e003e05dd6ffa1f482f
怠惰の極み、ESP32を使って電源スイッチのワイヤレス化

## プログラマーは怠惰の人たち
プログラマーは怠惰の人たちだという説がある。なぜならというと、2回以上繰り返す可能性のある物事に対して、ついスクリプトなどを書いてしまうからだ。たとえ1回ごとに数秒で済むことであろう、2時間以上かかってプログラミングすることはよくある。今回のこのつまらないものでも、これに似る思考から生み出されたものだ。

## 解決すべく課題
一言というと、毎日のPC起動がしんどいだ。きれいさっぱりに収まるために、自作PCが結構ユニークなケースを採用した。それで毎回デスクの下へ探って電源オンにしなければならない。これを解消すべき、何とかして電源スイッチをデスクトップに移したい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950721/501cd588-71d2-39f1-dbc4-c92dc87a6fe6.png)
参考にデスクトップ環境の写真を添付する

## ESP32に至る経緯
### 一番シンプルな解決策
単純にマザーボードへもう一つの電源ジャンパーを接続させて、適当にスイッチを設置すれば済む話ですが、そうするとあまり面白味がなくて変にケーブルが繋いて格好悪いからやめた。
### キーボードによる起動
電源オフ状態から起動させるには、PS/2端子のキーボードでないといけない。USBで起動できるのは、せいぜいスリープ状態ぐらいのパソコンだ。
### 電源状態
ACPI ( Advanced Configuration and Power Interface ) は 電源管理の規格で、S0 ~ S5 までのスリープ状態が規定されている。
| コード | 状態 | 起動可能の方式 |
|  ----  | ----  | ----  |
| S0 | 通常の運用状態 | - |
| S1 | スタンバイ | USB、PCIe、ソフトウェア |
| S2 | サスペンド | USB、PCIe、ソフトウェア |
| S3 | スリープ | USB、PCIe、ソフトウェア |
| S4 | 休止・ハイバネーション（ハイブリットシャットダウン） | PS/2、PCIe |
| S5 | 電源断 | PS/2、PCIe |
### WOL
>「Wake On LAN」とは、「マジックパケット」と呼ばれるネットワークパケットを送信することで、PCをスリープ状態から「復帰（Wake）」させる機能だ。

電源オフ(S5)の状態でも、PCIeからの起動ができるため、マザーボードに有線LANが接続されている限り、WOLを使ってPCを起動させるのは現実的だと思う。ちょうど研修で使っていたESP32は最近使ってないので、今度はそれを使ってワイヤレスの電源スイッチを作ろうと思う。

## 準備
### ハードウェア
+ ボタンスイッチ
+ ESP32開発ボード
+ モバイルバッテリー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950721/d21de028-ced9-43ef-3e94-2702feece38d.png)
### ソフトウェア
+ ESP32のWOLライブラリー
https://github.com/a7md0/WakeOnLan
+ マザーボードのWOL有効化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950721/dfa348b3-39dc-4661-a91e-d7be3bf72511.png)
+ 念のためネットインタフェースの設定も
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950721/afcaaa40-6ab6-7c39-d2c5-55c7fd88d53e.png)

## 接続
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950721/709fa995-9bd6-92b1-0ace-6e54a3523d5d.png)


## いざコーディング
```C
#include <WiFi.h>
#include <WiFiUdp.h>
#include <WakeOnLan.h>

// スイッチ
#define PIN_SW1 5

// WIFI設定
WiFiUDP UDP;
WakeOnLan WOL(UDP);
const char* ssid     = "your-ssid";
const char* password = "your-password";

// マジックパッケージ送信
void wakeMyPC() {
  const char *MACAddress = "01:23:45:67:89:AB";
  WOL.sendMagicPacket(MACAddress);
}

// チャタリングフリー・スリーブ復帰
unsigned long lastTimeSW1 = 0;
bool sw1 = false;
void IRAM_ATTR handleSW1()
{
  unsigned long now = millis();
  if ( (now - lastTimeSW1) > 200 ) sw1 = !sw1;
  lastTimeSW1 = now;
}

setup() {
  // ピンモード初期化
  pinMode(PIN_SW1, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_SW1), handleSW1, FALLING);
  // Wi-Fiに接続
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println(WiFi.localIP());
}

loop() {
  if (sw1) {
    // マジックパケットを送信する
    Serial.println("電源ON");
    wakeMyPC();
    delay(500);
  }
  //スリープ節電
  esp_deep_sleep_start();
}
```

## 終わり
![end.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950721/3f1babe6-59b3-f692-f868-b29da7a51b91.gif)
WEBサイト分析ログ管理の常識とは

## 自己紹介

始めまして。私はsinkCapital様と一緒にお仕事をさせていただいているhyutaです。
大手SIer企業に従事し、社会人歴8年・データ分析歴3年です。

今まではシステムDBの開発や分析はしてきたのですが、WEBサイト分析系のツール（AdobeAnalyticsとかGoogleAnalyticsとか）のログ開発・分析をやってみて、今までの常識と違うなと思ったことをつらつらと語っていきます。

## データ品質において重要なこと
DMBOKとかでデータ品質系の話とか色々ありますが、個人的には分析観点では以下が重要だと思ってました。
①データが欠損しないこと
②データの整合性が取れていること
③データの意味がわかりやすいこと
※もちろん他にも色々ありますが、WEBサイト分析ツールとの今までのGAPってことで上記挙げてます。

特に、システム開発では上記のことが起こらないように注意してテストしたり、DBMS側で制御したりすると思います。
ただ、WEBサイト分析ツールだと結構野良で開発されてたり、ツールの特性だったりでこれまじか？ってことが結構ありました

## 何が衝撃だったの？
### 1. まず、WEBサイト分析は欠損することが前提です
え？欠損することが前提？？
どういうこと？？
データ欠損してたら正しい数値見れないじゃん

その通り、正しい数値は見れないです。何となく雰囲気で見ることになります。
※正しい数値はデータベース側で見てください

じゃあ、なんで正しい数値見れないのかというと、AA（AdobeAnalytics）とかGA（GoogleAnalytics）とかのWEBサイト分析ツールはWEBビーコン型ログというものに属するので、Cookieをオフにされてたりネットワークが切れてたりすると計測ができないケースがあります。

今までの常識をお持ちのお客様と会話する時はこの欠損率を改善してくれ！という話になりがちなので、お客様と合意するときには欠損する前提で会話してくださいね。
※大体5~10%くらいは欠損すると思ってもらえれば

### 2. データの整合性が合わない
分析するときに本番DBと分析する数値の整合性が取れないと大問題になったりしますよね。
上の欠損に近い話ではあるんですが、これはWEBサイト分析ツール内に閉じたデータでも整合性が合わないことがあるってことです。

WEBサイト分析ツールを分析する方法は大きく2パターンあります
1. WEBサイト分析ツールが提供するレポート機能（AAだとワークスペース）を利用
2. WEBサイト分析ツールのrawデータ（加工されていないデータ）やDWHのデータを分析環境（Bigquery等）に移行し利用

ここで問題となるのが1,2で同じ分析方法を実施しようとしても数値が合わないケースがあること。
ここの原因分析を実施しようとすると深い闇につっこみに行くことになります・・・
各ベンダーに問い合わせても製品の仕様でブラックボックスになってます的なことになることもあり、ここの数値合わせに全力を注ぐことへの徒労感というかコスパの悪さは半端ないです。
なので、数件の誤差かつ原因調査やったけどようわからんって場合はそういうものだと割り切るしかないんですよね。
あくまで、目的は分析を実施し、傾向を把握することであって、正値を求めることではないのであきらめも肝心ということを念頭に置きましょう。

### 3.データの意味がぱっと見不明
開発系のDBやマートとかだとカラム名である程度データの内容とかが推察できたりします。

ただ、WEBサイト分析ツール（特にAA）はカスタム変数のメタデータ管理してないとマジでわからないです。
取り合えず、中身観てもフラグ値とかで入ってた時にはお手上げ。

なぜかっていうとAAってeVar/prop/eventってカスタム変数に番号が書いてるだけ（eVar1みたいな）なんです。
そう、番号が書いてるだけなんです！！
いや、番号書いてるだけだと意味わかんないやんけ・・・
これ、各チームの秘伝のたれ（そのチームの暗黙知）とかになってると最悪でデータ分析をそのチームしかできないみたいなことになっちゃいます。
※そもそもドキュメントすらないのは論外

なので、WEBサイト分析ツールを導入する際にカスタム変数入れるときは必ず色んなチームが確認できるようにドキュメント化してください！！

### まとめ
総じて、上記があるからWEBサイト分析ツールがダメなのではなく、上記が起きないようにだったり特性を理解して使いましょうってことですね。
ちなみに、WEBサイト分析ツール自体はめちゃめちゃ便利でサイト改善やWEBマーケするなら必須といっても過言ではないです。
慣れてしまうとデータ分析の幅が広がるのでめちゃめちゃ楽しいです。
※あくまで上記は私が感じた所感なので、それは違うよってのがあればコメントもらえると！

以上
Outlookで左上にあるナビゲーションバーを非表示に

# 概要

左上の ↓ これを非表示に。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/a14091ba-d606-37dc-1e90-aaab506d0f83.png)

# やり方

ファイル 。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/bfdefc1b-055d-3fd0-a6c3-2fc5fa8b9726.png)
オプション 。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/17162ea8-f9f9-19a6-bcab-dfa10957ae28.png)

詳細設定 ⇒ Outlookでアプリを表示する のチェック外す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/dc141bb9-7964-8508-c35b-d772b36672b1.png)

この再起動は **Outlookの再起動** 。
（PCの再起動ではない）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/87aaed8a-56ee-313b-4bbf-99e2b24e44d4.png)

# 参考サイトさん

https://forest.watch.impress.co.jp/docs/serial/yajiuma/1463069.html

# バージョン

Windows 10 Pro 21H2 OSビルド 19045.2311
Microsoft Outlook for Microsoft 365 MSO (バージョン 2211 ビルド 16.0.15831.20098) 32 ビット
SAMでAWS Lambda(カスタムイメージ) + API Gatewayを構成するときに、できるだけ効率的なlambda関数のunit testを考える

# 概要

Lambda + API GatewayをSAMを使って構成するときに、カスタムイメージを使ったlambda関数に関する情報があまり出て来なかったので、試行錯誤した内容を残す。具体的には以下の内容。

- lambda関数をカスタムイメージで作るときに、作った関数をunit testするときのtips



# カスタムイメージを使ってlambda関数をunit testすることの難しさ
カスタムイメージを前提としたLambda + APIGatewayを構成しようとすると、大体以下のようなディレクトリ構成になる。

```
.
├── README.md
├── __init__.py
├── lambda_function
│   ├── Dockerfile
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── events
│   └── event.json
├── samconfig.toml
├── template.yaml
└── tests
    ├── __init__.py
    └── unit
        ├── __init__.py
        └── test_handler.py
```

一方、lambda関数を動かすカスタムイメージとして、[公式](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/images-create.html#images-create-from-alt)では以下のような`Dockerfile`を例示している。
```Dockerfile
# Define function directory
ARG FUNCTION_DIR="/function"

FROM python:buster as build-image

# Install aws-lambda-cpp build dependencies
RUN apt-get update && \
  apt-get install -y \
  g++ \
  make \
  cmake \
  unzip \
  libcurl4-openssl-dev

# Include global arg in this stage of the build
ARG FUNCTION_DIR
# Create function directory
RUN mkdir -p ${FUNCTION_DIR}

# Copy function code
COPY app/* ${FUNCTION_DIR}

# Install the runtime interface client
RUN pip install \
        --target ${FUNCTION_DIR} \
        awslambdaric

# Multi-stage build: grab a fresh copy of the base image
FROM python:buster

# Include global arg in this stage of the build
ARG FUNCTION_DIR
# Set working directory to function root directory
WORKDIR ${FUNCTION_DIR}

# Copy in the build image dependencies
COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR}

ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ]
CMD [ "app.handler" ]
```

要は以下の感じ。
- 特定のディレクトリ(`FUNCTION_DIR`)にpythonのresourceを一通り`pip install`
- multi-stage buildでまっさらな環境にコピーして、lambda関数の実行環境・コマンド等を整備

ただ、この通り作ると、以下の観点でlambda関数のunit testがやりづらくなる。

- **ローカル(imageの外)でのテストがやりづらい**
  - 処理がpythonで完結しない（python以外のモジュールを呼び出してる、など）場合は、それらをローカルにインストールする必要があるが、unit testのためだけにそれする？
- **buildしたimageを使っても、微妙にテストしづらい**
  - テストを実行するにあたって、`ENTRYPOINT`が微妙に邪魔


# multi-stage buildで生成される中間imageを利用する
ところで先ほどのDockerfileの例を見ると、multi-stage buildが利用されている。

multi-stage buildを使うと、最終的に生成されるimage以外に中間stageも生成される（以下の`<none>`ってやつ）。これを使えば良いのでは？というのが、今回のコンセプト。

```shell
REPOSITORY        TAG            IMAGE ID       CREATED          SIZE
lambda-function   latest         77fa66e7adc8   12 minutes ago   3.37GB
<none>            <none>         70dd38f37db9   12 minutes ago   4.29GB
```

具体的には、`FUNCTION_DIR`にテストに必要な資材をmountし、`<none>`のイメージを使ってテストを実施する、という感じ。

```shell
docker run --rm
  --mount type=bind,src="$PWD/lambda_function",dst="$FUNCTION_DIR/lambda_function"
  --mount type=bind,src="$PWD/tests",dst="$FUNCTION_DIR/tests"
  -w $FUNCTION_DIR
  $(docker images -f "dangling=true" -q)
  /bin/bash -c "pip install pytest pytest-mock mocker && python -m pytest tests"
```

# 終わりに
なんかもっといい方法ありそうなんだよなぁ、、、