OmniAICreator/Qiita-1.07M huggingface dataset:
SalesforceとMulesoftの公式のおすすめキャッチアップコンテンツ

Salesforceは<a href="https://trailhead.salesforce.com/ja/today">Trailhead</a>で、Mulesoftは<a href="https://training.mulesoft.com/overview">Mulesoft Training</a>の公式コンテンツで学習するとよいです。組織や個人のメールアドレスを使って簡単にアカウントを作成して、ハンズオンで学習を進めることができます。

# Salesforce のおすすめキャッチアップコンテンツ (Trailhead)
アドミニストレーター認定の準備: 構成、設定とオブジェクト
https://trailhead.salesforce.com/ja/content/learn/modules/administrator-certification-prep-setup-and-objects

アドミニストレーター認定の準備: アプリケーションと活動
https://trailhead.salesforce.com/ja/content/learn/modules/administrator-certification-prep-applications-activities-and-mobile

アドミニストレーター認定の準備: データと自動化
https://trailhead.salesforce.com/ja/content/learn/modules/administrator-certification-prep-security-and-data-management

データ管理
https://trailhead.salesforce.com/ja/content/learn/modules/lex_implementation_data_management

ユーザ管理
https://trailhead.salesforce.com/ja/content/learn/modules/lex_implementation_user_setup_mgmt

Lightning アプリケーションビルダー
https://trailhead.salesforce.com/ja/content/learn/modules/lightning_app_builder

外部サービス
https://trailhead.salesforce.com/ja/content/learn/modules/external-services

Lightning Web コンポーネント開発者ツールの設定
https://trailhead.salesforce.com/ja/content/learn/projects/set-up-your-lightning-web-components-developer-tools

Lightning Web コンポーネントの基本
https://trailhead.salesforce.com/ja/content/learn/modules/lightning-web-components-basics

# Mulesoftのおすすめキャッチアップコンテンツ (Mulesoft Training)

Anypoint Platform Development: Fundamentals (開発: 基礎) (Mule 4)
https://training.mulesoft.com/course/development-fundamentals-mule4-japanese
androidでelixir その6

# 概要
androidでelixirやってみる。
androidでの開発手順を提案する。
paiza.ioは、パソコンからも、共有できる。

# 手順

# chromeでpaiza.ioを開いて、ソースコードを打ち込んで、バグ取りまで。

# ソースコードをコピーする。

# userlandを起動、nanoを起動。

# ソースコードをペースト。保存する。

# iex -S mixで実行。

以上。
【Vue3】別のアプリケーションインスタンスを外部から操作する時に気をつけるべきこと

# 環境
vue 3.1.5

# 実装したい内容
複数のアプリケーションインスタンスが共存する際、別インスタンスから別のインスタンスを操作したい。

# 具体的な状況
jsファイルに2つの異なるアプリケーションインスタンス（app1とapp2）が共存する。
```js
const app1 = Vue.createApp({
  data: () => ({
    message: 'instance1'
  }),
  methods: {
  }
})
app1.mount('#app1')

const app2 = Vue.createApp({
  data: () => ({
    message: 'instance2'
  }),
  methods: {
    changeMessage1: function() {
      app1.message = 'changed from instance2'
    }
  }
})
app2.mount('#app2')
```
app2のmethodsにchangeMessage1関数メソッドを設置し、app2からapp1のmessageを書き換えるような操作を行う。

```html
<body>
  <div id="app1">
    {{message}}
  </div>
  <div id="app2">
    {{message}}
    <button @click="changeMessage1">change message</button>
  </div>
  <script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
  <script src="js/hogehoge.js"></script>
</body>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538308/31b6a4cf-3ab0-a148-ca04-3aff9063c75c.png)
↑[change message]ボタンを押すことで、instance1という文字列を変更したい。。
が、このままではできない！

# 直し方
jsファイルを以下のように修正します。
```js
const app1 = Vue.createApp({
  data: () => ({
    message: 'instance1'
  }),
  methods: {
  }
}).mount('#app1')
//------------------
//直接、mountさせる！！
//------------------
const app2 = Vue.createApp({
  data: () => ({
    message: 'instance2'
  }),
  methods: {
    changeMessage1: function() {
      app1.message = 'changed from instance2'
    }
  }
}).mount('#app2')
```
```
const app1 = ~
app1.mount('#app1')
```
と書いていた部分を↓
```
const app1 = ~.mount('#app1')
```
のように変更しました。すると、狙い通りに↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538308/eb72889e-6732-7521-baee-bbdea9b465bc.png)
[change message]ボタンを押すことで、"instance1"から"changed from instance2"とメッセージが変更されました！！
# 教訓
```
const app1 = ~
app1.mount('#app1')
```
これと
```
const app1 = ~.mount('#app1')
```
これでは、返り値が異なるためにうまく動かなかったぽいですね。
【Laravel】モデルのスコープの使い方

DBの値を検索する際、モデルにスコープを指定するメソッドを定義し、そのメソッドを呼び出すことで条件を絞り込むことができます。

## ローカルスコープ

モデルにスコープを指定するメソッドを指定します。
※メソッドの名前は`scope` + 大文字で始まる名前（`scopeUser`など）

```php:app/Models/Memo.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;

class Memo extends Model
{
    // ユーザーを指定
    public function scopeUser($query)
    {
        // 認証済みユーザー
        return $query->where('user_id', Auth::id());
    }

    // 有効な（論理削除されていない）ものを指定
    public function scopeActive($query)
    {
        return $query->whereNull('deleted_at');
    }
}
```

呼び出す時のメソッド名は`scope`なしの小文字です(`user()`など）

```php:app/Http/Controllers/HomeController.php
    public function index()
    {
        $memos = Memo::user()->active()->orderBy('updated_at', 'desc')->get();
        return view('create', compact('memos'));
    }

```

モデルに定義したメソッドを組み合わせることで、柔軟にスコープを組み合わせることができます。

## グローバルスコープ

そのモデルでの全ての検索にそのスコープが適用されます。

```php:app/Models/Memo.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder; //追加
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;

class Memo extends Model
{
    // モデルの初期起動メソッドでスコープを指定
    protected static function booted()
    {
        static::addGlobalScope('user_id', function (Builder $builder) {
            // 認証済みユーザー
            $builder->where('user_id', Auth::id());
        });
    }

    // 有効な（論理削除されていない）ものを指定
    public function scopeActive($query)
    {
        return $query->whereNull('deleted_at');
    }
}
```

```php:app/Http/Controllers/HomeController.php
public function index()
    {
        $memos = Memo::active()->orderBy('updated_at', 'desc')->get();
        return view('create', compact('memos'));
    }
```
検索結果はローカルスコープを使用した場合と同じです。

## 参考

https://readouble.com/laravel/7.x/ja/eloquent.html

## 良かったら^^
https://musubite-job.com/recruitments/XPriPw?utm_source=qiita
GitLab CIでVitestを実行し、coverageを計測する

# はじめに

GitLabのCIを使ってVitestのテストを回してみたいなぁと思い、回したときのメモです。

# 説明しないこと

* GitLabとは？
* CIとは？
* Vitestの詳しい使い方(各種メソッド等)

# Vitestとは

- 私が説明するよりも良い記事があったのでこちらから抜粋させていただきます。

https://reffect.co.jp/html/vitest#:~:text=Vitest%E3%81%AFVite%E7%92%B0%E5%A2%83%E3%81%A7,%E5%88%A9%E7%94%A8%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E7%89%B9%E5%BE%B4%E3%81%A7%E3%81%99%E3%80%82


:::note info 
VitestはVite環境で利用することができるUnit Testingのフレームワークです。Viteを利用していためViteと同様に高速で動き、TypeScriptなども追加設定なしで利用できることが特徴です。
:::

# SvelteKitアプリケーション構築

```bash
$ npm create svelte@latest my-app
$ cd my-app
$ npm install
$ npm run dev -- --open
```
[公式](https://kit.svelte.jp/)

# Vitest実装

## 1. インストール

```bash
npm install --save-dev vitest jsdom @testing-library/svelte
```

## 2. vite.config.ts修正

```typescript:vite.config.ts
export default defineConfig({
  ...
  test: {                  // 追加
    environment: 'jsdom'   // 追加
  }                        // 追加
})
```

## 3. 実行コマンド追加

```json:package.json(抜粋)
{
  "scripts": {
    "test": "vitest",   // 追加
  },
}
```

## 4. テストコード追加

テストコードを置くディレクトリには好みがあると思いますのでご自由に設定ください。

私は↓のようにsrc以下に`__test__/index.test.ts`を作成しました。

```
.
├── src
│   ├── __tests__
│   │   ├── index.test.ts
```

動作確認のため`1 + 1 = 2`を試してみます

```typescript:index.test.ts
import { test, expect } from 'vitest'

test('1 + 1 は 2 であること', () => {
  expect(1 + 1).toBe(2);
});
```

詳しくは[公式ドキュメント](https://vitest.dev/)を見てください。

## 5. 実行

```bash
$ npm run test
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/50229cf4-9c47-7aa8-c146-c08b374bfeb7.png)
**watchモードでテストが動きます**

# Vitest Coverageモード起動

## 1. インストール

```bash
npm i -D @vitest/coverage-c8
```

## 2. 実行コマンド追加

```json:package.json(抜粋)
{
  "scripts": {
    "coverage": "vitest run --coverage",   // 追加
  },
}
```

## 3. 実行

```bash
$ npm run coverage
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/48b3f38d-80e1-7d9e-16a8-c71c63d8f72c.png)

**coverageの計測ができました。**
どのコンポーネント、ファイルも参照していないので当然coverageは0%ですね

# GitLab CIでテストを実行する

## 1. .gitlab-ci.yml記入

```yaml:gitlab-ci.yml
default:
  image: node:18.14-alpine3.16

before_script:
  - npm install

stages:
  - test

test:
  stage: test
  script:
    - npm run coverage
```

## 2. pushしてみる

pushすることでpipelineが動き出し、ちゃんと動きました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/3e405c20-bc51-30ce-1027-cc97b3e9a6b6.png)


# さいごに

:::note warn
coverageはあくまでテストが通った率であり、これを100%にすればよいというわけではありませんが、1つの指標にはなると思います。良いテスト駆動開発ライフを～！
:::


## 余談

- `src/coverage`に結果が出力されるので、それを`GitLab Pages`を使ってホスティングすることで、結果をWeb上から確認することができました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/69cd57cf-7984-bbe1-d964-00f14645a23a.png)
タワーデフェンスゲーム　Unity　敵の機能　2/10　敵が経路に沿って移動する

# 概要
前回の続きです。

https://qiita.com/RYA234/items/baa5c93c4904c1c23bea

今回はオブジェクトが経路に沿って移動する機能を実装します。
以下gifアニメは、オブジェクトが経路を沿って移動している様子です。

![Enemy1222.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/d91fb3c0-77a3-3258-9d4f-7c67cffac975.gif)

次

https://qiita.com/RYA234/items/fa9f1e49b7b97233c4d1

# 開発環境

IDE:Rider
Unity:2020.3.42(LTS)
OS:Windows10

# 実装のポイント
敵オブジェクトが移動予定地点に近づくと、次に移動する点が変更されます。
これを終点に達するまで繰り返すことで、敵オブジェクトは経路に沿って移動します。

敵オブジェクトが近づいたかどうかは、Vector3.Distanceメソッドを使って敵オブジェクトと到着予定地点の距離から判断します。距離の値が近づいた場合は「現在の要素数+1」をして次の点の座標に移動するようにします。

# コード

```diff_c_sharp:Enemy.cs
using System;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 3f;
    [SerializeField] private Waypoint waypoint;
    public Vector3 CurrentPointPosition => waypoint.GetWaypointPosition(_currentWaypointIndex);
    private int _currentWaypointIndex;

    private void Start()
    {
        _currentWaypointIndex = 0;
    }

    private void Update()
    {
        Move();
+        if (CurrentPointPositionReached())
+        {
+            _currentWaypointIndex++;
+        }
    }

    private void Move()
    {
        Vector3 currentPosition = waypoint.GetWaypointPosition(_currentWaypointIndex);
        transform.position = Vector3.MoveTowards(transform.position, currentPosition, moveSpeed * Time.deltaTime);
    }
    
+    private bool CurrentPointPositionReached()
+    {
+        return Vector3.Distance(transform.position, CurrentPointPosition) < 0.1f;
+    }

}
```


# 参考

https://docs.unity3d.com/ja/2019.4/ScriptReference/Vector3.Distance.html


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/5ae1a635-782c-2884-479e-0dd3b8b927c9.png)


Section4 13

https://www.udemy.com/course/learn-how-to-create-a-2d-tower-defense-game-in-unity-2020/learn/lecture/22898166#content

github コミット分

https://github.com/RYA234/2DTowerDeffenceMyLearning/commit/bb46812cfaba2f06f87c3bf9b7cc5578393e12fc

章まとめ

https://qiita.com/RYA234/private/6d878a7958a3c760c8af
ec2を再起動するたびに特定のコマンド実行

ec2起動時にはアプリのバイナリファイルを実行するコマンド
```
nohup ./main &
```
をターミナル上で打つ。
ただサーバー代をケチっているため、夜はインスタンスを停止させ、朝にインスタンスを再起動？している。
そのため毎朝ターミナルにssh接続をして上記コマンドを打たないといけない。
今回はそれがとても面倒に感じたためインスタンス再起動時に自動でbashスクリプトを読み込む設定をしてみた。

参考にさせていただいたURL
https://qiita.com/Ueken3pei/items/003600c9b1c8435af548

上記のページで言うとおりに
ec2インスタンスのユーザーデータの箇所にテキストベースで
MIME-MULTIPART形式でかけばよい。上記のコマンドの場合だと
```
Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud_final_modules:
- [scripts-user, always]

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
nohup ./main &
--//--
```
とユーザーデータを編集して保存すると、再起動しても毎回このコマンドが打ち込まれるため、とても便利になる。
[小ネタ] NewRelic コンソールのショートカットキー上書きを無効化する

# 環境
- Chrome: 110.0.5481.177
- Tampermonkey: 4.18.1

# ショートカットは上書きされたくない
私はかつて Emacs 原理主義者だったこともあり Ctrl+K は kill-line の動作をしてくれないと落ち着きません。

しかし NewRelic コンソール 上で Ctrl+K を押すと・・・

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97890/05fd70c3-c9b3-b789-c9bc-31d58ff7b911.png" width="400" />

な、なんじゃこりゃぁぁ:fearful::fearful::fearful:
そういうの、いいから:sweat:


# Tampermonkey の UserScript で上書きを防止する

[Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ja) は UserScript でブラウザの動作をカスタマイズできる Chrome 拡張です。
これを使って、お呼びでないショートカットの上書きからブラウザを保護します。

開発者ツールで確認すると、件のショートカットは keydown イベントで発火しているようなので、addEventListener で keydown イベントを上書こうとする振る舞いを無理やり無視させます。
スクリプトは以下。
他にも巻き添え食ってるイベントあるかもしれないけど、知らん。

```javascript:protect_newrelic_shortcut_hijack.user.js
// ==UserScript==
// @name         Protects keyboard Hijacking on NewRelic website
// @namespace    https://github.com/takeyoda/
// @version      0.1
// @description  Protects keyboard hijackiing on NewRelic website
// @author       takeyoda
// @match        https://one.newrelic.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
  'use strict';
  const orig = EventTarget.prototype.addEventListener;
  orig('DOMContentLoaded', function() {
    EventTarget.prototype.addEventListener = orig;
  });

  EventTarget.prototype.addEventListener = function(type, listener, useCapture) {
    if (type === 'keydown') {
      console.log('Shortcut HIJACKING detected!', type, listener, useCapture);
    } else {
      orig(type, listener, useCapture);
    }
  };
})();
```

# 私見
やれやれ。せめて User Preference で On/Off 切り替えられればいいのに。
OS やブラウザが設定しているショートカットを尊重しない Web サイトって Bad UX なのでは？
Alpine.jsで動的に追加した配列要素が初期化されてしまう

Alpinejsのx-forで配列を回してinputを表示し、button押下で要素の追加や削除するケースはよくあると思う。

```html:html
<div x-data="{items: ['A', 'B', 'C']}">
    <template x-for="(item, index) in items">
        <div class="item">
            <input name="`item[${index}]`" x-model="item">
        </div>
    </template>
    <button type="button" @click="items.push('')">追加</button>
</div>
```

上記のようにすると、追加ボタンを押したときにそれまでに入力した情報は初期化されてしまう。
原因はx-modelでディレクティブしている対象がx-forで新しく代入したitemになっている点と思われる。

```html:html
<div x-data="{items: ['A', 'B', 'C']}">
    <template x-for="(item, index) in items">
        <div class="item">
            <input name="`item[${index}]`" x-model="items[index]">
        </div>
    </template>
    <button type="button" @click="items.push('')">追加</button>
</div>
```

itemsにindexを渡すことで正しく親のデータが書き換えられ、pushで追加したときもそれまでのデータが保持されるようになる。

以上備忘録。（数時間苦しんだ）
androidでelixir その5

# 概要
androidでelixirやってみる。
日本製ファイルマネージャー、(エクスプローラー)のfdcloneを導入する。

# インストール

```
sudo apt install fdclone

```

# このままでは、メニュー、日本語ファイル名が文字化けしますので、文字コードの変更をします。

- fd を起動
- [Shift]+[E] で設定画面を呼び出す
- 何回も「↓」で下にスクロール
- 「LANGUAGE」までスクロールし [Enter]
- そこで「UTF-8」を選択し [Enter]　-->正常に
- そのまま「→」で、上のタブの一番左の「/保存」まで移動
- そのタブの一番下の「Overwrite」からメニューに従って保存



# 実行結果

![Screenshot_20230329-182605.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/280d5ef5-3593-3122-7f6d-ae80c68ba13d.png)

以上。
[メモ]Firebase関連HowTo

# 複数環境にデプロイしたい

dev,stg,prodなど環境を分けたい場合など

Firebaseのコンソールで必要なプロジェクトの作成する

## 設定の確認
プロジェクトディレクトリのルートにある`.firebaserc`を確認

:::note info
このファイルを直接編集してもデプロイ先の追加ができる
:::

```json:.firebaserc
{
  "projects": {
    "default": "[プロジェクトID]"
  }
}
```

## コマンドでプロジェクトを追加する

```bash:コマンド
$ firebase use --add
```

```bash:例　devプロジェクトを追加
$ firebase use --add
? Which project do you want to add? (Use arrow keys)  # 追加するプロジェクトを選択
❯ [プロジェクトID dev]
  [プロジェクトID]
? What alias do you want to use for this project? (e.g. staging) dev # エイリアスを指定
```

```json:.firebasercで追加されてか確認
{
  "projects": {
    "default": "[プロジェクトID]",
    "dev": "[プロジェクトID dev]"
  },
  "targets": {},
  "etags": {}
}
```

`projects`に`dev`が追加されていれば成功

```bash:devへのデプロイ
$ firebase use dev  # 使用するプロジェクトをエイリアスで指定
$ firebase deploy
```

# CIツールからデプロイしたい

:::note alert
紹介するやり方は非推奨でバージョンアップで削除される予定という警告が出ていたので使えないかもしれない
:::

## デプロイ用のユーザーの準備

```bash:ユーザートークン取得コマンド
$ firebase login:ci
```

```bash:実行
$ firebase login:ci

Visit this URL on this device to log in:
https://accounts.google.com/o/oauth2/... #　URLを省略してます

Waiting for authentication...

✔  Success! Use this token to login on a CI server:

[Firebaseトークン]

Example: firebase deploy --token "$FIREBASE_TOKEN"
```
表示に従ってユーザーの認証をすると`Firebaseトークン`が取得できます

後は対話型にならないように`force`オプションを使用してデプロイ

```bash:デプロイコマンド
firebase deploy　--force --token "[Firestoreトークン]"
```
# コンソールで設定したFirestoreのindexをローカルに反映したい

```bash
$ firebase firestore:indexes > firestore.indexes.json
```

`firestore.indexes.json`は各環境の設定に合わせて指定
わからなかったら`firebase.json`の`firestore > indexes`を確認

# Functionsのレイテンシが遅い

## インスタンスが立ち上がってるか確認
Firebaseのコンソールから確認できないのでgcpのコンソールから確認する
URL: https://console.cloud.google.com/functions/list

インスタンスを立ち上げるときは4秒くらいかかってる印象
最小インスタンスを設定するのも良さそう

```javascript:最小インスタンスを1に設定
exports.hogehoge = functions
    .runWith({"minInstances": 1})  // インスタンスの設定
    .https.onRequest(async (req, res) => {
        // 省略
    });
```

## ロケーションを近い地域に変更
日本からusのロケーションにアクセスした場合と`asia-northeast1 (Tokyo)`からアクセスした場合
レイテンシが一気に短くなった

```javascript:リージョンをasia-northeast1に設定
exports.hogehoge = functions
    .region("asia-northeast1")  // リージョンの設定
    .https.onRequest(async (req, res) => {
        // 省略
    });
```

## インスタンスのメモリサイズを大きくする

割り当てられたメモリサイズによってCPUのスペックが変わる
デフォルトで256MBが割り当てられてる
URL: https://firebase.google.com/docs/functions/manage-functions?hl=ja#set_timeout_and_memory_allocation

CPUを使うような処理を行ってない場合は効果が殆ど無い

```javascript:インスタンスのメモリサイズを変更
exports.hogehoge = functions
    .runWith({"memory": "1GB"})  // インスタンスの設定
    .https.onRequest(async (req, res) => {
        // 省略
    });
```

## その他

public previewの2nd genから関数インスタンスで一度に複数のリクエストを実行できるようになるみたいなので
試してないのでわからないけど正式リリースになったら早くなるかも
URL: https://firebase.google.com/docs/functions/beta

この他に早くなる方法があればコメント欄で教えてほしい
text-alignが効かない！vertical-alignが効かない！

# はじめに
`text-align` や `vertical-align` を指定しているのにうまく効かなくて困ったことはありませんか？

「`text-align: center;` は水平方向に中央寄せしてくれるもの」
「`vertical-align: middle;` は垂直方向に中央寄せしてくれるもの」

最初にこう覚えてしまったことがすべての元凶です。
これから新人が入ってきて「うまく効いてくれないんですけどなんでですか？(;o;)」と質問されるかもしれません。そんな事態に備えて、なぜ効かないのか、何を確認したら良いのかをちゃんと説明できるようになっておきましょう！

# `text-align` とは
「**ブロック要素に指定して、要素内のインライン要素の位置を水平方向に変えるもの**」です。
[CSS: カスケーディングスタイルシート](https://developer.mozilla.org/ja/docs/Web/CSS/text-align)

## `text-align` が効かない理由
### 1. ブロック要素に指定していないから
`div` や `p` などの**ブロック要素に指定**しているかを確認してください。

### 2.子要素に指定しているから
位置を決める要素自体ではなく、その**親要素に指定**してください。
指定する要素と位置が決まる要素が異なることを覚えておきましょう。

### 3. 位置を決める要素がブロック要素だから
`text-align` はインライン要素の位置を決めるものなので、いくらブロック要素である親要素に指定していたとしても肝心の位置を決める要素がブロック要素だと効きません。
ブロック要素自体を中央寄せにするには、`margin: 0 auto;` を使ってください。

# `vertical-align` とは
「**インライン要素同士を垂直方向に整列するもの**」です。
[CSS: カスケーディングスタイルシート](https://developer.mozilla.org/ja/docs/Web/CSS/vertical-align)

## `vertical-align` が効かない理由
### 1. ブロック要素に指定しているから
`vertical-align` は**インライン要素とtableのセル要素に使える**ものです。
ブロック要素に指定していても効きません。
インライン要素でも`float`がかかっていたり、`position: absolute;` がかかっていると効かないので注意しましょう。

### 2. 親要素に指定しているから
子要素に継承されないため、**位置を決める要素自体に指定**します。

# つぶやき
こう見るとこの2つってかなり紛らわしいですよねぇ〜。

# 参考
[CSS「text-align」を理解【効かない場合の対処方法】](https://web-camp.io/magazine/archives/27392)
[CSS text-align:centerが効かない原因と対処法](https://csshtml.work/text-align-bad/)
[vertical-alignが効かない人は多分勘違いしている](https://learning.mihune-web.com/vertical-align/)
[vertical-alignが効かない時](https://www.baycross.jp/column/production/va_doesnt_work)
Log Analytics を試してみる

こんにちは。Enabling team の山本です。

「負の整数」を初めて学んだ時のことを覚えていますか❓

マイナス×マイナス＝プラス
になる理由など。

娘の中学準備で数学を改めて学ぶと、自分は形式的に覚えていただけなんだな、、と思いました。
それなりの成績しか取れないはずだ 🙀、、

日常に、見逃していた学びがありました。

さて、今回は、[Log Analytics](https://cloud.google.com/logging/docs/log-analytics?hl=ja) について書きます。

この記事の作成時点で、 `asia-northeast1` が利用可能になりました。

個人的には、Log Explorer の機能は、充実しており、問題なく使える状態でした。
特に、[Log Based Alerts](https://cloud.google.com/logging/docs/alerting/log-based-alerts?hl=ja) は、Security 対策に重宝しています。

今回、Log Analytics を試すモチベーションは、アドホック分析です。

例えば、以下のような調査の依頼が最近よくあります。
- ◯◯ エラーの時間帯別の発生件数。
- ◯◯　の平均。

## TL;DR
- グループ化、平均など SQL ならではの機能が嬉しい。
- 表示対象のフィールドを絞れるので見やすい。
- Log Explorer にある機能の一部(Log based alert など）は、今のところ無い。

## 比較
### Filter resource label
- Log Explorer
    ```
    severity>=ERROR
    AND jsonPayload.message:"ホゲホゲ"
    ```
![le-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569971/70231c63-218c-b855-36da-71140d9e2c00.jpeg)

- Log Analytics
    ``` sql
    SELECT
      timestamp,
      JSON_VALUE(json_payload.message) as message
    FROM
      `hoge.global._Default._AllLogs`
    WHERE
      timestamp >= TIMESTAMP("2023-03-27 16:18:00", "Asia/Tokyo")
      AND timestamp <= TIMESTAMP("2023-03-27 16:20:00", "Asia/Tokyo")
      AND severity IS NOT NULL
      AND severity IN ('ERROR')
      AND JSON_VALUE(json_payload.message) LIKE "%ホゲホゲ%"
    ```
![la-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569971/df7c7010-a00a-5ee5-c09d-a62399a6028a.jpeg)


### Group
- Log Analytics
```sql
SELECT
  count(*) as count
FROM
  `hoge.global._Default._AllLogs`
WHERE
  timestamp >= TIMESTAMP("2023-03-27 16:18:00", "Asia/Tokyo")
  AND timestamp <= TIMESTAMP("2023-03-27 16:20:00", "Asia/Tokyo")
  AND severity IS NOT NULL
  AND severity IN ('ERROR')
  AND JSON_VALUE(json_payload.message) LIKE "%ホゲホゲ%"
```
![la-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569971/73a22a3e-59a7-48c3-6416-cbd7cb5ddee7.jpeg)


## まとめ
Log Analytics について書きました。
これを書いている時点では、あーでもない、こーでもない、と色々試してる段階です。
このあと、色々書き足していこうと思います。
集計・平均・フィールドでの並び替えを使えることで、不正や異常など、その時々の要件に対応できるため、有用だと感じました。
【SAP】業務の流れ

# 会社部門とSAPモジュール関係
※青い部分はSAP関連の部門です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/424429/529fa566-f261-f6d4-1164-00189148a92c.png)


## 調達の業務内容
物と人の仕入です。
### 仕入
１．製造で使う資材・原材料
２．会社で使う備品
３．契約社員（人材の調達）

### 仕入ために
４．仕入先の会社と調整・金額交渉・契約
５．物・人の発注
６．届いた物や派遣された人に対して検収

### 関連部門
#### 生産管理/製造
　自社で作る製品に必要な資材・原材料が、いくら足りないか・いくら買いたいかを調達部門に連絡必要。
#### 物流
　調達部門から物と人を仕入れ後、物流部門で自社倉庫で製造に使われるまで管理されます。
#### 経理
　検品時に仕入先から請求書（モノがいくら、金額がいくら、と書かれた紙）が届きます。
　その請求書に間違いがないかを調達部門でチェックします。
## 生産管理/製造
いくら物を作るか計画し
生産計画に沿って、製造する
### 関連部門
#### 営業
営業から入る受注で「いつまでに・いくらモノが必要なのか」という情報を元に、生産計画を作ります。
#### 調達
資材・原材料がいくら必要なのか・いくら足りないのかを調達部門に伝え、調達部門に仕入先からモノを買ってもらうように依頼をします。
#### 物流
１．実際に製造に使うタイミングになったら、物流に連絡をして製造現場・ラインまで持ってきてもらいます。
２．製造でできた半製品・最終製品も、いったんは自社倉庫で保管するため、できたものも物流部門に連絡し、運んでもらい保管してもらいます。
#### 経理
１．製造に「作業時間はいくらかかったか」、「資材・原材料をいくら使ったか」、「半製品・最終製品がいくらできたか」、という製造実績をとらえ、経理の原価計算に利用します。
２．コスト視点での分析をするために、製造実績を経理で集約し、原価分析をします。
## 営業
１．得意先に自社製品を紹介し、買ってもらう
２．注文をもらったモノを得意先に配送手配
３．買ってもらったモノに対してお金を請求
<font color=red>※業務理解し易いため、まとめましたが、２、３は営業＋営業支援部門のパータンが多い</font>
### 関連部門
#### 生産管理/製造
　営業が取ってきた受注情報を生産管理/製造に連絡し、得意先向けに製品を作ってもらいます。
#### 物流
　製品が保管されている自社倉庫から、得意先に出荷するために物流部門に連絡します。
#### 経理
　得意先へ請求処理、会社の銀行口座にお金が振り込まれたことを経理で確認し、売上を計上します。
## 物流
１．自社内の物の移動（材料、半成品、成品入庫）
２．仕入先から・得意先への物の移動の手配（転売会社）
※物を移動させる際に、移動させる物を間違えたらいけないので、間違い防止のために外箱にバーコード付きラベルを貼って、物の管理をしてたりします。
### 関連部門
#### 調達
調達が仕入先に発注した物が仕入先から届き次第、入庫検品し、自社倉庫で保管します。
#### 生産管理/製造
１．生産現場からの依頼で、仕入れた資材・原材料を製造現場へ払い出します。
２．生産現場で作られたものをいったん自社倉庫で保管します。
#### 営業
得意先へ製品を出荷完了すれば、営業に連絡します。

## 経理
経理業務は、日次・月次・年次業務に分けられます。
### 日次
経費精算
預金管理
帳簿や伝票などの管理
取引先の信用分析（与信管理）
### 月次
月次決算書の作成
予算実績管理
給与計算
請求・支払業務（掛金処理）
実際原価分析
### 年次
決算業務
法人税等の申告・納税
年末調整
償却資産の申告
次年度の予算編成
標準原価計算
### 関連部門
#### 調達
　調達部門で確認した仕入先からの請求書を元に、月次で仕入先への支払業務をします。
#### 営業
　営業部門から得意先に送った請求書・得意先からの検収情報を元に、月次で得意先への請求業務をします。
#### 生産管理/製造
１．製品の標準原価（これくらいのコストで作れるだろうという理論値）を年次で計算をします
２．実際の製造実績から実際原価（実際に製品を作るのにかかったコスト）を月次で計算し、標準原価と実際原価の差異を分析をします。
#### その他部門
　各部門から上がった経費（出張旅費、備品購入などなど）の精算業務をします。
## 人事
１．人に関わるもの（採用・入社/退社手続き・教育・評価など）
２．金に関わるもの（給与・社会保険手続きなど）
### 関連部門
#### 人に関わる業務
全部門関連があり、配属、異動など関連があります。

#### 金に関わる業務
給与・社会保険などの支払いは、経理部門へ連携されます。
【エンジニアのこれから】ChatGPTに対するスタンスを考えてみた

# 0. TL:DR

- ChatGPTは、あくまでも便利ツールとして考えるべき。
- 脳死して利用するのは、あくまでもエンジニアとしてはやるべきではない。
- Trust but verify.

ふわっと投げられた質問に、明確に分析できる能力が問われていくと思われる。
一応、使いこなすために論文と公式まずよむ。

公式
[OpenAI公式](https://openai.com/)

論文
["Improving Language Understanding by Generative Pre-Training" (Radford et al., 2018)](https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/language_understanding_paper.pdf)
["Language Models are Few-Shot Learners" (Brown et al., 2020)](https://arxiv.org/abs/2005.14165)

# 1. ChatGPTとは

OpenAI社がリリースした自然言語からなんらかの解を出すChat型のAIシステム
精度がとても、とてもよいので、現在かなり流行している。
**MicrosoftがOpenAIへ数十億米ドル規模の投資**をした
**ChatGPTの出現によって、自社の検索ビジネスが脅かされるとしてGoogleが社内緊急事態を宣言**をした
ことで一躍有名となった。

ChatGPTの素晴らしいところは、自然言語をプロンプトとして、適切な回答をするところだ。
この製品はとても、優秀で発表した2022年11月~約1ヶ月でバージョンアップで日本語にも対応してきた。

おそらくは、さらなる発展は想定される。

https://openai.com/
https://en.wikipedia.org/wiki/OpenAI

defaultの状態で利用した場合（学習データをこちら側で用意しない場合）の強みと弱み
GPTアルゴリズムについては、論文よんで。

## 1-1. 強み

- 高度アルゴリズムの生成については、かなりの強みを持つ。
- 検索のフィルタリングとしての検索ツールとしても、かなり強力である。

## 1-2. 弱み

- 法律関係
- 感情関係
- 反社会的調査
- インターネット非公開情報に対する調査

上記は、OpenAI側が学習に対していれないというスタンスであるため、難しい。
その他の弱みとして、以下のものもある。

- リーダブルコード
- 新規事業の発想
- GPT4は遅い…


# 2. ChatGPTの利用について

## 2-1. 企業の恐れ

ChatGPTに対して、1企業の経営者として考えたのは以下のとおりである。

- 自社のコアドメインをOpenAIに渡してよいのか？
- AI法律がどのようになっていくのか？
- AIがつくった生成物の著作権はどこに帰属するのか？
- AIでつくったものをNFTとしてよいのか？
- 他社のAIと自社のAIの組み合わせをおこなった場合のライセンスはどこになるのか？

## 2-2. エンジニアとしての恐れ

- ただ単純なるコード労働者の人材が、AIに取られる可能性
- AIが生成したコードを誰が担保するのか？
    - AIのAIによるAIのためのAIを管理する人間がやっぱりでてくると想定
    - 楽観視はしているが、悲観もしてはいない。
- 自分たちの知識が滅びるのではないか？
    - 知識は滅びる可能性はあるが、知恵は滅びない。
    - 直感は、AIにはない。ここにバグありそうとかいう嗅覚とかは問題ない。

## 2-3. スタンス

- ChatGPTに溺れない
- AIを活用するための新たなる創造をつくりだしていく
- 学習を止める必要はない。

# 3. AIの活用について

さまざまな考えがあるが、弊社は積極利用をしていくスタンスを取る。
AIのために、GPUマシンも買うし、環境構築もするし、論文も読む。
原著よんで、ちゃんと分かって使おう。

# 4. 参考

ChatGPT: 
["Improving Language Understanding by Generative Pre-Training" (Radford et al., 2018)](https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/language_understanding_paper.pdf)
["Language Models are Few-Shot Learners" (Brown et al., 2020)](https://arxiv.org/abs/2005.14165)
viteでビルド時、"import.meta.env"という「文字列」がエラーになる場合がある

### 環境
vite: 4.0.0
react: 18.2.0
typescript: 4.9.3

### 起こった事象と対処方法
viteを使用したプロジェクトで`vite build`を実行したところ、以下のようなエラーが出ました。
```terminal
 $ tsc && vite build
 vite v4.0.4 building for production...
 ✓ 154 modules transformed.
 Unexpected token (Note that you need plugins to import files that are not JavaScript)
 file: (省略)
  9: 
 10:   if (!blar) {
 11:     throw new Error('import.meta.env.VITE_BLAR の値が不適切です');
                                                                     ^
 12:   }
 error during build:
```

このエラーは、以下のように`VITE_BLAR`という環境変数を取得している部分で起きています。
```typescript
   const blar = import.meta.env.VITE_BLAR;
 
   if (!blar) {
     throw new Error('import.meta.env.VITE_BLAR の値が不適切です');
   }
```

そのため、初めは「viteでのビルド時に環境変数が取得できていない」という要因によるエラーだと思って色々調べていました。しかし、原因は表題の通りで、`throw new Error()`や`console.log()`などで**文字列の**`"import.meta.env"`を使っていたためにこのエラーが発生しているようでした。
そのため、該当箇所のテキストから`import.meta.env.`の部分を削除すると、エラーは出なくなりました。


### おまけ
色々試してみたところ、`console.log`で`"import.meta.env"`を記述しても、必ずしもエラーが出るというわけではなさそうでした。
試したものの結果を以下にまとめます。
- エラーが出るパターン
    - `.env`ファイルに書かれている環境変数を呼び出している場合
		- `console.log("import.meta.env.VITE_BLAR")` など
  - `"import.meta.env"`のみの場合
  	- `console.log("import.meta.env")`
 	- viteのビルトイン変数のうち、`MODE`と`BASE_URL`の場合
 		- `console.log("import.meta.env.MODE")`
 		- `console.log("import.meta.env.BASE_URL")`
- エラーが出ないパターン
 	- `.env`ファイルにない環境変数を呼び出している場合
 		- `console.log("import.meta.env.VITE_HOGE")` など
 	- `"import.meta"`のみの場合
 		- `console.log("import.meta")`
 	- viteのビルトイン変数のうち、`DEV`、`PROD`、`SSR`の場合
 		- `console.log("import.meta.env.DEV")`
 		- `console.log("import.meta.env.PROD")`
 		- `console.log("import.meta.env.SSR")`

### 参考
- 環境変数とモード | Vite (https://ja.vitejs.dev/guide/env-and-mode.html)
- 【Vite】謎のエラーが発生 | IT土方の奮闘記 (https://it-blue-collar-dairy.com/mysterious-error-of-building-by-vite/)
GitHub Actions extension for VS CodeがBeta公開されたので触ってみる

:::note warn
以降の内容は2023/3/29時点の情報で作成しています。
GitHub Actions for VS Codeはパブリック ベータ版であるため、将来的に動作や仕様が変更される可能性があります。
:::
GitHubは、Visual Studio Codeの拡張機能としてGitHub Actionsによるワークフローの実行や監視、管理を可能にする「GitHub Actions extension for VS Code」のパブリックベータ公開を発表しました。

https://github.blog/2023-03-28-announcing-the-github-actions-extension-for-vs-code/

## インストールしてみる
Marketplaceからインストールできます

https://marketplace.visualstudio.com/items?itemName=github.vscode-github-actions

初期設定ではGithubへの認証と認可が求められます。

機能を確認するには既にワークフローが設定されているリポジトリをcloneし、VS Codeで開きましょう
するとサイドバーのメニューから機能にアクセスできます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206219/fc91362c-0645-5b11-d57b-98e8e5d590b9.png)

## 何ができるのか

### 実行結果の確認
ワークフローの実行結果を確認することができます
例えばPushがワークフローの起動トリガーになっているケースは多いと思いますが、VS Codeで作業してそのままPushまで行い、IDEから離れることなく結果を確認できるのは便利ですね

結果確認も現在のブランチとリポジトリ全体で分かれています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206219/55a8aa34-39ec-bb57-324a-f59e36501bc8.png)

### ログの確認
実行結果の確認はできることが分かったのですが、詳細なログが見たいですよね
機能としては用意されているようなのですが、「View Logs」を実行してもエラーが発生してしまいます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206219/1a08c1da-97c0-13eb-5620-38f4bdf94365.png)

こちらについては公式のIssuesにも挙がっていますので続報を待ちましょう

https://github.com/github/vscode-github-actions/issues/15

### EnvironmentsやSecrets、Variablesを管理することができる
ワークフローの実行に必要な秘匿情報や環境変数を設定することはよくありますが、どんな名前でリポジトリに設定していたかわからなくなって、リポジトリの設定を確認しにいく、なんてことは日常茶飯事です
この機能を使えばどんな変数が設定されているかをIDEから離れず確認できますし、権限があればValueの変更や新規作成もできてしまいます。
これは地味に便利！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206219/87a2ea7b-aa09-900f-41f7-2eba1a053829.png)

### エディタによる開発補助
ワークフローの編集時にはシンタックスハイライトによる分かりやすい表示が行われると同時に、ドキュメントの参照、コード補完とバリデーションも行われます

こいつがかなり賢く、トリガーなどの文脈に応じてバリデーションしてくれたりします
例えば、「push」がトリガーになっている場合は`event.pusher`のプロパティが使えます
※説明も参照できてどんなプロパティかわかりやすいです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206219/73a12981-c634-0cc5-370c-aaddd722f72d.png)

トリガーを「pull_request」に変更すると、そんなプロパティは使えないと警告してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206219/46630fe8-53a3-06b1-0354-41fdc8c46461.png)

さらに先ほどのSecretsやVariablesもサジェストに対応しています
存在しない変数だと警告もしてくれます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206219/b674c14c-c919-0747-2bdc-44c5f210b75b.png)

### 手動トリガーの起動
`workflow_dispatch`イベントを設定すると、手動でワークフローをを実施できますが、こちらの拡張機能からトリガーすることも可能です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206219/adf9b025-c22e-dda2-0433-c73dffb227ed.png)

## さいごに
かなり便利な機能がそろっており、ローカルでの開発から結果の確認（トライ＆エラー）も効率よく実施できそうです
今後のGAやバージョンアップに期待大です！
問い合わせメールを分析してDBに書き込む方法

# 課題
Webフォームからの問い合わせを受け付ける場合、多くが氏名、電話番号、住所、メールアドレスを記載していただくことになります。
そして、その問い合わせは多くはメールとして受信して、その内容を社内の営業システムに記録して対応をしていくことになるのが一般的です。

ただ、この部分問い合わせを受け付けるチャンネルが複数あるとシステム連携が難しくなるものですよね。

これってなんとかメールから分析して個人情報を抽出して営業システムに入れられないものかと考えました。

# アーキテクチャの検討
弊社内ではGmailで問い合わせメールを受け取っており、社内のやりとりはSlackを中心に行なっていたので、Zapierを使ってSlackに問い合わせメール内容を転送していました。

そのため、以下のような方法でAPI Gatewayを呼び出し、Lambdaで処理してDBに書き込むことを検討しました。

![スクリーンショット 2023-03-29 17.40.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1926542/0bd6a2fe-93e1-1626-3d98-476ade7f0795.png)


## GmailからGAS経由でLambdaを呼び出す
メール内容をGASからAPI Gatewayを呼び出す方法です。
Gmailから直接DBに流し込むのでシンプルなフローになります。
ただし、GASはメール受信をトリガーに処理を呼び出すことができないため、ポーリングをし続ける必要があります。

## ZapierからLambdaを呼び出す
Zapierの連携先としてAWS Lambdaを指定することができます。
GUI上で設定ができるのでとても構築がしやすい方法です。
この場合ZapierPremium(有料)の契約が必要になります。

## ZapierからカスタムWebhook経由でAPI Gatewayを呼び出す
Zapierの連携先として自身で構築したREST APIを呼び出す方法です。
こちらもZapierPremiumの契約が必要になります。

## SlackからWebhook経由でAPI Gatewayを呼び出す
Slackで独自アプリを作成してAPI Gatewayを呼び出す方法です。
Gmail→Zapier→Slack→API Gateway→Lambda→DBとフローが長くなってしまうのが難点ですが、AWSサービス以外は基本無料で構築できるのがメリットです。

SlackからAPI Gatewayを呼び出す方法が費用もかからずいいかなと思ったのですが、今回の案件とは別の事業部でSlackではなくGoogleChatを使っていたこともあり、チャットツールへの依存は排除する必要がありました。
そのため、今回はZapierからAPI Gatewayを呼び出す方法を選択しました。

# Lambdaでのメール解析処理の構築
まずはLambdaでのメール解析処理の構築です。
API GatewayおよびLambdaの作成はAmplifyを使いました。

メールの改行、記号などを元に氏名、メールアドレスなどを機械的に抽出するロジックにしています。
言語はTypescriptを利用、単体テストはjestで行なっています。
Lambda関数をTypescript+Amplifyで作ろうと思うと一手間必要になります。

https://www.kwbtblog.com/entry/2021/02/14/005227

こちらを参考にさせていただきました。

# DBへの接続
Prismaを利用したかったのですが、LambdaでPrismaを利用しようと思うとLambdaLayerの構築が必要になります。
今回はスピードを重視したので、mysql2を利用してSQLを直接投げる形にしました。

https://qiita.com/suzukiplan/items/9e1bc829c8c1a8df006c

# API GatewayとLambda関数の作成
API GatewayとLambda関数の作成はAmplifyを使いました。

# ZapierとAPI Gatewayの連携
ZapierのWebhooksアプリからはJsonでメールデータを送る必要があります。
Payload Typeの部分がデフォルトでformになっているのでjsonに変更します。

![スクリーンショット 2023-03-29 11.04.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1926542/a820b414-e53a-697f-3370-f91fd1ee877e.png)


またAPI Gatewayは受け取ったデータをJSON文字列で受け取ってしまうため、Lambda内でparseする必要があります。
これについては以下の記事が参考になります。

https://qiita.com/the_red/items/e5808f3ceda64bef73e4

# E2Eテスト
マネジメントコンソールでのLambda関数単体のテストをしたのち、API Gateway→Lambdaの接続テストを実施。その後Zapier上からテストリクエストを送る形で、Zapier→API Gateway→Lambda→DBのE2Eテストを実施しました。
Lambda関数内でconsole.logを駆使してCloudWatchでログを確認しながらデバッグを行っています。

以上を経て、無事に問い合わせメールと営業システムの連携が完成しました🙌 🙌 🙌

# 今後の検討事項
* 今回は営業システム(SuiteCRM)のDBに直接書き込んでいますが、SuiteCRMのAPI機能を使う方がよりスマート
* メールを解析するロジックの精度を上げていきたい
* DBへの書き込みにprismaを使えないか検討したい
ChatGPTとの会話をMarkdownに変換するブックマークレット

ChatGPTとの会話をMarkdown形式のテキストとして別ウィンドウに表示します。
リスト、ソースコード、表にも対応しました。

```javascript
javascript:(function() {
  if (!document.URL.startsWith('https://chat.openai.com')) {
    alert('Please use on https://chat.openai.com');
    return;
  }
  const sanitize = html => {
    return String(html).replace(/&/g,'&amp;')
      .replace(/</g,'&lt;')
      .replace(/>/g,'&gt;')
      .replace(/"/g,'&quot;');
  };
  const table = element => {
    const rows = [...element.rows].map(row => [...row.cells].map(cell => cell.innerText));
    const header = rows.shift();
    const separator = header.map(() => '---');
    const body = rows.map(row => `| ${row.join(' | ')} |`);
    return `| ${header.join(' | ')} |\n|${separator.join('|')}|\n${body.join('\n')}`;
  };
  const escapeTags = text => {
    return String(text).replace(/<(\/?[a-zA-Z]+)>/g, '`<$1>`');
  };
　const content = element => {
    const tag = element.tagName;
    if (tag === 'OL') return [...element.querySelectorAll('li')].map((li, i) => `${i+1}. ${li.innerText}`).join('\n');
    if (tag === 'UL') return [...element.querySelectorAll('li')].map(li => `- ${li.innerText}`).join('\n');
    if (tag === 'PRE') {
      const lang = element.querySelector('span').innerText;
      return '```' + lang + '\n' + element.querySelector('code').innerText + '```';
    }
    if (tag === 'TABLE') return table(element);
    return escapeTags(element.innerText);
  };
  const messages = [...document.querySelectorAll('div[data-message-author-role]')];
  const text = messages.map((m, i) => {
    const who = m.getAttribute('data-message-author-role') == 'user' ? 'あなた' : 'ChatGPT';
    const elements = m.querySelectorAll('p,ol,ul,pre,table');
    const c = elements.length === 0 ? m.innerText : [...elements].map(e => content(e)).join('\n\n');
    return `## ${who}:\n${c}`;
  }).join('\n\n');
  const w = window.open('', 'target', 'width=640,height=600');
  w.document.write(`<body><pre style="white-space: pre-wrap;">${sanitize(text)}</pre></body>`);
  w.document.close();
})();
```

ChatGPTによる説明:

このブックマークレットは、OpenAIのチャットアプリケーションの画面でChatGPTとの会話履歴をMarkdown形式に変換して表示するものです。以下の手順で動作します。

1. 現在のURLがOpenAIのチャットページであるかどうかを確認します。
2. `sanitize`関数を定義し、HTMLのエスケープ処理を行うために使用します。
3. `table`関数を定義し、テーブルタグをMarkdown形式のテキストに変換します。
4. `content`関数を定義し、各要素をMarkdown形式のテキストに変換します。
5. `.text-base`クラスを持つ要素に対して、ChatGPTとの会話の各部分を取得します。
6. 取得した各部分をMarkdown形式に変換します。
7. 変換されたテキストを新しいウィンドウに表示します。

このブックマークレットは、ユーザーがOpenAIのチャットアプリケーションでChatGPTとの過去の会話をMarkdown形式で表示し、コピーや保存などの用途に使用することができます。
ChatGPTのSlackボットをGoogle App Scriptで作ってみた

## ChatGPT Slackボット on Google App Script
ChatGPTとSlack上でやりとりができるBotを開発してみました。
以下のようにボットにメンションするとスレッドでChatGPTボットが返信してくれます。

![Screen Shot 2023-03-29 at 15.15.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81962/0313aede-e90c-6a6d-b2ab-a5993b820e85.png)

Slack上でChatGPT使いたいなと思って、トライしてみました。
スレッド内ではChatGPTと会話を継続させることができますし、いつもの慣れ親しんだUIでChatGPTが使えるのは嬉しいです。

ソースコードや導入方法は公開してあるので、参考にしてください。
https://github.com/nishiyama-k0130/gas-chatgpt-slackbot


今回の開発ポイントはなんといってもGoogle App Scriptで作ったというところです。
当初はAWS API Gateway + AWS Lambdaで開発してみようと考えたのですが、エンジニアでなくても構築できるという点を実現したみたくて、GASに手を出してみました。

構築方法はGitに公開していますので、今回は苦労ポイントを書きたいと思います。

## 構成
まず構成は以下のようになっています。

![gas-bot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81962/47ce38ca-1224-5e5d-ba30-efd045d439d6.png)

大体の流れは以下のとおりです。
1. SlackからのメンションイベントをdoPost()で受信して、Script Propertiesにイベント内容を登録する
2. Triggerをスケジュール起動させてprocessEvent()を実行
3. Script Propertiesに入っているEventを読み込んで処理（ChatGPT問い合わせ、Slack返信など）をする
4. Script Propertiesから処理したEventを削除する

## 苦労ポイント
### Slackの制限
今回始めてGASに手を出してみて、まずWebアプリが開発できることにビックリしました。これならばSlackからのイベントwebhookを受信することができます。
開発当初はdoPost()内で、ChatGPT呼んでSlackに返信してという一連の流れで開発を進めていたのですが、めんどくさいことが発覚しました。

Slackには、メンションイベントには3秒以内に200のステータスコードを返さないといけないというルールがありました。
以下は[ドキュメント](https://api.slack.com/apis/connections/events-api#responding)の翻訳です。
>アプリは、3 秒以内に HTTP 2xx でイベント リクエストに応答する必要があります。 そうでない場合は、イベント配信の試行が失敗したと見なされます。 失敗すると、3 回再試行し、指数関数的に後退します。 いくつかのベスト プラクティスは次のとおりです。
>自動無効化を防ぐために、60 分あたり少なくとも 5% のイベントの応答成功率を維持します。
>できるだけ早く HTTP 200 OK でイベントに応答してください。
>同じプロセス内で実際にイベントを処理して反応することは避けてください。
>インバウンド イベントを受信後に処理するためのキューを実装します。


ChatGPTのAPIも呼んでいるので、とてもじゃないけど3秒以内に返信はできません。
そこでScript PropertiesというGASの機能を使ってJobQueueとすることにしました。これは言ってしまえばグローバル変数です。
ここにwebhookのイベントの内容を保存しておいて、slackには返事をします。

### GASの制限
このイベントの処理はスケジューラーであるTriggerというGASの機能を使って、順次処理をしていきます。
1分間隔での起動が最小設定でしたので、これを6本仕込んで1分の中でも秒数がばらけるように調整しています。

トリガーをたくさん仕込めば仕込むほど、レスポンスのスピードがあがるので、1分に6本と言わず、10本でも20本でもと当初は思ったのですが、調べたらトリガーの合計実行時間には制限があることがわかりました。

トリガーの合計実行時間は一般ユーザーは90 分(日)、Google Workspaceアカウントは6時間(日)となっています。そこも加味して、1分にトリガー6本をデフォルトとしています。

一般ユーザーの方は、トリガーの本数を半分の3本に減らすか、稼働時間を制限（9時から18時までなど）したほうがいいと思います。トリガーを一気に作成したり、削除したりする関数はスクリプトの中にありますので、それを起動するトリガースケジューラーを作成すれば、稼働時間の調整も可能です。

## 利用ログ

また、利用ログも取得しています。誰がどれくらい使ったのかがわかるようになっています。

![Screen Shot 2023-03-29 at 15.43.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81962/221e21e3-187b-8c39-8693-23137c1b8074.png)



ぜひ皆さんのSlackにも導入して、充実したChatGPTライフをお楽しみください。
Genvid 1.4x以降のUnityプラグイン更新備忘録


# Genvidとは
Genvidは、インタラクティブなライブ配信を実現するSDKです。

![Genvid Logo](https://camo.qiitausercontent.com/350bf13d717131c3273c22b2fc1f2e5540e249d8/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f31393031302f35346262646161652d643334382d643833332d646337642d3438306363643038666334332e706e67)

Genvidは、ゲームの技術を用いてサーバー上でリアルタイム描画した映像を、TwitchやFacebook Gaming経由で動画ストリーミングします。動画視聴サイトから送信された視聴者のボタン入力などをサーバーで上集計し、配信中のコンテンツの展開の変化に使用します。


大勢の動画視聴者が１つのコンテンツに能動的に参加し、物語を作っていく「マッシブリー・インタラクティブ・ライブ・イベント（Massively Interactive Live Events）」を実現します。

いわゆるクラウドゲーミングは1サーバー1プレイヤーですが、Genvidは1サーバーで生成した映像をブロードキャストし、1サーバー対数千人～数万人の体制になります。

https://www.genvidtech.com/ja/

# GenvidのUnityプラグインが大幅更新
Genvid SDKに同梱されるUnityプラグインは、Genvidシステムの初期化と動画のTwitchサーバー送信、、Genvidサーバー経由でのゲームと動画視聴者間のデータ通信を執り行うプラグインです。

Unityゲーム内のデータをクラウドサーバーへ送信したり、クラウドサーバーから送られてきた視聴者操作データを受信してC#のメソッドをコールします。
Qiitaでは何回かGenvidについて紹介をしましたが、バージョン1.4xあたりから、Unityプラグインの構造が変わったため、備忘録として使い方を紹介します。

## プレハブ構造の変化
1.3xより前のGenvid SDK for Unityプラグインでは、複数のプレハブを組み合わせてGenvidの機能を呼び出す形になっていました。まず「Genvid Session Manager」がプロジェクト全体を管理し、その下にぶら下がる「Genvid Session」がビデオ配信やGenvidサーバーとの通信などの一式を保持していました。各設定は「Audio」「Video」「Events」「Streams」「Commands」のプレハブが独立して存在し、「Genvid Session Manager」に紐づける形でした。

![旧GenvidSDKforUnity](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/608f3b7d-31d1-20c3-cb8b-f41d9d24a7e5.png)

実行中に「GenvidSession」を切り替える形で、オンタイムでの設定変更を実施していた形です。
新しいプラグインではプレハブの種類がぐっと減り、機能が集約化されています。

## パラメーターをScriptable Objectで保持
以前のGenvid Pluginでは、ビデオのフレームレート設定やキャプチャ設定について、スクリプトで設定を行うか、シーンに配置したプレハブのインスペクタで設定を行う形でした。

新しいプラグインでは、これらの設定項目をScriptable Objectとして保持し、任意に差し替えが可能になっています。

![app - SampleScene - Windows, Mac, Linux - Unity 203.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/5c0fb6df-7288-ad45-da2a-9e5c36d1d84c.png)

これを「Genvid Session」のプレハブと、「EventsListener」などのUnity C#コードとの繋ぎ込みプレハブに設定します。

### Genvid Pluginプレハブ
以前のGenvid Session, Genvid Session Managerの機能を集約したプレハブです。
後述するEvents, Commands,Streamsプレハブの参照を直接持つのではなく、データIDなどのパラメーターを保持したScriptableObjectの参照を持ちます。

![app - SampleScene - Windows, Mac, Linux - Unity 204.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/ddfb8084-e2e9-5da3-4d4d-cc92be0992d9.png)

### Events Listenerプレハブ
前バージョンの「Events」プレハブに相当します。特定のデータIDがGenvidサーバーから送られてきたときに、コールするメソッドの参照をアタッチします。

以前は直接Event IDをstringで記入していましたが、ScriptableObjectで設定する方式に変わっています。

![app - SampleScene - Windows, Mac, Linux - Unity 20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19010/88069f0d-320a-9617-1262-7057bae0b998.png)

### Streams Listenerプレハブ
以前の「Streams」と同様です。ゲームからGenvidサーバーへ配信したいデータを設定します。

### Commands Listenerプレハブ
こちらも、以前の「Commands」と同様で、Genvidサーバーから送られてきた制御用データを受け取ります。

# 今回のアップデートで設定の差し替えが便利に
以前はプレハブごとロードしなおしていたのが、事前に設定したScriptableObjectの差し替えで設定変更が可能になりました。

少し概念は変わりましたが、C#側のコードに影響はないため、1.3x以前からの以降はコードを変えることなく実施可能です。
二次元配列に円を描く

# 概要
不思議のダンジョンなローグライクゲームを製作中、円形のマップ欲しいな～と思い、二次元配列に円を描くアルゴリズムが無いか探してみた。
無事発見し、きれいな円を描くことに成功した。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319500/72d0d554-9141-c91b-6f59-c5bbe8af02f9.png)

# 参考サイト
https://qiita.com/harmegiddo/items/711c478058aa7a770960

https://fussy.web.fc2.com/algo/algo2-1.htm

https://talavax.com/isinsidecircle.html#gsc.tab=0

# 具体的な方法
### 準備
まずは、円を描くために適当な二次元配列を用意。今回は、0をデフォの値、1を円にしたいところ、とした。
（初期値は一旦0で埋めておく）
```c++
    const int size = 30;
    int area[size][size];

    for (int y = 0; y < size; y++)
	{
		for (int x = 0; x < size; x++)
		{
			area[y][x] = 0;
		}
	}
```

次に、描きたい円の半径と中心点を決める。
今回はテストコードなので、適当に決める。

```c++
    // 半径ｒ
	int r = 10;
	// 中心点
	int x0 = size / 2;
	int y0 = size / 2;
```
### 外周の円を描く
次に、参考サイト１つ目と２つ目にあるように、外周を描いていく。
正直クソコード感が否めない。
```c++
void set_dot(int _x, int _y)
{
	area[_x][_y] = 1;
}
```
```c++
    int x = r;
	int y = 0;
	int F = -2 * r + 3;
	while (x >= y)
	{
		set_dot(x0 + x, y0 + y);
		set_dot(x0 - x, y0 + y);
		set_dot(x0 + x, y0 - y);
		set_dot(x0 - x, y0 - y);
		set_dot(x0 + y, y0 + x);
		set_dot(x0 - y, y0 + x);
		set_dot(x0 + y, y0 - x);
		set_dot(x0 - y, y0 - x);
		if (F >= 0) {
			x--;
			F -= 4 * x;
		}
		y++;
		F += 4 * y + 2;
	}
```
ここまでを描画すると、こうなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319500/398b813a-329c-ab1e-ff0a-1f6654f4c9f0.png)

これで円形の完成。

でも今回は、ローグライクのダンジョンマップ用の円なので、円の内側も埋めていく。

### 円の内側を埋める
この作業に関しては、area[y][x]が円形の内側にあるかどうかを判定すればいいため、円の方程式を利用する。（参考サイト３）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319500/10439b6d-b988-e4b5-4d7e-d3d5bdf49a27.png)

```c++
    for (int y = 0; y < size; y++)
	{
		for (int x = 0; x < size; x++)
		{
			// 判定用
			int lx = (x - x0) * (x - x0);
			int ly = (y - y0) * (y - y0);
			int lr = r * r;
			if (lx + ly < lr)
			{
				area[y][x] = 1;
			}
		}
	}
```
そして、完成。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319500/414a4392-71de-80de-faa2-077322a88a18.png)

ちなみに、外周の円を描かずに、円の内側だけを埋める処理だけでもそれっぽいものが描けるが、ちょっといびつな形になる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319500/2f5b0653-74c3-ca43-8190-4c4781a4ebea.png)
`docker compose up -d`を実行すると`failed to solve ... tcp: lookup auth.docker.io on 192.168.0.1:53: server misbehaving`というエラーが出た話し

## 概要

タイトルのとおりコワーキングスペースで作業していたところ以下のエラーに遭遇して１〜２時間ほど溶けました・・・

```bash
failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition: failed to authorize: rpc error: code = Unknown desc = failed to fetch oauth token: Post "https://auth.docker.io/token": dial tcp: lookup auth.docker.io on 192.168.0.1:53: server misbehaving
```


## 環境

- Macbook 12 Early 2016 / macOS Monterey 12.6.2
- Docker Desktop 4.17.0
- とあるコワーキングスペースのWiFi


## 問題

使っていたとあるコワーキングスペースのWiFiが何らかのセキュア対策によってDockerのイメージをPullする工程が問題が起きた模様（ネットワークはあまり詳しくないため根本的な原因はわからず・・・）。

## 対策

- ①`docker compose up -d`でエラーが発生しないネット環境に変える。
- ②とあるコワーキングスペースのネット環境を調べて対策できないか相談する。

②をやる体力はないので①で解決しましたw
Xserver htaccess

# wwwなしに統一
RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.jp/$1 [R=301,L]
RewriteCond %{HTTP_HOST} ^(www\.example\.jp)(:80)?
RewriteRule ^(.*) https://example.jp/$1 [R=301,L]
SetEnvIf Request_URI ".*" Ngx_Cache_NoCacheMode


#ドメイン変更と特定ディレクトリへ

#hoge.comにアクセスがあった場合、fuga.jp/aaaに転送する
RewriteEngine On
RewriteCond %{HTTP_HOST} ^hoge\.com$ [NC]
RewriteRule ^(.*)$ http://fuga.jp/aaa/$1 [L,R=301]
【Vue.js】watchでdeep: trueを設定するとnewValとoldValが同じになる問題

### はじめに

Vue.js(Composition API)では、watch関数を使用することで、リアクティブな状態の一部が変更されるたびにコールバックを実行することができます。

```ts
const a = ref(0)

watch(a, (newVal, oldVal) => {
  console.log(newVal, oldVal)
})
```

コールバック関数の第一引数（newVal）には、変更後の`a`の値が入り、第二引数（oldVal）には変更前の`a`の値が入ります。

つまり、上記のリアクティブな値`a`に`1`を代入した場合、`newVal`には`1`が入り、`oldVal`には`0`が入ることになります。


### 本題

リアクティブなオブジェクトをwatchで監視する場合、下記のようになります。

```ts
const obj = ref({ a: 1, b: 2 })

watch(
  obj,
  (newVal, oldVal) => {
    console.log(newVal === oldVal);
    // => true
  },
  { deep: true }
);
```

`deep: true`をつけることで、オブジェクトのプロパティの変更時にも、watchが変更を検知してくれるようになります。

しかしここで問題があります。


リアクティブなオブジェクトをwatchで監視する場合、コールバック関数の引数である、`newVal`と`oldVal`の値が同じになってしまいます。


つまり、上記で`obj`のプロパティ`a`に`3`を代入した場合、`newVal`と`oldVal`の値はどちらも`{　a: 3, b: 2　}`になります。(どちらも`newVal`の値になる)


原因として、JavaScriptではオブジェクト型の場合、プリミティブ型と違って値自体ではなく、参照をコピーするため、結果的に同じオブジェクトを参照するからです。


### 解決方法

解決方法としては、スプレッド構文で別の参照のオブジェクトをつくり、その値を返すgetter関数を`watch`の第一引数に指定することです。


```ts
const obj = ref({ a: 1, b: 2 })

watch(
  () => ({ ...obj.value }),
  (newVal, oldVal) => {
    console.log(newVal === oldVal);
    // => false
  },
  { deep: true }
);
```

スプレッド構文を使うことで、オブジェクトを展開できるため、別の参照のオブジェクトをつくることができます。

1点注意点なのが、スプレット構文はディープコピーではなくシャローコピーになるので、階層の深いオブジェクトの場合は、一旦JSONにして戻したり、`structuredClone`やlodashの`cloneDeep`を使ってディープコピーで別の参照のオブジェクトを作る必要があります。

```ts
import { cloneDeep } from "lodash";

const obj = ref({ a: 1, b: { c: 1 } })

watch(
  () => ({ ...obj.value }),
  (newVal, oldVal) => {
    console.log(newVal.b === oldVal.b);
    // => true
  },
  { deep: true }
);

watch(
  () => cloneDeep(obj.value),
  (newVal, oldVal) => {
    console.log(newVal.b === oldVal.b);
    // => false
  },
  { deep: true }
);
```

続いて、下記のようにオブジェクトのみを返すアロー関数の場合、カッコ`()`で囲むことで、`return`と外側の中括弧`{}`を省略することができます。

```ts
// 省略なし
() => { return { ...obj.value }}

// 省略
() => ({ ...obj.value })
```

そして、watchではリアクティブなデータソースであれば、第一引数には下記の4パターンで定義することができます。

- refやcomputed
- リアクティブなオブジェクト
- 値を返すgetter関数
- 上記3つのいずれかの配列


```ts
const x = ref(0)
const y = ref(0)
const obj = ref({ a: 1, b: 2 })

// refやcomputed
watch(x, () => {
// 処理
})

// リアクティブなオブジェクト
watch(obj, () => {
// 処理
})

// 値を返すgetter関数
watch(
  () => x.value + y.value,
  () => {
// 処理
  }
)

// 複数ソースの配列
watch(
  [x, y, obj, () => x.value + y.value],
  () => {
// 処理
})
```

`値を返すgetter関数`を使う場合、ウォッチャーはgetter関数の戻り値が変更されたときだけ起動します。

そのため別の参照のオブジェクトをgetter関数で返すことで、`newVal`と`oldVal`で別々の値がとれるようになります。


最後にもう一度完成コードを載せておきます。


```ts
const obj = ref({ a: 1, b: 2 })

watch(
  () => ({ ...obj.value }),
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  { deep: true }
);
```



もっと良い方法や間違いなどあればご指摘いただけると幸いです。


### 参考

https://ja.vuejs.org/api/reactivity-core.html#watch

https://ja.vuejs.org/guide/essentials/watchers.html
[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content.

nuxt.jsで下記の様なエラーが発生。
クライアントレンダリングでは問題無いが、サーバーサイドレンダリングでは表示が崩れていた。
ローカル上では警告だけで表示できていたものが、本番にデプロイするとレンダリングされず、あわや惨事に。

```
[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content. This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>. Bailing hydration and performing full client-side render.
```

## 原因

下記のようなコーディングがダメみたい。

```html
    <div>
      text
      <span>text</span>
      text
    </div>
```
↓
```html
    <div>
      <span>text</span>
      <span>text</span>
      <span>text</span>
    </div>
```
このようにしたら直りました。