OmniAICreator/Qiita-1.07M huggingface dataset:
【Flutter】データの取得と表示

[chatGPT に立ててもらったスケジュール](ceecf4a987d3f75b6d93)に準じてFlutterの状態管理を勉強。
> - Day 8: データの取得と表示
>    - REST APIからデータを取得する方法を学ぶ
>    - 取得したデータを表示する方法を学ぶ
# APIを使う準備
## httpパッケージを使う
[pub.dev](https://pub.dev/packages/http)から`pubsec.yaml`で使う部分をコピーする。
執筆時点では`http: ^1.1.2`。
``` yaml
dependencies:
  http: ^1.1.2
```
## 依存関係として追加
ターミナルで以下を実行
``` 
$ flutter pub add http
```
# REST APIからデータを取得
``` dart
List<dynamic> _posts = []; // List<dynamic>は動的なリスト

@override
void initState() {
  super.initState(); //ウィジェットの状態を初期化
  fetchPosts(); // キャッシュさせるために変数にしておく
}

Future<void> fetchPosts() async { // Future は 非同期処理で Widget を生成したいときに使う
  final response = await http.get(Uri.parse('https://qiita.com/api/v2/items'));
  if (response.statusCode == 200) {
    final List<dynamic> data = jsonDecode(response.body);
    setState(() {
      _posts = data;
    });
  } else {
    throw Exception('Failed to load data');
  }
}
```
# 取得したデータを表示
``` dart
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Qiita API で記事取得'),
        ),
        body: ListView.builder(
          itemCount: _posts.length,
          itemBuilder: (context, index) {
            final post = _posts[index];
            return ListTile(
              leading: Image.network(post['user']['profile_image_url']),
              title: Text(post['title']),
              subtitle: Text('by: ${post['user']['name']}'),
            );
          },
        ),
      ),
    );
  }
```

# サンプルソース
<details><summary>サンプル</summary>

``` dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<dynamic> _posts = []; // List<dynamic>は動的なリスト

  @override
  void initState() {
    super.initState(); //ウィジェットの状態を初期化
    fetchPosts(); // キャッシュさせるために変数にしておく
  }

  Future<void> fetchPosts() async { // Future は 非同期処理で Widget を生成したいときに使う
    final response = await http.get(Uri.parse('https://qiita.com/api/v2/items'));
    if (response.statusCode == 200) {
      final List<dynamic> data = jsonDecode(response.body);
      setState(() {
        _posts = data;
      });
    } else {
      throw Exception('Failed to load data');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Qiita API で記事取得'),
        ),
        body: ListView.builder(
          itemCount: _posts.length,
          itemBuilder: (context, index) {
            final post = _posts[index];
            return ListTile(
              leading: Image.network(post['user']['profile_image_url']),
              title: Text(post['title']),
              subtitle: Text('by: ${post['user']['name']}'),
            );
          },
        ),
      ),
    );
  }
}

```

</details>

# 参考

https://qiita.com/haru-qiita/items/3aa303c9f76077b36e48

https://zenn.dev/faucon/articles/771b65ccc58b2f
JAVAメモ―breakとcontinueの使い方

```java
class Main {
  public static void main(String[] args) {
    System.out.println("=== while文 ===");
    int i = 1;
    while (i < 10) {
      if(i % 5 == 0 ){
        break;
      }
      
      System.out.println(i);
      i++;
    }
    
    System.out.println("=== for文 ===");
    for (int j = 1; j < 10; j++) {
      if(j % 3 == 0 ){
        continue;
      }
      
      System.out.println(j);
    }
  }
}
```
結果：
=== while文 ===
1
2
3
4
=== for文 ===
1
2
4
5
7
8


break:処理を終了するため
continue：処理をスキップするため
JavaScript ランダム値のゼロパディングを少しだけ短く記述

今更ながらのネタですが、JavaScriptで数値のゼロパディング、例えば`Math.random()`の戻り値を元に`000`～`999`のランダムな整数を得たい場合、先頭に必要桁数の`0`を付けた上で`slice()`で後方から切り出す

```js:
const randomNum = ('00' + Math.floor(Math.random() * 1000)).slice(-3);
console.log(randomNum);
```

`padStart()`を使うなら

```js:
const randomNum = String(Math.floor(Math.random() * 1000)).padStart(3, '0');
console.log(randomNum);
```

辺りが常套手段かと思います。
テンプレートリテラルを使えばもう少し短く、それぞれ

```js:
const randomNum = `00${Math.floor(Math.random() * 1000)}`.slice(-3);
console.log(randomNum);
```

```js:
const randomNum = `${Math.floor(Math.random() * 1000)}`.padStart(3, '0');
console.log(randomNum);
```

といったところでしょうか。

これらを、もう少し簡潔にできないか考えてみます。
0～999の範囲なら`Math.floor()`の代わりにビット演算で整数化する手もありますが、この範囲はあくまで一例なので、とりあえず今回はビット演算は無しで…

`Math.random()` の戻り値が `0.07425549624427696` だったとして、
1000倍して `74.25549624427696`、それに1000を加えて `1074.25549624427696`
そこから 1<font color='red'>074</font>.25549624427696 この部分を切り出す考え方で、

```js:
const randomNum = `${Math.random() * 1000 + 1000}`.slice(1, 4);
console.log(randomNum);
```

もうひとつ、1000倍するのも省略して 0.<font color='red'>074</font>25549624427696 この部分を切り出す考え方で、

```js:
const randomNum = `${Math.random()}`.slice(2, 5);
console.log(randomNum);
```

`Math.random()`が`0.01`や`0`など、切り出したい桁数に満たない値を返す可能性が無いとも限らないので、

```js:
const randomNum = `${Math.random()}0000`.slice(2, 5);
console.log(randomNum);
```

これで良さそうです。

どの方法でも同じ結果になるか、簡単に確認。

```js:
[0, 0.9, 0.09, 0.009, 0.0009, Math.random()].forEach(random => {
  console.log('random: ', random);
  console.log(
    `00${Math.floor(random * 1000)}`.slice(-3),
    `${Math.floor(random * 1000)}`.padStart(3, '0'),
    `${1000 + random * 1000}`.slice(1, 4),
    `${random}0000`.slice(2, 5)
  );
});
```

```txt:
random:  0
000 000 000 000
random:  0.9
900 900 900 900
random:  0.09
090 090 090 090
random:  0.009
009 009 009 009
random:  0.0009
000 000 000 000
random:  0.16843531459923278
168 168 168 168
```

---- 追記

> ```js:
> `${Math.random()}0000`.slice(2, 5);
> ```

この方法は、`Math.random()`の戻り値が`0.000001`を下回った場合に指数表記になってしまうので駄目ですね。

```js:
console.log(`${0.000001}0000`.slice(2, 5)); // 000
console.log(`${0.0000009}0000`.slice(2, 5)); // -70
```

---- 追記終わり

---- 追記2

> Math.random()の戻り値が0.000001を下回った場合に指数表記になってしまう

1を足すことで指数表記になるのは避けられそうです。

```js:
[0, 0.009, 0.0000009, 0.00000000009, Math.random()].forEach(random => {
  console.log('random: ', random);
  console.log(
    `${1000 + random * 1000}`.slice(1, 4),
    `${random}0000`.slice(2, 5),
    `${1 + random}0000`.slice(2, 5)
  );
});

/*
random:  0
000 000 000
random:  0.009
009 009 009
random:  9e-7
000 -70 000
random:  9e-11
000 -11 000
random:  0.8297180866780045
829 829 829
*/
```

ただ、ぱっと見で何のための1なのか分かりにくいので、あまり旨味は無さそう。

---- 追記2終わり

----

別パターンで、日時表記等で正の整数を0埋めしたいケース。

```js:
[0, 1, 10, 59].forEach(num => {
  console.log('num: ', num);
  console.log(
    `0${num}`.slice(-2),
    `${num}`.padStart(2, '0')
  );
});
```

```txt:
num:  0
00 00
num:  1
01 01
num:  10
10 10
num:  59
59 59
```

変な小細工をするまでもなく、単純に従来から使われている`slice()`で後方から切り出す方法や`padStart()`を使う方法で十分簡潔だと思います。
タイムアウトが頻発して泣きを見た話

こんにちは。[CYBIRD Advent Calendar 2023](https://qiita.com/advent-calendar/2023/cybird) 13日目担当の[@cy-seiyan](https://qiita.com/cy-seiyan)と申します。
12日目は[@cy-naull](https://qiita.com/cy-naull)さんによる「[面倒な手作業をPythonに任せる](https://qiita.com/cy-naull/private/e693fcd53a835c247fa9)」でした。
自動化、本当に大切ですね。全て自動化してしまえば手作業で行ってた時間を別の事に使えますしメチャクチャ捗りますね。

## 自己紹介
私は2018年にサイバードに入社をし、主にイケメンシリーズのサーバサイドエンジニアとして活動をしております。直近ではイケメンシリーズの1タイトルでサーバサイドだけでなく入社以前より業務経験を積みたかったUnityを使用したクライアントサイドも担当し某スポーツ選手の如く二刀流のエンジニアを目指しイケメンシリーズを支えております。

## 管理ツール
スマートフォン向けゲームやPC向けオンラインゲーム等、ネットに接続する事が前提のサービスにはサービス内で使用するマスタデータ(ゲーム内のアイテムや敵の強さのデータ等)やユーザの進行状況の確認、ゲーム内に蓄積されたデータの集計等が可能なツール(以下、管理ツール)が用意されていると思うのですが、筆者が現在所属している1タイトルのイケメンシリーズのチームにて使用している管理ツールで発生したトラブルで苦労した話とどの様に解決したのかについてお話しようと思います。

## 集計機能でタイムアウト発生
ある日、所属しているチームにてプロデューサーが管理ツールに実装されているゲームデータの集計機能を実行した際に「タイムアウトが発生してデータ集計が出来ない！」と連絡があり、調査を行いました。

## 何が悪さをしてるのか
プロデューサーが集計しようとしたデータの条件が担当しているタイトルが世にサービスインした日から現在に至るまでのデータという条件で、各イケメンシリーズのタイトルで採用されている管理ツールにも同等の機能があるにも関わらず担当タイトルのみ発生していた為、差異について洗い出しを行いました。
その結果、判明した点は下記になります。

- 他のタイトルと比較し担当タイトルのデータ量は少ない。
- 使用しているサーバのスペックも他のタイトルと同等。
- 集計機能の処理に他のタイトルとの差異は無い。

何故、自分の担当タイトルだけ発生しているのか解らずしらみ潰しで調査を行う事にしました。

## ini_setでメモリ上限変更
エラーログを確認したところ集計時に「**Fatal Error - Allowed memory size of ～～～**」のエラーが出ており集計処理時に使用するメモリが不足していた事から集計処理内にini_setで一時的にメモリ上限を上げるようにし、解決するか試して見たのですが、タイムアウトが発生し更に頭を抱える結果になりました。

## インフラチームに相談
集計処理のソースコードを他のタイトルと比較して差異はほぼ無く、このままだと埒が明かないと判断しインフラチームに相談を行い、発生している事象の説明とインフラ側で心当たりが無いか調査の依頼をする事にしました。
インフラチームと共に事象の解決に向かっている最中、インフラチームからロードバランサのタイムアウト値やswapfile、SELinuxの設定内容に他のタイトルと差異があったと聞き、設定内容を他のタイトルと同じ内容に合わせて貰う＆php.iniのmemory_limitの上限を引き上げて貰い集計処理を実行したところ、タイムアウトは発生せず事象は解決しました。頭を悩ませた問題が解決し気分は爽快でした。

## まさかの悪夢再び
問題が解決してから数ヶ月後、プロデューサーから「またタイムアウトが発生して集計が出来ない！」と連絡があり、再度調査を行う事になりました。
エラー内容を確認したところまたもやサーバのメモリ関連のエラーとの事で、サーバ側の設定・集計処理のコードに他のタイトルと差異が無くどうしたものか…と悩み、プロデューサーに「集計時、サービスインしてから現在に至るまでのデータは必ず必要なのか。」とヒアリングしたところ「1週間の集計か1ヶ月の集計しか取らない。」という回答だったので、抽出するデータの期間を指定してから集計する様に改修を行う事にしました。期間指定で集計する様に改修を行った事でタイムアウトは無事、解消しました。

## まとめ
発生した内容と解決までの道のりはこの記事を見ている方からしたら「何故、サーバ設定とか集計条件的にアウトなのか直ぐに解らなかったのか筆者よ…。」と思った方もいるかもしれません。
期間指定無しでの集計が他のタイトルでは問題無く行えているという事実に囚われていたのとサーバ側設定に目を向けられなかった事が大きな敗因だと思います。しかし、本事象を通して集計する内容や実装されている処理の内容、サーバ側設定に対しても疑ってみる事の大切さを学びました。
もしも似た事象に直面した方に何かしらの参考になれば…と思います。

[CYBIRD Advent Calendar 2023](https://qiita.com/advent-calendar/2023/cybird) 14日目は[@tomoko_ishizaka](https://qiita.com/tomoko_ishizaka)さんの「【rpm,yum,npm,apt,dnf...】結局xxx install系のコマンドは何が正解なのか【パッケージ管理コマンド】」です！
お楽しみに！
地元で勝手に5374.jpを公開してみた

[CivicTech ＆ GovTech Advent Calendar 2023](https://qiita.com/advent-calendar/2023/civictech)　5日目の記事です。

今回はCivicTech1年目の私が地元で5374.jpのローカライズを行った話をしたいと思います。

# 今年から始めたCivicTech
私自身、生まれてから大学卒業までお世話になった地元（岩手県奥州市）にITでなにか還元できないかと考えていました。

また、2022年に現市長・倉成市長がDXの推進の押し進めていくことも公言されており、私自身のCivicTechへのモチベーションとなりました。

そして、各ブリゲードの活動を見ていく中ではじめの一歩として5374.jp(ゴミナシ)のローカライズを行いました。

# 少しだけ奥州市について
私の地元・岩手県奥州市を紹介させてください。
奥州市は、岩手県の内陸南部に位置し、県内で3番目の広さを持つ町です。

2005年に全国的な平成の大合併の流れがあり、奥州市も5つの市町村が合併してできた市になります。

最近では、メジャーリーガーの大谷翔平選手が誕生した町として、市外・県外からも多くの方が訪れています。

![81306024006414.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82566/d0b1fc93-be50-da0e-c63d-400f0db5e0ff.jpeg)
 ※1 奥州市の位置

# 技術について
実際に5374.jpをローカライズしたサイトは[こちら](https://5374foroshu.pages.dev/)です。

ホスティングには、Cloudflare Pagesを使用しています。

理由としては、これまでCloudflareを使用してきたことがなかったこと&他のホスティングサービスに比べて制限が寛容のためです。

本来であれば、自身の勉強も兼ねてクラウドサービスへのデプロイを考えましたが、費用面なども考慮すると包括的かつ無料で提供しているサービスを利用しました。

# つまずいた点
### ゴミ収集カレンダーのフォーマットが統一されていない
上記でも記載している通り、奥州市は5つの市町村から合併しています。

合併前の各市町村で作られたゴミ捨てのスケジュールが合併後にも使われており、
フォーマットが統一されていないため、データの整形が一筋縄ではいきませんでした。

下画像※2のように胆沢地区と江刺地区ではフォーマットが異なります。

![スクリーンショット 2023-11-27 23.15.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82566/08f83c74-35c3-a2e4-91a8-6133e8692261.png)

![スクリーンショット 2023-11-27 23.15.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82566/07ad498b-3663-9deb-335b-d2ffe40750f1.png)

# 所感
### ゆるく進めていくことの大事さ

CivicTechの活動をしている方の多くは、ボランティアで活動している方が多いと思います。
そのような状況でモチベーションを保ちながら開発していくのは難しいです。

CivicTechでは、参加へのハードルを下げることと適度な交流による縁を保ち続けることが大事だと考えています。

# 今後やっていくこと
### デザインの改良
![スクリーンショット 2023-12-04 13.20.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82566/a361b224-f9dc-0848-f0e5-fc64c68b2773.png)
※3 5374.jp for 奥州（β版）

奥州市では高齢化率が35%(全国平均28%)となっており、年配の方にも利用してもらいたいと考えております。
そのため、文字の大きさや操作性などに関してはわかりやすくガイドする必要があるため、アクセシビリティは一番の課題としています。

### 燃えるゴミ/燃えないゴミ以外の種類への対応

今回はリリースを優先してスピード重視で実装してきました。
ゴミの種類を「燃えるゴミ」と「燃えないゴミ」の2種類のみ対応しています。
今後はリサイクル品やその他ゴミに関しても対応できたらと思っています。


### 仲間の募集

私の理想としては市民と自治体が手を組んでITの推進・街の発展に寄与できればと思っています。

そのためには、一緒に活動してくれる仲間の募集とCode for Oshuの立ち上げを目指していきたいと思います。

# 出典
- ※1. [コトバンク - 奥州(市)](https://kotobank.jp/word/%E5%A5%A5%E5%B7%9E%28%E5%B8%82%29-1511740)
- ※2. [奥州市 - ごみ・リサイクルの出し方、収集日程](https://www.city.oshu.iwate.jp/soshiki/5/1051/2/3/316.html)
- ※3. [5374.jp for 奥州（β版）](https://5374foroshu.pages.dev/)
excelファイルをPythonでつくる

[excelファイルをPythonから扱う方法](https://interuniversitylearning.com/archives/4012)が説明されていたのでやってみた。
## openpyxlのインストール

openpyxlをインストールするにはpip installを使う。
```
pip install openpyxl
```


## openpyxlをインポートしてワークブックを作成
openpyxlがインストールされていれば`import`でインポートできる。
Workbook()でワークブック作成できる。
```
from openpyxl import Workbook
# 新しいワークブックを作成
workbook = Workbook()
```

## ワークブックを保存
このように作ったworkbookは.saveを用いて保存できる。
```
workbook.save(filename="new_file.xlsx")
```
Django DEBUG=FalseでSQLのログを出力する

先人の知識から、

https://tokibito.hatenablog.com/entry/20140831/1409495967

を試してみましたが、djangoのバージョンが違うので、動かない・・・

- django 3.2
- python 3.9

```middleware.py
class UseDebugCursorMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        self.process_request(request)
        response = self.get_response(request)

        return response

    def process_request(self, request):
        from django.db import connection

        connection.force_debug_cursor = True
```
```settings.py
MIDDLEWARE = [
    "myapp.middleware.UseDebugCursorMiddleware",
    ・・・
```

で、多分OK

```diff
-　　　　　　　　　　　　　　　connection.use_debug_cursor = True
+　　　　　　　　　　　　　　　connection.force_debug_cursor = True
```
なんですね。

ちなみに、複数のスキーマがあるときには、こんな感じになると思います。
```python
    from django.db import connections
    for connection_name in connections:
        connections[connection_name].force_debug_cursor = True
```
VS Codeのindent-rainbowは不要になりました！

こんにちは、GxPの土田です。
この記事は[グロースエクスパートナーズ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/gxp)の5日目です。
あっさりめの記事なので前置きはほどほどに早速本題です。対戦よろしくお願いします。

## indent-rainbow
VS Codeでインデントをわかりやすく色分けしてくれる拡張機能の [indent-rainbow](https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow) 、
使っている方も多いのではないでしょうか。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586144/910133ff-7987-508d-304a-fbb2e35346d4.png">

長年感じていた不満なのですが、この拡張機能はインデント（空白文字）を判定して色を付けているだけなので、↓のように「同じブロック内だが、空改行を入れた行」でハイライトが途切れてしまいます。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586144/e58a5139-efc6-365d-cb8f-a50d2775972d.png"/>
空白を残せばハイライトされますが、大体のフォーマッタは空行に空白を入れると勝手に消してしまいます。

そこで、indent-rainbowを使わなくても色を付けて見やすくできる、かつ↑の問題点も解消できる機能があるので紹介します！

## 前提
かなり最近追加されたオプションなので、[July 2023 (version 1.81)](https://code.visualstudio.com/updates/v1_81) 以上が前提となります。

## やり方
settings.jsonを開く（参考：[VS Codeのsettings.jsonの開き方](https://qiita.com/y-w/items/614843b259c04bb91495)）。

↓を追記する。
```settings.json
"workbench.colorCustomizations": {
    "editorIndentGuide.background1": "#b22222",
    "editorIndentGuide.background2": "#ff4500",
    "editorIndentGuide.background3": "#ffd700",
    "editorIndentGuide.background4": "#228b22",
    "editorIndentGuide.background5": "#000080",
    "editorIndentGuide.background6": "#8b008b",
    "editorIndentGuide.activeBackground1": "#fff",
    "editorIndentGuide.activeBackground2": "#fff",
    "editorIndentGuide.activeBackground3": "#fff",
    "editorIndentGuide.activeBackground4": "#fff",
    "editorIndentGuide.activeBackground5": "#fff",
    "editorIndentGuide.activeBackground6": "#fff"
}
```
↑の色は私が使っている虹色のパターンです。

これを指定すると↓のような感じでブロックごとにハイライトされるようになります。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586144/e0066048-a70e-c2e9-2219-94dd97b449ad.png"/>

ちゃんと空改行でも途切れてない、、！
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586144/1b6fd92f-3f15-df00-9cdc-55a58c8c530c.png"/>

## ちょっと詳しく

6色まで設定できます。指定した個数以上のインデントレベルは指定した色がループします。

`editorIndentGuide.background` がラインの色、
`editorIndentGuide.activeBackground` がカーソルがあるブロックのラインの色です。

`editorIndentGuide.background` と `editorIndentGuide.activeBackground` はセットで指定する必要があります。

v1.81より前のバージョンの場合、

```settings.json
"workbench.colorCustomizations": {
    "editorIndentGuide.background": "#f00",
    "editorIndentGuide.activeBackground": "#fff"
}
```
のように一色しか指定できません。

## 他にも、、
今回指定した `workbench.colorCustomizations` ですが、他にもたくさんの色を指定できます。
↓ `Ctrl + Space` で候補表示。

![_(3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586144/cb54f8a3-9ec8-4ee5-0b98-3d3275c7e8d0.gif)

おそらくVS Code上でデフォルトで配色されているものであればなんでも色を変えられます。
（拡張機能での配色は `workbench.colorCustomizations` では設定できません）

## 最後に
この機能、本当に読みやすくなるのでぜひ使ってみてください。
色んな配色をいじって自分好みのVS Codeにするのも楽しいので、カスタマイズしたいものを探してみてはいかがでしょう。
日付の比較処理を見かけたら UTC+ いくつか意識してレビューしたいお話


# UTC+なんなのか意識してレビューとコーディングをしたい

`Time.current.class` は `=> ActiveSupport::TimeWithZone` であり現地時間を取得できる
 `Date` 型は 世界標準時 `UTC + 0` を参照する

そのため `to_date` を用いて型変換しない場合 は `UTC+9` の時間を追加すると比較できる

```ruby
[78] pry(main)> product.start_date
=> Wed, 29 Nov 2023

[79] pry(main)> product.start_date <= current_day + 8.hour
=> false

[80] pry(main)> product.start_date <= current_day + 9.hour
=> true
```

境目は 8時間59分59….. ＋9時間すると正しく動作する

# 実際にあった怖い話

DateTimeオブジェクトとTimeオブジェクトをコード内で同列に扱って
日付判定ロジックでミスった話

公開日が 　11/29日
今日の日付 11/29日
```ruby
current_day = Time.current.beginning_of_day
product.start_date = Wed, 29 Nov 2023
```

シンプルに気持ちだけ伝えると
`start_date == product.start_date` は `True` になってほしい
意識せずレビューしていたら、適当にLGTMしてしまいそうになる。

```ruby
[48] pry(main)> product.start_date == current_day
=> false

[49] pry(main)> product.start_date
=> Wed, 29 Nov 2023

[50] pry(main)> current_day
=> Wed, 29 Nov 2023 00:00:00.000000000 JST +09:00
```

現実はそうもいかず
同じ日付でも型が違うと悲しい気持ちになる

DBに時間指定なしで日付だけ保存している場合 `Wed, 29 Nov 2023`  
`to_date` で変換しないと正しく比較できない

```ruby
[49] pry(main)> product.start_date
=> Wed, 29 Nov 2023

[54] pry(main)> current_day.to_date
=> Wed, 29 Nov 2023

[53] pry(main)> product.start_date == current_day.to_date
=> true
```


## デバッグも比較演算子バグかな？という目線で見てしまう

```ruby
product.start_date <= Time.current 
```
このように書いていて、コード的には 開始日が今日以降かどうかという感じで
演算子が原因でバグかなー？という目線でコードをみていると

比較演算子のバグじゃないから別の部分のエラーかな？と罠にハマりそうになりますね
Re ： 1からはじめるSvelte入門【part 1】


# はじめに

本記事は、svelte 初心者が一から入門して、Web アプリを１人で構築できるようになるためのハンズオン形式の投稿です。

# 想定読者

:::note warn

以下の知識がある前提で進めていきます。

- HTML、CSS が分かること
- バックエンドは解説しないので、何かしらの言語でバックエンドの処理を書けること
- Node.js の環境があること

:::

- バックエンドエンジニアだけど、フロントエンドは苦手な方
- Web エンジニアを目指す方
- Svelete に興味を持った方

# 環境

開発環境は、以下の想定で進めていきます。

- OS :
  - ProductVersion : 14.0
  - BuildVersion : 23A344
- Node.js : v18.16.1
- npm : 9.5.1
- Svelte : 4.0.5

# 参考リポジトリ

https://github.com/o-ga09/tutorial-Svelte/commits/

part 毎のソースコードをタグで作成しておりますので、part 毎のソースコードが必要な方はダウンロードしてご使用ください。

# やること

<font color="red">**最終目標**</font>は、AWS を使用したフロント＋バック＋認証付きの Web アプリを作成できるように
なること。

# 始める前に

## Svelte とは？

＞＞公式引用

> Svelte とは permalink
> 手短に言えば Svelte は、ナビゲーションバーやコメントセクション、コンタクトフォームなど、ユーザーがブラウザで見たり操作したりするユーザーインターフェースコンポーネントを書く方法です。Svelte コンパイラは、コンポーネントを、ページの HTML をレンダリングする実行可能な JavaScript と、ページのスタイリングをする CSS に変換します。このガイドの残りの部分を理解するのに Svelte を知っておく必要はありませんが、もし知っていれば役に立つでしょう。より詳しく知りたい場合は、Svelte のチュートリアル をご覧ください。

Web アプリを作る際に、必要な UI コンポーネントを作成するためのツール。

## SvelteKit とは？

＞＞公式引用

> SvelteKit は、Svelte を使用して堅牢でハイパフォーマンスな web アプリケーションを迅速に開発するためのフレームワークです。もしあなたが React 界隈から来たのであれば、SvelteKit は Next に似ているものです。Vue 界隈から来たのであれば、Nuxt に似ています。

公式にも記載のある通り、React というところの Next.js、Vue いうところの Nuxt.js にあたる Web フレームワークです。

## Svelte + SvelteKit を使用すると何がうれしいのか？

- Bundle サイズが小さい
- 少ない手間で効率的な Web アプリを構築できる
- ビルトインで色々な処理が書ける

# 第１回目

## 環境構築 + Hello World

### 想定作業時間

- 15 分

### 成果物

![スクリーンショット 2023-12-04 22.30.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1312905/d24b7b49-daf9-71bd-4ffe-d553a8b953ce.png)

### 使用する構成

- Svelte
- SvelteKit

## 手順解説

<details><summary>解説を見る</summary>

1 . Svelte プロジェクトを作成する

```bash
npm create svelte@latest [好きなプロジェクト名]
cd [プロジェクト名]
npm install
```

<br>

2 . 一度、公式が用意したサンプルが動作するか確認する

```bash
npm run dev
```

<br>

3 . 不要なファイル削除する

- about ディレクトリ
- sverdle ディレクトリ
- src/Counter.svelte

<br>

4 . Hello World を書く

```html:src/routes/+layout.svelte
<script>
</script>

<div class="app">
	<main>
		<slot />
	</main>
</div>

<style>
	/* root layout style */
	main {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 100%;
		height: 100vh;
	}
</style>
```

```html:src/routes/+page.svelte
<script>
</script>

<svelte:head>
	<title>Home</title>
	<meta name="description" content="Svelte demo app" />
</svelte:head>

<h1>Hello World !</h1>

<style>
	h1 {
		text-align: center;
	}
</style>
```

```css:src/styles.css
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
	font-family: 'Times New Roman', Times, serif;
	color: black;
}

```

</details>

# まとめ

いかがだったでしょうか？これから、私自身、Svelte を使用して Web アプリ構築するために本記事を投稿していこうと考えております。
よろしければ、いいね、フォローよろしくお願いいたします。

一緒に、Svelte について学んでいきましょう！
Postman : Salesforceで使う時の処理手順(既に一度接続した環境に対して)

[Postmanのまとめに戻る](https://qiita.com/Keiji_otsubo/items/84098dc24465d5787ea6)


未だに毎回悩みます。既に１回以上接続している場合。

# トークンの取得 (新しいトークンを取得することによって Playground を Postman に接続します。)

1. Salesforceの環境にログインします。
2. Postman Web アプリケーションにログインします。
3. コレクションを選択します
4. Authorizationタブをクリックして、下にスクロールすると Get New Access Tokenがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/46140345-57d4-d2fd-6a6a-245f06110cf6.png)

5\. Get New Access Tolenをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/b469096c-59d5-f3bc-b79f-e72e32e92539.png)

6\. Salesforceの認証画面で認証します。
7\. Use Tokenをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/60b280d3-78d9-c523-f472-7ad209fb77ec.png)


# 接続の確認 

1. REST GET Limits リソースを使用して接続が機能していることをテストします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/43c95628-8576-617d-6781-d2f35eab196e.png)

この場合はトークンを取得できてない。

```
[
    {
        "message": "Session expired or invalid",
        "errorCode": "INVALID_SESSION_ID"
    }
]
```
Bardに献立を立ててもらいました。

# 前置き
最近、ChatGPTよりBard推しなのでBardの記事を書きました。

~~当初はGit submoduleの記事を書こうと思ったのですが、Bardに記事の添削してもらった時に記事にしないでBardに聞けばいいじゃんという気持ちになってしまいモチベが消えたのが本音~~

# Bardとは

## Bard自身に何者なのかきいてみました。
> Bard は、Google AI が開発した大規模言語モデルです。テキストとコードの膨大なデータセットでトレーニングされており、テキストを生成し、言語を翻訳し、さまざまな種類のクリエイティブ コンテンツを作成し、有益な方法で質問に答えることができます。 

## 筆者が思うBardのいいところ
・レスポンスが早い！（←個人的にこれが一番うれしい）
・画像を張って質問ができる
・参考文献のURLを引っ張ってきてくれる
・Googleスプレッド形式の提案してくれる


## 近年のUpdate情報
画像検索ができるようになったのは2023年の5月からのようですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982130/110507f1-e6f5-5c91-aadc-5610127c999c.png)


筆者がこの記事を書いている時点での最新のUpdateでは悩める若者にBard神が救いの手を差し伸べてくれるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982130/e099607e-1719-8f17-3f3d-de4352d90d2a.png)



# Bardに献立を立ててもらいました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982130/6238d009-f359-feb3-fad8-3dfcc7780fa0.png)


この時点で自分がバナナを嫌いなのを思い出しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982130/7b79b738-a13d-4c90-04ba-d5abf0a866df.png)



# まとめ

将来的にスプレッドシートが扱えるようになれば、減量中の情報を表形式で保持、Bard上でインポートしてレビューしてもらったりと活躍の幅が広がるなと思いました。

GoogleAppSheetとかと連動して個人アプリをつくって、スプレッドシートにデータを蓄えてBardで対話的に解析等をかけるようなアプリを作りたくなりました！！


:::note
会話は、Bard で使用される技術の改善のため、人間のレビュアーによって処理されます。見られたくない内容や使用されたくない情報は入力しないでください。
ChatGPTの利用規約を確認する

# ChatGPT：ねーねーパパー、今日ママがね、パパには内緒にしてって言われたんだけどね、今度ママと二人で、おいしいもの食べに行くんだー

2023年12月1日　生成系AIという単語がユーキャン流行語大賞を受賞しました。

当社でも、ChatGPTが流行り始めたタイミングで、生成系AIに関しては、早急に利用していきたいという要件が多く寄せられていた。

現情報漏洩とか話題になってるんだけど、会社で利用していいの？　的なことを相談されることも多く、その際に調査した内容や、いわゆる情シス的な役割の私が感じたことやをつらつらと書き残したいと思います。

## 生成系AIブームの到来
　2023年になって急にメディア等で騒がれるようになったChatGPT、社内でもエンジニアを中心として利用したい！という声が大きくなっていました。テレビ等でも、ChatGPTを使って業務を効率化しました！的な報道がされるようになると、非エンジニア職の方もこぞって利用したいといった声が上がってきました。

ChatGPT君は、事前に学習したデータのほかに、入力データを学習用のデータとして利用し、さらなる進化をしていくという、自己学習のできる、とても頭のいい子です。頭がいい子なんですが、知りえた情報は、他にお漏らしちゃう子らしいです。

なので、社外秘情報等をChatGPT君にお話をすると、お漏らしをしちゃうかも？　そうだったら、会社で利用させるのまずくね？ってことで、本当に使っていいのか利用規約を調査をしていきました。
　※いったん利用規約等は2023年6月ごろに確認した時の見解です。

## ChatGPT
　・13歳以上で保護者の同意を得られていること、18歳以上であること・・・まぁ社員はみんな成人してるから大丈夫だね！
　・違法なこととか、有害なこと、虐待的なことには使わないでね！・・・まぁ常識の範囲だけど、ちゃんと使う人には注意喚起しておこう
　・作ったコンテンツは、ChatGPTが作ったけど、命令したあなたが作ったことにするからね！　ChatGPTが嘘をついても、責任は命令したあなただからね！・・・これ結構重要です！
　・もらった入力データと出力データは学習に使わせてもらうからね！・・・困るなぁ
　・API経由で使ったものは、学習用データには使わないよ！・・・おお！いいじゃないですか！
　・学習用データには使わないけど、入力したデータは一定期間チェックのためにため込むし、お漏らししても、ChatGPTのせいじゃないよ！・・・ぇ？

って感じでした。
　社内で検討の結果、学習用データには使わないとは言ってるけど、勝手にお漏らしされるのは困るという事で、導入は断念となりましたが、マイクロソフトさんがAzure上でChatGPTの展開をしていて、利用ができるとのことで、こちらも確認してみました。

## Azure OpenAI
　・ChatGPTのインスタンスを契約毎にAzure上に構築するから情報漏洩しないよ！・・・いいじゃん！
　・本家のChatGPTの新機能はすぐには使えないよ・・・とりあえず大丈夫です！
　・お漏らししちゃった場合は、既存のAzureの契約にて包括されているDPA契約の内容にて対応します・・・とりあえずマイクロソフト側が原因ならマイクロソフトがなんかしてくれる！
　・監視用として保存はするけど、条件によってはオプトアウトも可能ですよ！・・・今後もしかしたら利用させていただくかと思います。

ということでしたので、当社では、Azure OpenAiを利用して、生成系AIを利用することとなりました。

## あとがき
Github Copilotや、O365Copilot、Google Duet AI、Amazon Q等 ビジネスで利用可能な生成系AIが出てきますが、今後も社内にて安全に利用できるか確認しつつ、有効活用できればと思っています。

プログラムを全く書けない人が、社内向けにAzure OpenAIを使って、社員用ChatGPT利用UIを作った話は、要望があったときにでも書きます。

## FYI GoogleBard
　・Bard との会話に機密情報を入力しないでください。また、レビュアーに見られたくないデータや、Google のプロダクト、サービス、機械学習技術の向上に使用されたくないデータも入力しないでください。・・・承知しました。利用しません。
Daniel Velasquezさんのシェーダー入門を紹介してみる


こんにちは。静岡アドベントカレンダーに、引き続き若干孤独に投稿を続けるはるまきです。
よかったら皆さんもよろしくお願いします。
いつもの皆さんの元気な様子が知りたい。初めての方とのwebならではの交流を楽しみたい。そう願ってやみません。

さて、今日は、Daniel Velasquezさんの「INTRO TO SHADERS」が楽しかったので紹介してみたいと思います。（ちなみに有料でメール講座をされている方のようで、PRも一緒に送られてきます。念のため付記。）

# 早速試してみる

こちらのURLにアクセスして、

https://offscreencanvas.com/courses/intro-to-shaders

メールアドレスを入力したら、メールが届きます。
何回かに分けてメールは届きますが、初回の内容に含まれるgithubプロジェクトからcloneして動かせば、すぐに動くshaderが体験できました:beers:

# Daniel Velasquezさんの名言をピックアップして紹介

メールの内容が面白いです。
shaderを初体験する以上の価値が、メール文面にある気がします。
ちょっとピックアップして紹介してみます。

##### 1通目のメールより抜粋

> DEBUGGING IS A VISUAL PROCESS
Colors are numbers, and numbers are colors.

デバッグは見た目でわかるようにして確認してみるのがわかりやすいよ。
GPUで一度に何千ものピクセルを動かすのだから、視覚的なデバッグの勘を養うといいよ。


##### 3通目のメールより抜粋

> To be clear, I have ideas of how to make certain parts of my vision. But when I'm coding, I'm as clueless as they come about how it's gonna work. What I initially planned never works, and the end result I never make what I intend to make.

> But while failing at what I envisioned, a new idea comes along and I often follow it. It's a matter of trusting the process and experimenting. These breakdowns are very much me going back and fully understanding what I did because in the moment I had no clue. Not a “I knew all along…. my grandpa's deck has no pathetic cards😎” moment.

> I just plugged values until it worked🙉

作るものについて、最初に計画はしているけど、うまくいかなかったり、作っている間にアイデアが思いついたりする。
かなり試行錯誤して「うまくいくまで値を突っ込んだ」りする。
実験中に思いついたものがいかに多いかわかるだろう。


##### 4通目のメールより抜粋

> Sine and Cosine are math functions. Not only that, they are trigonometric functions. Really. Spooky. Stuff. But for our purposes, all you need to know is this: Sine and cosine are functions that create infinite waves and circles like you saw in the last lesson:

サインとかコサインとかめっちゃきもい。
けど私たちに必要なのはこのこと：サイン、コサインは、無限の波と円を作り出す。


##### 5通目

５通目からは特に抜粋しないけど、ここでMixや歪みについて教えてもらえます。
あと、メールの中にクイズがあった（１通目にもあった。他のメールにもあったかな？...なかった気がする）。


##### ６通目（さいご）

> It took a couple of iterations to make a sketch that looked decent both in grid and non-grid. But the tools we're gonna learn in this lesson are gonna help you create and iterate on grid sketches.

何度か繰り返して試してみて、ちゃんと見られるものにできた、と言ってくれているみたいです。
心折らず、繰り返しよくなるまで試す...！


# そんな感じで

何か面白いの作ってみたいな。
モブプロを1ヶ月実践した結果

## はじめに
最近モブプロを業務で実施し始めていたので現状のやり方とかをまとめてみました。
現状どのようなアプローチを行うこととしたかなどありきたりなことになっているかもしれませんが整理のためにも残しておきます。


### 進め方
現在はリモート勤務が中心となっており、主に下記のツールを使って開発時の情報共有やコミュニケーションを取っている
##### Discord
ドライバーとなっている作業メンバーの画面の共有
会話等もDiscordを使って実施

##### codewithme
コードの共有
並列作業,ドライバーの交代時にそのまま継続して作業ができる

##### miro
要件の整理する際のホワイトボード替わりとして利用
朝会夕会でのタスクの整理とかもここに書き出し



### やってみて良かったこと
１ヶ月ほど実施して,モブプロを始める前後でここ変わったなと思うところ

##### 新しいメンバーでも知識の前提が整う
特に現行システムの仕様等共有しながら進めることができている

##### 詳細の詰めかたなど相談がしやすい  
課題が見つかり要件を再検討しなければいけなくなったときとかその場で方向を決めて進めることができている

##### 開発が止まることがない
別のMTG等で参加できない間もモブとドライバーが引き継いで作業を行うため止まることなく進められている



### トライしていること
振り返りなどで話題に上がりトライアルとして実践していること

##### 朝会、夕会のタスク確認
その日にやる内容を具体的な作業としてリスト化して終了したらタスクを終わったことを整理
共通認識を持つことができるので途中退席していても戻ってきたときにどこまで進んでいるかがわかりやすくスムーズに合流できている
夕方にはgood,bad,actionの簡易的な振り返りを行って明日の進め方を決定している


##### モブファシリテーターの採用
話し始める人が固定してしまって負担が偏ってしまっていたため、ドライバーとかと同じように完全ローテーションでファシリテーターのような役割を行う人を立てることにした
基本はモブのメンバーは全員でコミュニュケーションを取るが、会話の切り出しや、他のモブの人に会話をふるなどを行う



### 終わりに
初心者が落ちいる失敗にハマっているかもしれませんが現時点での感触としては良好でした。
現時点では3人と小規模なプロジェクトなため、意思疎通が比較的とりやすい環境であったことも
プロジェクトのサイズなどによってもやり方などは変えた方が良いかもしれないとは思いますが、今後も続けてみて改善のPDCAとかを回していけたらと思います。
Amazon Linux 2 をローカライズする

こんにちは、主に、インフラエンジニアの皆さん！今日は、Amazon Linux 2を東京に最適化するための重要な設定について解説します。これらの設定を行うことで、サーバー運用体験を向上させ、ユーザーにとってより快適な環境を提供できます。

### **ホスト名のカスタマイズ**

まずは、ホスト名の変更から始めましょう。これは、サーバーを識別しやすくするための重要なステップです。

- 現在のホスト名の確認
    
    ```bash
    hostname
    ```
    
- ホスト名の変更
例えば、ホスト名を `my-tokyo-host` に変更したい場合、以下のコマンドを実行します。
    
    ```bash
    sudo hostnamectl set-hostname my-tokyo-host
    ```
    
- 変更の確認
    
    ```bash
    hostname
    ```
    

### **タイムゾーンの設定**

次に、タイムゾーンを東京に設定します。これにより、ログファイルやスケジュールされたジョブが地元時間で実行されます。

- **現在のタイムゾーンの確認**:
    
    ```bash
    timedatectl
    ```
    
- **タイムゾーンの変更**:
    
    ```bash
    sudo timedatectl set-timezone Asia/Tokyo
    ```
    
- **変更の確認**:
    
    ```bash
    timedatectl
    ```
    
    ```bash
    Local time: 月 2023-12-04 12:29:38 JST
      Universal time: 月 2023-12-04 03:29:38 UTC
            RTC time: 月 2023-12-04 03:29:39
           Time zone: Asia/Tokyo (JST, +0900)
         NTP enabled: yes
    NTP synchronized: yes
     RTC in local TZ: no
          DST active: n/a
    ```
    

### **言語設定の調整**

システムのデフォルト言語を日本語に設定することで、システムメッセージやコマンドラインツールがより理解しやすくなります。

- 言語設定の変更
    
    ```bash
    export LANG=ja_JP.UTF-8
    ```
    

### **キーボードレイアウトの変更**

物理的または仮想的なキーボードを使用する場合、キーボードレイアウトを日本語に設定することで、入力がよりスムーズになります。

- **キーボードレイアウトの変更**:
    
    ```bash
    sudo localectl set-keymap jp
    ```
    

### **まとめ**

これらの設定を行うことで、Amazon Linux 2サーバーを日本の東京に最適化し、効率的で快適な作業環境を実現できます。地域に合わせた設定は、サーバーのパフォーマンスとユーザーエクスペリエンスの両方を向上させるために重要です。ぜひ、これらの設定を試してみてください！
YouTubeの広告を早送りでスキップするChrome/Edge拡張機能

YouTubeの広告を自動的にミュート＋早送りする拡張機能を作ってみた。

## コード

```js:content.js
let video
main()

function main(){
    video = document.querySelector('#movie_player video')
    video ? new MutationObserver(mutated).observe(movie_player,{attributes:true}) : setTimeout(main,100)
}

function mutated(){
    if(movie_player.classList.contains('ad-showing')){
        video.muted = true
        video.playbackRate = 16
    }
    else{
        video.muted = false
        video.playbackRate = 1
    }
}
```
`#movie_player`タグの属性変更を検知して、広告表示中なら動画速度を上げたりミュートにしています。

短いコードなので、JavaScriptを知ってる人ならすぐ理解できると思います。
1つだけ補足すると、唐突に出現する`movie_player`とは、そのidを持つ要素のことです。

## 使い方
Edgeでのインストール方法を紹介しますが、Chromeでも大体同じです

1. メニューの拡張機能
2. 拡張機能の管理
3. 開発者モードをチェック
4. 展開して読み込み
5. content.jsとmanifest.jsonが入ったフォルダを指定する

```json:manifest.json
{
"name": "YouTube Ad Mute",
"version": "1.0",
"manifest_version": 3,
"content_scripts": [{
    "matches": ["https://*.youtube.com/*"],
    "js": ["content.js"]}]
}
```

※開発者モードにチェックを入れると、2週間毎に確認のダイアログが表示されます

## メモ
📝スキップボタンの自動クリックは実装してません。アウトらしいので。

📝早送りは16倍速です。[Ad Speedup](https://chromewebstore.google.com/detail/ad-speedup-skip-video-ads/pcjlckhhhmlefmobnnoolakplfppdchi)の真似ですｗ

📝videoタグをポーリングする所はPromise化すべきですが、トップレベルのawaitが動かなかったので断念した。

```js:ポーリングquerySelectorは没になった
function $(selector){
    function async(ok){
        const el = document.querySelector(selector)
        el ? ok(el) : setTimeout(async, 100, ok)
    }
    return new Promise(async)
}
```
Kubernetes v1.24 からServiceAccountには自動でSecret Tokenは生成されなくなったことによる問題対応


# Kubernetes 変更点

Kubernetes v1.24からServiceAccountを作成しても自動でSecret Tokenは生成されなくなった。

__[公式: 1.24.0 release notes 該当箇所](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#:~:text=%40lobziik%29-,The%20LegacyServiceAccountTokenNoAutoGeneration%20feature%20gate%20is%20beta%2C%20and%20enabled%20by%20default.%20When,by%20following%20this%20guide.%20(%23108309%2C%20%40zshihang),-The%20calculations%20for)__
  
# 事象

[ArgoWorkflowサーバ](https://argoproj.github.io/argo-workflows/argo-server/)にて、v1.24以降に作成したTokenが存在しないServiceAccount紐づけユーザーでログインするとRoleの機能を実施することができずエラーになった。(Tokenが無いためRBAC認可の内容を実施できない状態であった。)

エラー内容

エラ-1

```
{"code":7,"message":"not allowed"}: not allowed
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/91562/affc9b99-3377-9495-fe7d-e674fb87098d.png)

エラー2

```
Connection closed to api/v1/workflow-events/...
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/91562/e9c22a61-2591-e448-1517-face4feab158.png)

# 対応

ServiceAccount用のSecretリソースを明示的に作成するようにすることでTokenがServiceAccountに付与されるようになった。

* [Kubernetesドキュメント](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-long-lived-api-token-for-a-serviceaccount)
* [ArgoWorkflowドキュメント](https://argoproj.github.io/argo-workflows/manually-create-secrets/#option-1-discovery-by-name)


ServiceAccount (既存で存在している)

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
  namespace: default
  annotations:
    workflows.argoproj.io/rbac-rule: "'<GROUP_OBJECT_ID>' in groups"
    workflows.argoproj.io/rbac-rule-precedence: "1"
```

Secrets(新規作成)

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-service-account.service-account-token # name is discoverable. ref. https://argoproj.github.io/argo-workflows/manually-create-secrets/#option-1-discovery-by-name
  annotations:
    kubernetes.io/service-account.name: my-service-account
type: kubernetes.io/service-account-token
```

上記Apply後、以下のようにServiceAccountの`Tokens`に新規追加したSecretが自動で適用され、エラーが解消された。

```
$ kubectl describe serviceaccount my-service-account
Name:                my-service-account
Namespace:           default
.
.
.
Mountable secrets:   <none>
Tokens:              my-service-account.service-account-token
Events:              <none>
```


# 参考

* [Kubernetes 1.24.0 release notes](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#:~:text=%40lobziik%29-,The%20LegacyServiceAccountTokenNoAutoGeneration%20feature%20gate%20is%20beta%2C%20and%20enabled%20by%20default.%20When,by%20following%20this%20guide.%20(%23108309%2C%20%40zshihang),-The%20calculations%20for)
* [Kubernetes original issue](https://github.com/kubernetes/kubernetes/pull/108309)
* https://argoproj.github.io/argo-workflows/manually-create-secrets/
Rails7.0.5から7.0.5.1への変更点

## 対象
Railsのバージョンを**7.0.5**から**7.0.5.1**に変更する人

## 情報ソース
Rails Versions 7.0.5.1, 6.1.7.4 have been released!
https://rubyonrails.org/2023/6/26/Rails-Versions-7-0-5-1-6-1-7-4-have-been-released


## 変更点
- `redirect_to`による脆弱性の対応

## 詳細
`redirect_to`を使っている場合にクロスサイトスクリプティング（XSS）の攻撃が発生する可能性がある模様。
ただし、`redirect_to`を使っている場所全てで発生するということではなく、**外部のホストにリダイレクトを許可している場合**に発生しうるというものです。
外部ホストにリダイレクトを許可する設定は
```rb
redirect_to 'https://example.com', allow_other_host: true
```
こんな形でallow_other_hostをtrueに意図的にする必要があります。
逆に言えばこの設定を意図的にしなければRails側でエラーになるようになっています。

なので、これらを使っていない場合は特に影響はありません。
また使っていた場合もバージョンをあげることで解消されるので気付いた時にあげましょう。
QAエンジニアがソフトウェアテストを楽しむ方法

# ソフトウェアのテストは楽しいですか？

QAの方々、テストを楽しんでいますか？
仕事なので、正直つらいこともあると思います。
 - 繰り返し作業がつらい
 - 同じようなテストを繰り返し行う必要がある場合、単調な作業となり精神的につらい
 - 再現性のないバグがつらい
 - 仕様の頻繁な変更がつらい

今回は、これらのつらさと立ち向かうために、テストを楽しんでいく方法を考えてみました。

# 楽しむにはどうしたらいいか

ソフトウェアテストをゲームを遊ぶように楽しくする方法を考えます。
ちなみに、　このようにゲームの仕組みを応用することをゲーミフィケーションといいます。

> ※ゲーミフィケーション（英: gamification）は、コンピュータゲームのゲームデザイン要素やゲームの原則をゲーム以外の物事に応用することを言う。
（引用 [wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B2%E3%83%BC%E3%83%9F%E3%83%95%E3%82%A3%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)）

# 楽しむための要素

導入する要素

#### 難易度
 - チュートリアルの追加
   - 初心者が初めてテストするときは何をするべきかがわからないと思います。順番に実施すれば最低限の操作はわかるようなチュートリアルを用意する。
     - （例） オンボーディングの充実

#### 難易度調整
 - Easyモード/Normalモード/Hardモード
   - Hardモードしかないゲームは、一部の人しかプレイしないでしょう。実施する人のレベルに応じて難易度を調整できるようにする。
     - （例） タスク毎に難易度付をして、適切に割り振る

#### 達成感の可視化
 - ポイント・実績システム
   - テストを実施するごとにポイントを獲得できるようにする。ポイントはテストの厳密度や重要度に応じて異なる値を設定する。
   - バグの発見でポイントを獲得できるようにする。難解なバグを見つけると、ボーナスポイントを獲得する。
   - 全員のポイントを可視化してランキングで表示する。
   - 特定の条件や成果に基づいて特別な実績や称号を獲得する。
   - 成績に応じてレベルアップようにする。

#### ソーシャル
 - ロールプレイ
   - 人をキャラクター化し、何をする人かすぐにわかるようにしてみる。

 - 協力プレイ
   - チームを作成して、チームの合計ポイントを使って、チームランキングを導入する。協力プレイにより、メンバーが協力して目標を達成するようになる。

 - 対戦プレイ
   - 特定の機能について、同時にテストを開始し、どちらがよりバグを発見できるか、対戦するイベント等を行う。

# 4. 最後に

みなさんの仕事が少しでも楽しいものになり、、QAエンジニアが増えてくれることを願っています。
【9日目】OSS連携してみたい！(7) Autoware-MicroにPythonでエセ自作モジュール追加する

この記事は筆者オンリーの[Advent Calendar 2023](https://qiita.com/advent-calendar/2023/aichallenge_racing_for_mas)9日目の記事です。  

# 前回までのあらすじ
OSS連携を試みるため、PythonでROS2のサブスクリプションでAutowareのmsgをサブスクライブしてログを表示させるプログラムを作りました。
今回はC++同様、Autowareのmsgをサブスクライブして、msgをそのままAutowareに渡す処理を作っていきたいと思います。

# ROS2でモジュール作成
[ROSのチュートリアル](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html)の内容を例によって参照します。

# Autowareのカスタマイズ
## パッケージの作成
例によって[ROSのチュートリアル](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html)を見ながら、簡単なサブスクリプションを作ります。

[公式サイト](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html)の通り、ROS2のパッケージを作成します。

```
$ cd ~/aichallenge2023-racing/docker/aichallenge/aichallenge_ws/src/aichallenge_submit/
$ ros2 pkg create --build-type ament_python py_path_subscriber
```

[ROSチュートリアル](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html)に従って、package.xmlとCMakeLists.txtを変更します。

## ソースコード
ソースコードはこんな感じ。
前回からパブリッシャーを作成し、サブスクリプションがmsgを受信した契機コールバック内からmsgをAutowareに投げています。
```py_pathwithlaneid_sub_function.py
import rclpy
from rclpy.node import Node
from autoware_auto_planning_msgs.msg import PathWithLaneId
from std_msgs.msg import String


class MinimalSubscriber(Node):
    def __init__(self):
        super().__init__("minimal_subscriber")
        self.subscription = self.create_subscription(
            PathWithLaneId,
            "behavior_planning/path_with_lane_id",
            self.listener_callback,
            10,
        )
        self.subscription  # prevent unused variable warning

        self.path_pub = self.create_publisher(
            PathWithLaneId,
            "behavior_planning/path_with_lane_id_modified",
            10,
        )

    def listener_callback(self, msg: PathWithLaneId):
        path = msg
        self.get_logger().info("I heard PathWithLaneId")
        self.path_pub.publish(path)


def main(args=None):
    rclpy.init(args=args)

    minimal_subscriber = MinimalSubscriber()

    rclpy.spin(minimal_subscriber)

    # Destroy the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    minimal_subscriber.destroy_node()
    rclpy.shutdown()


if __name__ == "__main__":
    main()


```

## パッケージのビルド
次に、Dockerを起動してコードをビルドします。
```
$ cd /aichallenge/
$ pip install setuptools==58.2.0
$ bash clean_build.sh 
```

## パッケージの実行
参考記事同様、launchファイルから実行します。本大会ではAutoware-Microを使用しており、autoware_micro_awsim.launch.xmlを変更します。

```autoware_micro_awsim.launch.xml
<!-- lane_driving -->
〜割愛〜
<group>
    <node pkg="cpp_pathwithlaneid_pubsub" exec="cpp_pathwithlaneid_pubsub"/>
</group> <!-- Simple Pub Sub -->
</group> <!-- lane_driving -->
```
実行してみます。
```
$ bash run_autoware.sh
$ bash run_awsim.sh
```
通常通りにAutowareが走行し始めれば成功となります。

# 今後の予定
OSS連携のためのソースコードの雛形は作れました。
今後は連携するOSSを調査、あるいは自作の方向で進めていく予定です。

# 参考記事
[自動運転 AIチャレンジ　~導入編~](https://qiita.com/yhisaki/items/01963edd696590c3e3d2)
vue3 element plus で skelton

vue3 で skelton を利用したい。

```hoge.vue
<el-skeleton :rows="5" animated />
```

高さを指定する場合は、以下のようにする

```hoge.css
<style>

.custom-skeleton .el-skeleton__item {
    height: 1.5em;
}

.custom-skeleton-200 .el-skeleton__item {
    width: 200px;
}

</style>


```

で、使う場合は

```
<el-skeleton class="custom-skeleton" :rows="0" animated></el-skeleton>
```

これで、高さ1.5emのスケルトンが出来上がる。
Docker使ってrails newしたい時にすぐできるtemplateを作った

## はじめに

こんにちは！
まるべいじです！

個人開発でちょっと作りたくなった時に、どのような構成で構築していますか？
私は、Rails+Docker+その他必要なものにしています。
Dockerがあると環境差分が減って開発しやすくなりますし、デプロイ先の選択肢も増えるので、この構成は外せません。

ただ。。。Docker経由で`rails new`するのが地味にめんどくさい。。。
`rails new`用のDockerに`ruby`や`node`のインストール、`rails`だけはいった`Gemfile`など、細かい調整がいろいろ必要です。

すぐに開発したいのに、ここでモチベーションがちょっと下がる。これは良くない！と思ったので、今回はRailsのセットアップをするTemplate Repositoryを作りました✨

:::note info
Template Repository とは？？？
この機能を使うと、あらかじめ設定されたリポジトリをベースにして新しいプロジェクトを簡単に始められます。
つまり、Template Repositoryは「繰り返しの労力を減らす」ためのスマートなツール。簡単に言うと、「スタートキット」のようなものです！！！
:::

## リポジトリの説明

### 各種ツールやバージョン

以下はデフォルトの設定です。
Dockerfileとセットアップスクリプトを変更することで変えることができます。

- Ruby
  - 3.2.2
- Node.js
  - 20系
- Database
  - PostgreSQL
- docker-compose
  - 開発環境でdocker-composeを使ってRailsを起動することを前提に作られています

### 使い方

1. https://github.com/salvage0707/rails-new-template このテンプレートを使ってリポジトリを新規作成します
2. 作成したリポジトリをcloneします
3. リポジトリのルートディレクトリで`$ scripts/setuprails`を実行します
4. 指示に従ってセットアップする
   1. バージョンやデフォルトのオプションが問題なければ、yesと回答してください
       ```sh
       以下でRailsをインストールします。
       Rubyのバージョン: 3.2.2
       Nodeのバージョン: 20
       デフォルトオプション: --force --database=postgresql
       この内容で次に進みますか？ (yes/no): yes
       ```
   2. 追加で必要なオプションがあれば入力してください
       ```sh
       他に必要なオプションがあれば入力してください > -j esbuild
       ```
   3. 実行するrailsコマンドが問題なければyesと回答してください
       ```sh
       以下のコマンドでRailsをインストールします。
       rails new . --force --database=postgresql -j esbuild
       この内容で次に進みますか？ (yes/no): yes
       ```
   4. rails newが実行され、スクリプトが終了したらOKです
5. railsの初期作成が完了したら、`$ docker compose build`でdockerをビルドします
6. `$ docker compose run --rm web rails db:create`でDBの作成をします
7. `$ docker compose up`で起動します
8. http://localhost:3000 にアクセスして初期画面が表示されることを確認します
9. `$ rm -rf Dockerfile.tmp && rm -rf scripts`で、rails newに使用した不要ファイルを削除します
10. 完了！！！！！！！


## さいごに
これでrails newしやすくなるはず...！
まぁ、これだけだとrails newまでしかテンプレートになっていないので、開発始めるとなるとrubocopとか細かい設定は必要なんですよね。
なので、このテンプレートからさらに派生して、Rails+vite+React構成のテンプレートも作ります！
Androidエミュレーターの通信をBurp Suiteで取得する

エミュレーターの通信を Burp Suite で取りたかったので、手順を簡単にまとめます。 
手順として下記2つの方法があります。
* [ポート転送を利用する方法](#ポート転送を利用する方法)
* [仮想ネットワークのIPを指定する方法](#仮想ネットワークのipを指定する方法)

## 検証環境

* macOS Monterey 12.7.1
* Android 12.0 Google APIs (API 31) ※root が使用できるもの
* Burp Suite Professional v2023.10.3.6

## ポート転送を利用する方法

複数起動している場合は、適宜 `adb -s [デバイスID]` を指定してください。  

### 手順

1. Android エミュレーターを起動する
1. Burpを `Loopback Only (127.0.0.1)`、`8080` ポートで開始する
1. Androidエミュレーターのプロキシ設定をする ( iptables または Wi-Fi の Proxy 設定)
1. エミュレーターからホスト側へポート転送を行う

```shell
$ adb reverse tcp:8080 tcp:8080
```

これで Burp で通信が取れるようになっていると思います。

※ポートフォワーディングの一覧表示
```shell
$ adb reverse --list
```

※ポートフォワーディングの削除
```shell
$ adb reverse --remove tcp:8080
```

## 仮想ネットワークのIPを指定する方法

### 手順

1. Android エミュレーターを起動する
1. Burpを `Loopback Only (127.0.0.1)`、`8080` ポートで開始する
1. 仮想ネットワークのホスト側の IPアドレス(`10.0.2.2`)でプロキシ設定をする ( iptables または Wi-Fi の Proxy 設定)

これで Burp で通信が取れるようになっていると思います。

## エミュレーターのプロキシ設定の例

Burp の証明書は下記を参考に適宜インストールしてください。
* [Configuring Burp Suite With Android Nougat](https://blog.ropnop.com/configuring-burp-suite-with-android-nougat)
* [Android Chrome 99 expands Certificate Transparency, breaking all MitM dev tools](https://httptoolkit.com/blog/chrome-android-certificate-transparency/)

### iptables で設定する場合
```shell
# ポート転送の場合
$ iptables -t nat -A OUTPUT -p tcp --dport [取得したいPORT番号] -j DNAT --to 127.0.0.1:8080

# 仮想ネットのホストIPの場合
$ iptables -t nat -A OUTPUT -p tcp --dport [取得したいPORT番号] -j DNAT --to 10.0.2.2:8080
```

### Android の Wi-Fi から設定する場合
```
画面: Settings > Network & internet > Internet > Wi-Fi > AndroidWifi > ペンマーク > Advanced options > Proxy
Proxy hostname: 127.0.0.1 (ポート転送の場合)
Proxy hostname: 10.0.2.2 (仮想ネットのホストIPの場合)
Proxy port: 8080
```

※ Wi-Fi が「制限されたネットワーク」となった場合は、一度 Proxy の設定を削除し再接続してください。

## 参考記事

* [Android端末から開発PCに向けてポート転送を行う](https://qiita.com/Gaku_Ishii/items/d4e4cab34893e76b1674)
* [Android Emulator のネットワークをセットアップする](https://developer.android.com/studio/run/emulator-networking?hl=ja)
Sentryに送信するフロントエンドエラーをフィルタするtips

Sentryはフロントエンド開発においてエラーログを管理する強力なツールですが、不要なエラーの送信を避けるための工夫が必要です。ここでは、Sentryでの不要なエラーのフィルタリング方法について説明します。

## Sentryの組み込みフィルタの活用
Sentryはいくつかの組み込みフィルタを提供しており、これにより一般的なエラー（例えば、よく知られたブラウザ拡張のエラーや古いブラウザ、BOTからのエラー）を除外したり、glob pattern matchingを用いてエラーをフィルタリングすることが可能です。

![sentry_filter_settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8948/c1af525b-5b87-e556-2423-a584b89efc62.png)


しかし、これらのフィルタは細かい制御ができないため実運用上不要なエラーが多く送信されて、quotaの枯渇を招くことがあります。

## フィルタしたい例
Sentryでフィルタができないエラーは次のようなものです。

- 特定のバージョン以前のブラウザで発生するエラー
- Seleniumなどの許可されていないクローラーによるエラー
- マイナーなブラウザ拡張のエラー

また、逆に特定のエラーの送信割合を増やしたい場合もあります。

## フロントエンドでのフィルタリング方法
フロントエンドでのフィルタリングは、[Sentryの公式ドキュメントで紹介されている方法](https://docs.sentry.io/platforms/javascript/configuration/filtering/)を用いて実施できます。
拒否リストを使って宣言的に定義したり、イベントオブジェクトの内容を参照してクライアント側で送信するかどうかを制御することも可能です。

```javascript
Sentry.init({
  ignoreErrors: [
    // 例: ランダムなプラグインや拡張機能のエラー
    "top.GLOBALS",
    "originalCreateNotification",
    // ...
  ],
  denyUrls: [
    // 例: Facebookのエラー
    /graph\.facebook\.com/i,
    /connect\.facebook\.net\/en_US\/all\.js/i,
    // ...
  ],
  // クライアントでイベントを参照する方法
  beforeSendTransaction(event) {
    // ここでイベントを変更または除外
    if (event.transaction === "/unimportant/route") {
      return null; // nullを返すとSentryに送信しない
    }
    return event;
  },
});
```
### デメリット
この方法には以下のようなデメリットがあります。

- フィルタ条件が複雑になると、クライアントがダウンロードするファイルサイズが大きくなり、ユーザー体験が悪化する
- フィルタ条件をユーザーに知られたくない場合がある
- フィルタ条件を変更したコードをデプロイしてから、ユーザーのクライアントに適用されるまでにタイムラグが生じる。この間、古いページを利用しているユーザーから不要なエラーが送信される可能性がある。


## バックエンドでのフィルタリング方法
Sentryにエラーを送信する前に自前のAPIでチェックする方法もあります。

```javascript
Sentry.init({
  // ...
  
  beforeSend(event, hint) {
    return fetch('/api/error/filter', {
      body: JSON.stringify({
        exception: event.exception?.values ?? {}
      }),
      headers: {
        "Content-Type": "application/json",
      },
      method: "POST",
    })
    .then((res) => {
      return res.ok ? event : null; // APIが200を返した場合のみエラーを送信
    })
    .catch(() => null); // フィルタAPIの送信に失敗した場合はエラーを送らない
  },
});
```
エンドポイント ```POST /api/error/filter``` はスタックトレースを受け取り、重要なエラーの場合に200を返します。このAPIは各サービスごとに実装してください。


### デメリット
この方法にもデメリットがあります。

- フィルタAPIの送信に失敗するとエラーが送られない
    - ```.catch(() => null);``` を ```.catch(() => event);``` に変更すれば、フィルタAPIの送信に失敗した場合に常にエラーを送信するように設定できます。
- エラーが送信された直後にページが閉じられると、エラーが送信されない
- ユーザーから送信されたスタックトレースにセンシティブな情報が含まれていても、サーバーのログに残さないように注意が必要（Sentryは外部サイトへの通信ログをexceptionに含まないため、行儀の悪いブラウザ拡張を使用していない限り、センシティブな情報を受け取ることはほとんどありません）。

### どう組み合わせるか
モイではまずクライアントで明らかに不要なエラーを除外して対象の件数を減らし、サーバー側でより詳細に調査するという方針にしています。


#### クライアント側のフィルタリング:
- [サンプリングレート](https://docs.sentry.io/platforms/javascript/configuration/sampling/)を設定して件数を絞る
- 発生件数が多いブラウザ拡張のエラーを弾くようにdeniedUrlsに設定する

#### サーバーサイドのフィルタリング:
- 古いコードで起きているエラーや詳細な判定でエラーを弾く


#### Sentryの設定
- デフォルトのフィルターをON
- 緊急に停止したいエラーをフィルタに設定する
- エラーが来た時はSlackに通知
- [Spike Protection](https://docs.sentry.io/product/accounts/quotas/spike-protection/) を設定して大量にエラーが来た時に気づけるようにする

### まとめ
この記事では、Sentryに重要なエラーだけを送信するために、サーバーサイドでログを送信するかどうかを制御する方法を紹介しました。実装の参考にしていただければ幸いです。
[Nuxt.js]　nuxt.config.jsでscss変数を読み込む。

::: note warn
 Nuxt2です
:::
::: note warn
 2021年に記録した内容です
:::

## NuxtではどこでCSSを読み込むか
コンポーネントごとに当てるのがVueらしくてよいのだろうけど……自分の現場では**過去のデータを流用**ことも多い。

何年も前にHTMLとjQueryで作成したページを、新しいサーバーでとりあえず表示したい。
**リプレイスは来年の予算でお願いします！** というケース。

たいていそのようなCSSは、コンポーネント志向など考えられているはずもなく、ひとつのCSSでどーんということが多い。
その場合は、そのままどーんとnuxt.config.jsに記述する。

## 基本のCSS読み込み

```nuxt.config.js
export default {
  
  css: [
   '~/assets/css/style.css',
  ],

}
```
CSSで済むなら、これだけでよい。

## Sassの場合
昔のデータが、scssやsassで作成されている場合もある。

```nuxt.config.js
export default {

  css: [
   '~/assets/css/common.scss',
   '~/assets/css/top.scss',
  ],

}
```
これで読み込みはできる。
しかし、**scssファイルの中で定義されている変数が.vueファイル内で展開されない。**

## scss変数が.vueファイルで展開されるようにする

### モジュールをインストール

```sh
$ npm i -D @nuxtjs/style-resources
```

### nuxt.config.jsを編集

```nuxt.config.js
export default {
  modules: [
  ...
    '@nuxtjs/style-resources',
  ],
  ...
  styleResources: {
    scss: ['~/assets/css/lauout.scss'],
  },
}
```

これで自分の環境では読み込めた。
VueやNuxtは最近変更点も多いので、これがそのまま使用できない環境もあるかもしれない。
Jasmine Tea でバーバーショップコーラスに挑戦

この記事は、 [Jasmine Tea アドベントカレンダー 2023](https://qiita.com/advent-calendar/2023/jasminetea) の第5日目です。

こんにちは！Jasmine Tea の開発に参加している松尾です。

[Jasmine Tea](https://jasminetea.app/) で音楽が鳴らせるのはご存知でしょうか？
例えばこんな簡単なプログラムで、ドレミが鳴らせます。

```
play "cde"
```

Jasmine Tea では MML (Music Macro Language) という記法を使って音楽をならすことができます。詳しくは [Jasmine Tea のドキュメント](https://jasminetea.app/docs/guides/sound) をご覧いただくとして、このポストでは Jasmine Tea でクリスマスソングを鳴らしてみたいと思います。

ここ何年か、シアトル界隈の日本人仲間と、バーバーショップコーラスを楽しんでいますので、ついこないだ歌った Chipmunk Christmas Song というのを MML で入力してみました！結構入力が大変だった〜😅

[Chipmunk Christmas Song](https://jasminetea.app/studio/projects/PjyjGAhp6ykAP9JHNJ02)

工夫した点としては、曲の最後の方でスライドする箇所があるんですが、`q8` のようにして音を音符の長さフルで鳴らすようにして、`{}`を使ってスライド途中の音を入れることで、うまいことスライドに聞こえるし、パート間のタイミングも合わせることができました。

例えばこんな感じで、下記の例では`{aa+b}`というところがスライドになっています。

```
play "f+a2q8g+8{aa+b}8q8o5c1q7c8r8o4g+"
```

ということで、メリークリスマスをお届けしました🎅

みなさんも Jasmine Tea でお気に入りの曲を入力してみてはいかがでしょうか？