OmniAICreator/Qiita-1.07M huggingface dataset:
Anacondaでchromedriverの過去バージョン指定update/install

#Anacondaでchromedriverの過去バージョン指定update/install

##前提
　Windows10
　Anaconda_Navigator利用

##手順
 １-自分のChromeのバージョンを確認する
 2-installしたい対象環境を選択してOpen Terminalする
 3-conda-forgeのチャンネル(下記参照)で自分のバージョン番号に近いものを探す。
　　ピリオドで区切られた第３単語目まで一緒のものを探そう
  https://anaconda.org/conda-forge/python-chromedriver-binary/files
 4-バージョンを指定してインストールする。バージョン84.0.4147.30の例なら
　　conda install -c conda-forge python-chromedriver-binary == 84.0.4147.30
 
　以上。
　ちな、普通にチャネル指定せずupdateしようとしたら予想外なバージョンがinstallされる。
　こういうのは自動でプログラム実行前にChromeのバージョンを検査してインストールするように
　しなきゃなって思った。思っただけでやらない派だから、次困った時にやり方を調べるだろう(ループ)

## 記載の経緯
　ChromeさんがバージョンアップしてくれたのでWeb系プログラムが動かなくなった
　ところが検索しても、Anacondaにおけるchrome-driverの
　指定バージョンへのupdateが日本語でパッと出てこなかった。
　英語でも出てきたのがmac向けばかりだった。
　次回の自分のため日本語でメモ。
　多分、もっと他の人が体系的にまとめてくれるはず。
プログラミング初心者について。

私はこの春から主にJavaを使う一般企業に就職したものです。
そして、現在在宅学習に伴い会社の方から大量の様々な種類の資料を送っていただき毎日学習している状況です。

例えば、データベースに関する資料、アルゴリズムに関する資料、プログラミング言語に関する資料。主にこの3つが大半を占めており、学習を進めているのですが全て初めて触れるものの為何から手を付ければいいのか分からない状況になりました。

最初に、私はプログラミング言語をひたすら学習しているのですが、途中で自分で書いたコードに混乱してしまいます。その時にアルゴリズムのフローチャートを勉強すれば流れが分かると思い勉強しましたが、余計に混乱するばかりで理解ができません。
（フローチャートは理解できるが、自分で描き出そうとすると違う形になります。）


つまり、この3つのジャンルがいつ、1本の線で繋がるのかもわからないので、何を軸に勉強していけば良いか教えてください。
【Laravel】リレーション時に取得するカラムを設定する方法

#やりたいこと

リレーションしたものを取得するとき
`select *`と全カラム対象になるのでこれをやめる

#前提

UserとPostが存在し、関係はユーザが複数の投稿を保持

```php
public function posts()
{
    return $this->hasMany(Post::class)
}
```

#やりかた1

withで呼び出す際にカラム名を追記する

```php
User::with(['posts:user_id,name'])->get()
```

![000005.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174757/de8d753e-f7c9-1a1b-343c-5e98079a7402.jpeg)


リレーションするための外部キーをセレクトに入れないといけないので注意

#やりかた2

withで呼び出す際にクエリビルダを呼び出す

```php
User::with(['posts' => function($query){
    $query->select(['user_id','name']);
}])->get()
```

クエリスコープも使えるのでもう少しコンパクトにできる。

```php
// Postモデルにスコープ定義
public function scopeDefaultSelect($query)
{
    return $query->addSelect(['user_id','name']);
}

User::with(['posts' => function($query){
    $query->defaultSelect();
}])->get()

```
where条件なども使えるし、良き

![000006.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174757/2b94785e-c8cc-a938-46c6-dc0081a2efe4.jpeg)

#やりかた3

リレーション時に指定しておく

```php
// Postモデルにスコープ定義
public function scopeDefaultSelect($query)
{
    return $query->addSelect(['user_id','name']);
}

public function posts()
{
    return $this->hasMany(Post::class)->defaultSelect();
}
```

なにも意識しなくても、`defaultSelct`が使用されるし`addSelect`を使用していれば、
別途ほかのカラムが欲しいときに、やりかた2の方法で追加できる
[json ⇒ csv] ファイル変換

###jq コマンドをダウンロードする
下記のサイトから[jq-win64.exe]をダウンロードする。
https://stedolan.github.io/jq/

###[jq.exe]を設置する
[jq-win64.exe] ⇒ [jq.exe]にファイル名を変更する。
C:\Windows\System32\jq.exe に設置する。

### [json ⇒ csv] ファイル変換コマンド
[コマンドプロンプト]でのコマンド入力は Linuxコマンドとは若干異なる。
[Gitbash]を利用することで Linuxコマンドと表記が統一される。

```bash
cat ZipcodeList.json | jq -r '.[] | [.zipcode, .pref, .city, .town] | @csv' > ZipcodeList.csv
```
ブラウザの戻るボタンを使用しているときにページを強制的に再読み込みする

[ブラウザの戻るボタンを使用しているときにページを強制的に再読み込みするにはどうすればよいですか？](https://www.it-swarm.asia/ja/javascript/%e3%83%96%e3%83%a9%e3%82%a6%e3%82%b6%e3%81%ae%e6%88%bb%e3%82%8b%e3%83%9c%e3%82%bf%e3%83%b3%e3%82%92%e4%bd%bf%e7%94%a8%e3%81%97%e3%81%a6%e3%81%84%e3%82%8b%e3%81%a8%e3%81%8d%e3%81%ab%e3%83%9a%e3%83%bc%e3%82%b8%e3%82%92%e5%bc%b7%e5%88%b6%e7%9a%84%e3%81%ab%e5%86%8d%e8%aa%ad%e3%81%bf%e8%be%bc%e3%81%bf%e3%81%99%e3%82%8b%e3%81%ab%e3%81%af%e3%81%a9%e3%81%86%e3%81%99%e3%82%8c%e3%81%b0%e3%82%88%e3%81%84%e3%81%a7%e3%81%99%e3%81%8b%ef%bc%9f/831289669/)

```
window.addEventListener( "pageshow", function ( event ) {
  var historyTraversal = event.persisted || 
                         ( typeof window.performance != "undefined" && 
                              window.performance.navigation.type === 2 );
  if ( historyTraversal ) {
    // Handle page restore.
    window.location.reload();
  }
});
```
pageshowイベントを使用して、ブラウザーが履歴トラバーサルを介してページに移動したときの状況を処理できます。
疑似外部結合を実現する

[Query関数を使ってデータを集計しよう – 其の弐](https://officeforest.org/wp/2019/04/26/query%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E9%9B%86%E8%A8%88%E3%81%97%E3%82%88%E3%81%86-%E5%85%B6%E3%81%AE%E5%BC%90/)

### 疑似外部結合を実現する
Query関数には残念ながら、INNER JOINやLEFT JOINのような結合に関するオプション項目がありません。
Visualization APIにはJOINメソッドがあり、キー同士で結合の出来るメソッドがあるのですが・・・
必ずしも、同じものではないようです。
しかし、データベースではこの結合方法はよく使うテクニックで、これが出来ると利用幅が広がるのに。
Google SpreadsheetにもPower Queryのようなツールが欲しいところですね。

さて、これを実現させる方法として、VLOOKUP とARRAYFORMULA 関数を組み合わせて実現する方法を使ってみました。

//擬似的に２つのテーブルをリンクする形で結合
=query({D3:E12,iferror(arrayformula(vlookup(F3:F12,A3:B6,2,false)))})

```
//擬似的に２つのテーブルをリンクする形で結合
=query({D3:E12,iferror(arrayformula(vlookup(F3:F12,A3:B6,2,false)))})
```
Accessではよく使うテクニックですが、こうする事で２つのテーブルの「キー」を元にリンクして１つのテーブルを作る事が可能です。
解説すると・・・

①：メインのマスタからは外部キー以外の部分を選択しておく（D3:E12の部分）
②：iferrorでエラーがでたら何も表示しないようにしておく（外部キーに存在しないキーがある場合の対処）
③：arrayformulaにてvlookup関数の結果を配列で返すようにする
④：vlookup関数では１つの値ではなく、範囲で値を指定（外部キー）。
　 ターゲットの範囲はその外部キーを持つ別のマスタを参照。
⑤：絶対値で参照させている

複雑な構文なので多用できないのと、Arrayformula関数自体が難しい概念の関数なので、利用する時は注意書きを添えておくと良いでしょう。

![screenshot_625.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212992/794d22f9-ce04-ace3-7bdc-c4c961e06d5f.jpeg)
外部ファイルの実装前に発生するホワイトアウト画面【SSIエラー】の暫定対応

外部ファイルの実装前で、参照コードのみ先行して記述されている場合、
SSIエラーでのホワイトアウト画面が表示されるケースがあります。
`.htaccess`ファイルへの調整をして、外部ファイルの実装までに暫定的に解消させておきます。

```
<dl>
	<dt>外部ファイル実装前テスト</dt>
	<dd><!--#include file="●●.shtml" --></dd>
</dl>
```

/var/log/error_log

```
[an error occurred while processing this directive]
```


http://pentan.info/else/ssi/html_error_occurred.html

SSIを無効にするにはApacheのOptionsディレクティブからIncludesを削除します。

```
Options -Includes
```
PHPの小数演算における切り上げ切捨て問題

http://ollie-creation.co.jp/blog/?p=1

https://gotohayato.com/content/491/

```
// 小数点下位+1桁 指定での切り捨て処理
$strNum = round($strNum - 0.5 * pow(0.1, $decimalPoint+1), $decimalPoint+1, PHP_ROUND_HALF_UP);
// 小数点下位 指定での切り上げ処理
$strNum = round($strNum + 0.5 * pow(0.1, $decimalPoint), $decimalPoint, PHP_ROUND_HALF_DOWN);
```

【1.5501】などの小数点下位第三位が0で且つ、第四位が0以上の場合
事前に第四位以下を切り捨てしないと、第三位での切り上げが【1.56】となってしまう。
Selenium IDEをcronで実行しようとした時に失敗した。 env: node: No such file or directory

# Selenium IDEを Command-line Runnerで動かしCronで動かしたが

エラー内容

```bash
env: node: No such file or directory
```
チーン

# 解決策
パスを通す。


# Selenium IDE使ってますか？
まぁまぁ自由度が高いし簡単に作れますし楽しいですね。


# nodeがない
作成したSelenium IDEをコマンドライン[^Command-lineRunner]で実行しました。

定期実行させる為にcronで実行すると以下のエラーが表示。

```bash
env: node: No such file or directory
```
nodeがないよーって言われました。

コマンドラインで動かすには以下がインストールされていないとダメです。

- node version 8 or 10
- npm
- selenium-side-runner
- 使用したいブラウザのドライバ

# いやインストールしてるわ
これらのものはnodebrew経由でインストールしていたので、

```bash
$ ls ~/.nodebrew/current/bin/
chromedriver         nodebrew             npx
node                 npm                  selenium-side-runner
```

ほら。入ってる。


# PATHを通さんとダメやん
そうそう。Cronで動かす際にそのコマンドが

- /usr/bin
- /bin

以外にインストールされている場合は、明示的にPATHを通してあげないとダメなんですよね。
これたまにしかcronをしなかったりすると忘れるんですよね〜〜


## なんでPATHを通さんといけんの？

ここで疑問。
cdとかrmコマンドは使えるやん。なんでcronになるとPATHを通さんといけんのよ！

気になりますね。


## cronの環境変数を確認

cronの環境変数を確認してみます

```bash
00 13 * * 6 env > /tmp/cron_env.log // 土曜日の13時に確認したのでこんな時間指定。
```

はい、logに吐き出したので見てみると、

```bash
$ cat /tmp/cron_env.log 
SHELL=/bin/sh
USER=michida
PATH=/usr/bin:/bin <- ここ注目
PWD=/Users/michida
SHLVL=1
HOME=/Users/michida
LOGNAME=michida
_=/usr/bin/env
```

環境変数で設定されているのは `/usr/bin` と `/bin` ですね。


# PATH通しました。
ということで1行目でパスを通しておきます。

```bash

PATH=/usr/bin:/bin:/Users/michida/.nodebrew/current/bin

00 12 * * 1-5 ...
```
他の環境変数を書き換えたい場合などは同じように記述しておけば大丈夫です。




[^Command-lineRunner]: https://www.seleniumhq.org/selenium-ide/docs/en/introduction/command-line-runner/
[3/10]Google 流、理想的な CI / CD を導入するための考え方とその実践 受講メモ

DORA DevOps Research and Assessment
GoogleのDevOps指標

https://cloud.google.com/devops

# CI/CDを始めるに当たって

## 企業が長期的に成功する鍵

* アイディアを可能な限り速くユーザに届け
* フィードバックに素早く反応すること

リリース後も重要、フィードバックループ

## 何を目的に導入するか？

CI/CDは技術的にに手間がかかる。
関係者を巻き込む。DORAのいい図があるよ。

継続的に改善するために達成度を測る指標の例
速度
品質
セキュリティ

意味のある可用性とは何か？
G suiteの論文面白そう

# CI/CDの実践(GCP）

## Cloud Source Repository(CSR)
フルマネージドでプライベートなGitリポジトリ
Gitをミラーリングしてこれを挟むことで簡単にGCPのサービスにデプロイできる

## Container Registry(GCR)
Dockerイメージリポジトリ

## Cloud Build
フルマネージド CI/CDプラットフォーム

## Cloud Monitoring
カスタムメトリクスも設定できる
サービスレベルのモニタリングが可能(Beta)

# CI/CDを始めるには

一人で始めない。仲間を見つける。
改善し続けることが大事。

## GKEの自動デプロイ
おすすめYAMLを作ってくれる
CSRでミラーリングしたリポジトリを作ってpushがあればCloud Buildが動くという設定がされる


https://cloudonair.withgoogle.com/events/app-modernization-1/resources?mkt_tok=eyJpIjoiWmpOalpETXpNR0ppT0RSaiIsInQiOiJHUUZcL3B5SjNyTGlBSVhHbDU0dk16VU5nWFNPVkc1aTJzaHFcL1lZcEIzYXRyRklhZXlJVFhwTitzOHZ3M1wvOTZtcnBRUTZhK09EWTNcL3FcLzNPdnhIWjFST0tDcFhcL3EzV2VETUE5V2dEYmhMSG8yTHdFaXpha2REMzB1a2pxMVM5byJ9#?utm_medium=email&utm_source=google&utm_content=thankyou
【備忘録】Azure Synapse AnalyticsのSQLプール名がSSMSで怒られた

私がAzure Synapse Analyticsを検証するにあたっての気づきを備忘録として残します。

## SQL Server Management Studio(SSMS)を使う場合
以下の環境で起きたことですが、
SQLプール名にハイフンを入れたことで、SSMSで権限付与する際にエラーが出ました。

- Windows10 home
- SQL Server 2017 EXPRESS
- SQL Server Management Studio v18.5

Azure Synapse Analytics（以降、ASA）サービスの検証で、作成したデータベースにユーザーの作成や権限付与をSSMSで行おうとしました。

AzureポータルにてASAリソースを作成する際、SQLプール名を「sample-2020」と、ハイフン入りで作成しました。

## いざ、SSMSでユーザー作成と権限付与
SSMSを起動し、ASAリソースの概要からインスタンス名をコピペ、、、ユーザーとパスワードを入力して、、、接続完了。

次にユーザーを作成です。
オブジェクトエクスプローラのデータベースを展開し、先ほどSQLプール名で入力したデータベースを右クリック「新しいクエリ」を起動。

```
CREATE USER ・・・・
```

※この時点で下記を実行していたらもっと早く気付きました。

```
USE sample-2020;
```

話を少し戻して、、、
次に権限を付与します。

```
GRANT CONTROL ON DATABASE::sample-2020 TO [ユーザー]
```

ここで初めてクエリ内に「sample-2020」というハイフン入りのDB名が登場して、実行すると、「ハイフン近くにエラー」となりました。

詳しく原因は追究していませんが、ハイフンなしだと正常に実行できました。
ということで、現時点ではハイフンあるとSSMSで怒られたな～、という感じで備忘録として残しておきます。
Ubuntu 20.04 でネットワークの再設定

Ubuntu 20.04 でネットワークの設定を変更する方法です。

GUI で設定を変更します。
![ubuntu_2004_network.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/5b4a8faa-92d7-28a8-d8db-cbfcd78807aa.png)

設定の反映方法

```bash
sudo ifconfig enp3s0f2 down
sudo ifconfig enp3s0f2 up
```

次のページを参考にしました。
[https://qiita.com/sosanagi/items/c502b1ddf31f5ea119e7](Raspbian Stretchからifdown,ifupが使えなくなった話)
CyberAgentのサーバーサイド向け2Dayインターンに参加した話

# インターンの概要
パフォーマンスに改善の余地があるWebサイトを2日間かけてチューニングし、20人で順位を競うという内容でした。
言語はGo, Node.js, Java, PHPから選べたので、自分はPHPにしました。
また、競技以外にもCyberAgent社員によるLTやパネルディスカッションもありました。
公式の募集要項は[こちら](https://www.cyberagent.co.jp/careers/students/event/detail/id=24419)に載せておきます。

# 自分がやったチューニング
以下、チューニングなんも分からんだった自分が競技の中で実際にやったことを時系列で説明します。

### 1日目 11:00 環境構築
とりあえず作業環境どうするか考えた。
まずサーバーから全部ローカルに持ってこようとしたけど、Nginxの設定とかデータベースとか再現するの面倒そうだなと思ったので断念。
次にVSCodeのRemote SSH使おうとしたけど、なんかPermission Error出て変更を保存できなかったので断念。
結局VSCodeのSFTPプラグインを使ってローカルで作業したファイルをサーバーにアップロードすることにした。セーブ時にアップロードするようにしたので楽だった。
ここまでで約30分。

### 1日目 11:30 indexを張りまくる
作業環境を準備したが、何から手を付ければいいのかさっぱりわからない（この時点ではログを見てボトルネックを見つけるという発想もなかった）。
とりあえずアプリケーションがどんな構造になっているのか見てみると、ルート直下にindex.phpがあってそれがControllerになっていて、Twigというテンプレートエンジンでレンダリングしているみたい。
で、index.phpを見ているとSQL文がたくさんあったので、「データベースのテーブルにindex作ればいいんじゃね？」と思いSQL文1つ1つ確認し、適当なindexを作っていく。
これを数時間やってスコアが1000を超えるくらいまで上昇（ちなみに初期スコアは700台）。

### 1日目 17:00 スロークエリログを知る
第一回のメンターさんとの1on1があり、そこでスロークエリ解析を教えてもらう。
さっそくスロークエリを作成して見てみると、メッチャ時間かかってるSELECT文を発見。それに対して適当なindexを作成し、スコアが2000くらいまで上昇。
ここで暫定1位になっていたので「いけるんじゃね？」と勘違いし始めるがここから地獄を見る。

### 1日目 19:00　遅延読み込み&Nginxの設定
1日目の競技時間は一応19:00で終了となっていたが、そんなの関係ねぇの精神で作業を続行。
とりあえず主なスロークエリはなくなったので、次に何すればいいんだと考える。考えた結果、トップページの画像の読み込みに時間がかかっていたので遅延読み込みを試すが、DOM構造を変えてしまうためベンチマークでエラーが起こり断念。
次にNginxの設定でメモリの使用上限とかプロセス数とかいじってみるが特にスコアは上がらない。これを夜通しやってスコアが全く上がらなかったので泣いた。

### 2日目 10:00　静的ファイルのキャッシュ
ここら辺から静的ファイルを全部キャッシュしようと思ってNginxの設定をいじるが、上手くいかないまま終了時間へと近づいていく。
終了直前にキャッシュ自体は成功するが、新しく登録された画像が表示されないエラーが起こってスコアは上がらず。

### 2日目 16:00　競技終了
結局1日目の19:00頃から何の進歩もないまま競技終了。
順位は20人中6位前後で、満足のいく結果にはならずとても悔しい。。。

# CyberAgentについて
競技終了後のパネルディスカッションなどを見て、CyberAgentの社員さんはとても仲が良く、月並みですがいい会社なんだろうなと思いました。いい意味で大学生みたいなノリの社員さんが多い印象でした。
また、若手社員が積極的に挑戦できるような社風を社員さんの実体験から知ることができてよかったです。

# 〆文
今までパフォーマンスチューニングをした経験がなく、今回のインターンでたくさんのことを学べました。
低レイヤーの知識はこれからもっと付けていきたいです。
また、9月にISUCON10があるのでそれを目指してパフォーマンスチューニングの学習も継続してやっていきたいなと思います。
このインターンを作ってくれたCyberAgentの社員の皆さん、一緒に参加した学生の皆さん、2日間本当にありがとうございました。
今夜のお疲れ様会楽しみにしています♪
変更された項目の差分を知るための PHP の array_diff_assoc の応用小ネタ

### 概要
ユーザがあるフォームからユーザ情報を変更したとする。
「どの項目が変更されたのだろう？」 というのを簡単に知る方法はないかというところで array_diff_assoc を使った小ネタです。

例えばユーザ情報の所属が `開発部` から `プロダクト部` に変更されたとすると、
`所属`の項目が変わったのだな、と知ることができる。

### コード
```php

$before = ['ユーザ名' => 'yamotuki', '会社名' => 'M&Aクラウド', '所属' => '開発部'];
$after = ['ユーザ名' => 'yamotuki', '会社名' => 'M&Aクラウド', '所属' => 'プロダクト部'];

$changedBeforeList = array_diff_assoc($after, $before);
$changedAfterList = array_diff_assoc($before, $after);
$beforeAndAfterDiff = [];
foreach ($changedBeforeList as $key => $value) {
    $beforeAndAfterDiff[$key] = $changedBeforeList[$key] . ' => ' . $changedAfterList[$key];
}
        
var_dump($beforeAndAfterDiff);
```


出力

```
array(1) {
  ["所属"]=>
  string(31) "プロダクト部 => 開発部"
}
```



### 補足

公式ドキュメントによると、

> array_diff_assoc — 追加された添字の確認を含めて配列の差を計算する


assoc は wikipedia によれば associative の略です。

> 連想配列 連想配列（れんそうはいれつ、英語: associative array）
パイプでつないでgrep?: Railsチュートリアル備忘録 - 8章

調べた内容を3分で振り返ることができるようにメモ

パイプ機能でコマンドをつなぐ?grep?
親切に”ターミナルの”と表記されているのがポイントでした

##演習内容
>ターミナルのパイプ機能を使ってrails routesの実行結果とgrepコマンドを繋ぐことで、Usersリソースに関するルーティングだけを表示させることができます。同様にして、Sessionsリソースに関する結果だけを表示させてみましょう。現在、いくつのSessionsリソースがあるでしょうか?
(Railsチュートリアル 第6版 8.1.1)

##パイプ?
`|`（パイプ）
わかりやすくて大好きです
コマンドの出力結果を次に渡す処理をする

##grep
ファイル中の文字列に対して正規表現を使って検索し表示するLinuxコマンド
（今回は理解した気になって進みます）

##使ってみる
`rails routes`を実行すると

```
$ rails routes
Prefix Verb   URI Pattern   Controller#Action
root GET    /   static_pages#home
help GET    /help(.:format)   static_pages#help
about GET    /about(.:format)   static_pages#about
contact GET    /contact(.:format)   static_pages#contact
signup GET    /signup(.:format)   users#new
login GET    /login(.:format)   sessions#new
POST   /login(.:format)   sessions#create
logout DELETE /logout(.:format)   sessions#destroy
.
.
.                                                                    
```

ここから"sessions"を含む行だけを得たい
`rails routes | grep sessions`

```
$ rails routes | grep sessions
login GET    /login(.:format)   sessions#new
POST   /login(.:format)   sessions#create
logout DELETE /logout(.:format)   sessions#destroy
```

ちょっと応用(AND検索)
`rails routes | grep sessions | grep login`

```
$ rails routes | grep sessions
login GET    /login(.:format)   sessions#new
POST   /login(.:format)   sessions#create
```
TestCafeでログイン画面をテストする

# はじめに

最近、TestCafeを使ってE2Eテストを書き始めました。
ログイン画面でログインに成功することのテストを書く時にちょっとハマったので共有します。

テストは以下のような内容になってます。

- ログイン画面を表示
- フォームにメールアドレス、パスワードを入力
- ログインボタンをクリック
- 遷移先のURLが`http://localhost/mypage/`だったらテスト合格

# コード

## うまくいったコード

```js
import { Selector } from 'testcafe'
import { BASE_URL } from './config'


fixture('ログインページ').page(`${BASE_URL}/login`)

test('ログインでマイページに遷移', async (t) => {
  const mailForm = Selector('input[aria-label="login mail address"]')
  const passwordForm = Selector('input[aria-label="login password"]')
  const loginButton = Selector('button[aria-label="login button"]')

  await t
    .typeText(mailForm, 'test@example.com')
    .typeText(passwordForm, 'password')
    .click(loginButton)
    .expect(t.eval(() => document.location.href))
    .eql('http://localhost/mypage/', { timeout: 10000 })
})
```

## うまくいかなかったコード
```js
import { Selector } from 'testcafe'
import { BASE_URL } from './config'

fixture('ログインページ').page(`${BASE_URL}/login`)

test('ログインでマイページに遷移', async (t) => {
  const mailForm = Selector('input[aria-label="login mail address"]')
  const passwordForm = Selector('input[aria-label="login password"]')
  const loginButton = Selector('button[aria-label="login button"]')

  await t
    .typeText(mailForm, 'test@example.com')
    .typeText(passwordForm, 'password')
    .click(loginButton)
    .expect(await t.eval(() => document.location.href))
    .eql('http://localhost/mypage/', { timeout: 10000 })
})
```

## なぜうまくいかなかったか

前者と後者の違いは、期待値を設定して評価する処理の部分です

```php
    .expect(await t.eval(() => document.location.href))
```

```php
    .expect(t.eval(() => document.location.href))
```

前者はうまくテストできましたが。
後者は、ログイン後の画面に遷移する前の、ログイン画面のURLが取得されてしまいました。

なぜこうなるかは、公式ドキュメントの以下の記事が参考になります。

https://devexpress.github.io/testcafe/documentation/guides/basic-guides/assert.html#smart-assertion-query-mechanism

> Smart Assertion Query Mechanism

> TestCafe uses the smart assertion query mechanism if the assertion receives a Selector's DOM node state property or client function promise as an actual value
> TestCafeは、アサーションがセレクターのDOMノード状態プロパティまたはクライアント関数プロミスを実際の値として受け取った場合、スマートアサーションクエリメカニズムを使用します

> if an assertion did not pass, the test does not fail immediately. The assertion retries to pass multiple times, and each time it requests the actual property value. The test fails if the assertion could not complete successfully within a timeout
> アサーションに合格しなかった場合、テストはすぐに失敗しません。アサーションは複数回の通過を再試行し、そのたびに実際のプロパティ値を要求します。アサーションが時間内に正常に完了できなかった場合、テストは失敗します。

expect()の引数にDOMセレクタかPromiseを渡した場合、即時に実行されず処理結果を待ってくれるようになります。
前者の記述ではexpect()の引数に`await t.eval()`の返り値を渡してますが、`await t.eval()` はawaitによりpromiseが解決されるのでpromiseを返しません。
ログインボタンをクリック後、現在のURLを取得する処理が即時に実行されてしまい、テストに失敗しました。

timeoutというオプションは、最大で何ミリ秒待つかという値なので、指定した秒数テストが止まるわけではありません。

以上、参考になりましたら幸いです。
#saveMLAK  消えない記録①

# #covid19-libdata 更新中

「COVID-19による図書館の動向調査」【最新】第7回2020年6月6日時点での休館状況等の調査結果より、抜粋。
![C1F8FECC-3028-4A05-AAA1-140190F2AB6D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246806/e675c803-db36-d404-c396-f51474cc6087.png)

#消えない記録をつくる

2020年4月10日、Code4LibのSlackチャンネル＃generalに「COVID19 図書館対応状況まとめ」が投稿された。

![2BED4BF5-6A92-4ECD-A410-47B88E75B3CC.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246806/587c76d0-8560-a914-e3dd-3e6b4d038eb7.jpeg)

この呼びかけを機会に、関係者によるZoomミーティングを経て、2020年4月12日＃covid19-libdataチャンネルで『日本の図書館:統計と名簿』2018年版の公共図書館マスタファイルを基にしたGoogle spread sheetが設定された。

これを基にして、Slack/google spread sheet/Zoomを運用し、調査活動をしているのが、saveMLAK COVID-19libdataチームである。

調査参加者は有志、筆者はその中のひとり。第7回は、新型コロナウィルスに関する公共施設休業のお知らせがWEB上消えている自治体があることに気付き、秘かに衝撃を受けている。
もしかしたら、自治体の公文書（紙媒体）で保存しているかも知れないが、公式ホームページ上から削除されている≒優先順位が低い情報と判断？が恐ろしい。
調査でアーカイブしているので、ホームページ更新前の情報は保存されているが、最新のweb情報がないことは、全国的に見れば僅かの事例であっても、哀しい。

という個人的感想と気付きについて、メモ①
【Swift5】RSSフィードのURLを見つける



RSSリーダーには、`https://〇〇〇.com`と入力するだけで`https://〇〇〇.com/feed.xml`のようなフィードのURLを自動で見つけてくれる機能があったりします。

今回は、Auto Discoveryというものを利用して実装してみます。

試しに[Gizmodo Japan](https://www.gizmodo.jp/)のHTMLを見てみると…

```HTML
<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="https://www.gizmodo.jp/index.xml">
<link rel="alternate" type="application/atom+xml" title="Atom" href="https://www.gizmodo.jp/atom.xml">
```
ありました。
この`application/〇〇〇+xml`がある、すなわちAuto Discoveryに対応しているサイトなら簡単にURLが取得できます。

#たったこれだけ
```Swift
if let doc = try? HTML(url: URL(string: "https://gizmodo.jp")!, encoding: .utf8) {
    for node in doc.xpath("//link[@type='application/rss+xml']") {
        print("rss:\(node["href"]!)")
    }
    for node in doc.xpath("//link[@type='application/atom+xml']") {
        print("atom:\(node["href"]!)")
    }
}
```
HTMLパーサーは`Kanna`というライブラリを使用しました。
[Swift製HTMLパーサ「Kanna」](https://qiita.com/_tid_/items/c228b1931cd9b23d52d3)
少し古いですが、こちらの記事が参考になります。importする手順も書かれています。

#出力結果
```
rss:https://www.gizmodo.jp/index.xml
atom:https://www.gizmodo.jp/atom.xml
```
問題なく取得できました。
今回試したGizmodoはrssとatomの両方に対応していたようです。
競技プログラミング練習記 No.3【ABC153練習】

初回 -> [はじめに](https://qiita.com/easychoco/items/a9ae28aca97cfe47091f)

ABC153を解きました。
4完です。
E問題は自力で解けず、解説等を見て解きました。

まさかのけものフレンズ回だった。
放送期間中に開催されたコンテストだったのでしょうか。
→追記 : 放送期間とは外れていて、けものフレンズを好きな方が問題を作っていました。

|問題|時間|アルゴリズム|
|:-:|:-:|:-:|
| A | 1min |-|
| B | 1min |-|
| C | 3min |-|
| D | 5min |-|
| E | - |重複ナップサック問題|



## A
```c++
	int h, a;
	cin >> h >> a;
	cout << (h - 1) / a + 1;
```

`h / a ` の切り上げ。
`ceil()`とかも使えます。

## B
``` c++
	int h, n, a, power = 0;
	cin >> h >> n;

	for(int i = 0; i < n; ++i)
	{
		cin >> a;
		power += a;
	}
	cout << (h <= power ? "Yes" : "No");
```

攻撃力の総和が敵のHPを超えているかで判定。
入力と同時に処理できればソースがシンプルになりますね。


## C
``` c++
	int n, k;
	cin >> n >> k;

	vector<int> h(n);
	for(int i = 0; i < n; ++i)
	{
		cin >> h[i];
	}

	sort(h.begin(), h.end());

	ll sum = 0;
	for(int i = 0; i < n - k; ++i)
	{
		sum += h[i];
	}
	cout << sum;
```
HPの高い敵を必殺技で倒し、のこりを攻撃で倒すと最小回数です。
必殺技は `k`回ですので、 `n-k`体を攻撃で倒します。
ソートして、HPの小さい順に `n-k`体分のHPを足しています。

## D


``` c++
	ll h;
	cin >> h;

	int log2 = 0;
	while(h > 0)
	{
		h /= 2;
		log2++;
	}
	ll ans = pow(2, log2) - 1;
	cout << ans;
```

「毎回半分になる」とあったので、2を底とする対数をとったらうまくいけました。
O(log2 H)です。

## E

```c++
struct Magic
{
	int a, b;
	bool operator>(const Magic& another) const {
		return a  > another.a;
	}
};


void solve()
{
	int h, n, a, b;
	cin >> h >> n;
	vector< Magic > magic(n);

	for(int i = 0; i < n; ++i)
	{
		cin >> magic[i].a >> magic[i].b;
	}

	sort(magic.begin(), magic.end(), greater<Magic>());

	// モンスターの体力を i 減らすため消耗する魔力の最小値
	vector<int> dp(h + magic[0].a + 1, 1e9);
	dp[0] = 0;

	for(int i = magic[magic.size() - 1].a; i <= h + magic[0].a; ++i)
	{
		for(int j = 0; j < n; ++j)
		{
			if (0 <= i - magic[j].a)
			{
				dp[i] = min(dp[i], dp[i - magic[j].a] + magic[j].b);
			}
		}
	}

	int ans = 1e9;
	for(int i = h; i <= h + magic[0].a; ++i)
	{
		ans = min(ans, dp[i]);
	}
	cout << ans;
}
```

ナップサック問題っぽいけど重複するのかー、、、まで考えて止まりました。
解説を見て回答。
ナップサック問題を理解しきれていないことが分かりました。復習しなきゃ。

初めは貪欲法かと思ったので `struct Magic` を作ったのですが、
`struct Magic` を作らなくても `pair<int, int>` で十分だったと思います。
【Rails】redirectとrenderの違いは？

## はじめに

redirectとrenderの違いって何だか分かりますか？
Rails初学者であれば意外と理解できていない人もいるかと思うので簡単に説明します。

## ズバリ違いはこれ

違いは何かというと、**コントローラを経由するかどうか**です。
それぞれ処理の流れを書いてみます。

■ redirect
リクエスト送信
→ ルーティングでコントローラとアクションを特定
→ 指定のコントローラ内のアクションが動作
→ 動作したアクションに対応するビューファイルがレスポンスとして表示される

■ render
指定のビューファイルを表示

## 使い分けは？

良く見かける例でいくと、投稿データを保存するという処理をする場合、保存に成功した場合はredirect、失敗した場合にはrenderを使うという使い分けになるかと思います。
コードの例を使って少し説明してみます。

```ruby:post_controller
def new
  @post = Post.new
end

def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to root_path
  else
    render "new"
  end
end
```
実際の動きを動画でみてみましょう
今回は５文字以上入力しないと保存できないようにバリデーションをかけています。

![81311fb1ec11688f69985b41a2984bc0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605568/10cdbaa5-8392-e7ca-fddf-be0ccc761493.gif)


保存に成功したときはルートパス（投稿データ一覧）に移動するだけというのはすぐ理解できると思います。
　保存に失敗した場合のrenderは、newのビューファイルを直接読み込みます。このときコントローラを経由しないため、ビューファイルに渡されるインスタンス変数（@post）はnewアクションのものではなく、**createアクションで使われたもの**（投稿データが格納されている）となります。
　こうすることで、保存失敗後にnewのビューファイルが表示されたときに、**入力データを保持**してくれます。
　保存失敗時にredirectを使うとnewアクションが読み込まれ、@postには新しく作られた空のインスタンスが渡されてしまうため、せっかく入力したデータが消えてしまいます。

## 最後に
renderは今回の使い方以外にも、部分テンプレートとして使用したり、繰り返し処理に使ったりと様々な使い方があり非常に便利なメソッドです。うまく使いこなせるようになると楽しくコーディングができると思うので、覚えておくと良いかと思います。

最後までご覧いただきありがとうございました！
RTX1210のパスワード設定をする 

RTX1210を使用する場合、インターネットに出れるための設定を多く入れることになりますが、セキュリティ対策も必要な設定となります。
セキュリティ対策としてパスワード設定をすることはとても有効です。
誰でもできる簡単なコマンドのなので、ぜひトライしてみてください。
今回入れる設定は以下の項目となります。

* コンソールのプロンプト表示の設定
* ログインパスワードの設定
* 管理者パスワードの設定

### 環境
* 初期化したRTX1210
* OS:MacOS
* terminal:iTerm2

### 今回の構成
![network_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641101/c913f052-e998-5cb5-771a-0e08cf7ee1c5.png)

※私の環境で動作確認を行ってはいますが、お使いの環境ですべての動作を保証するものではありませんのでご注意ください。

## コンソールのプロンプト表示の設定
RTX1210のコンソールに入りプロンプト表示の設定を変更します。
※コンソールへの接続がわからない方は[こちら](https://qiita.com/2480230/items/4b5a650ef19907ad30aa)から、telnet接続は[こちら](https://qiita.com/2480230/items/8b67e5df3a47069f0f8f)から。

### コマンド
```japanese:RTX1210
# console prompt RTX1210 //任意の名前を設定できます
```
```japanese:変更後の表示
RTX1210#
```
プロントの表示を`#`から`RTX1210#`に変更できました。
名前は任意で設定することができます。
一般の方ならRTX1210を２つ同時に運用することはあまりないかもしれませんが、仕事によっては十数台のネットワーク機器を設定することがあります。
そういった場合にはプロントの表示を個別に設定することで、どのルーターに接続しているのかが視覚的にわかるので誤操作などを防ぐことに繋がります。
使用場所、用途に合ったものに設定することをオススメします。

## ログインパスワードの設定
RTX1210のコンソールにログインするパスワードの設定を変更します。
### コマンド
```japanese:RTX1210
# login password
```
実際にやってみます。

```japanese:RTX1210
RTX1210# login password
Old_Password: //初期状態ではパスワードを設定されていないので「enter」で通る
New_Password:pass //任意のパスワードを設定
New_Password:pass //任意のパスワードを確認
Password Strength : Weak
RTX1210#
```
RTX1210のログインパスワード`pass`が設定できました。
※コンソールでのパスワード入力部分は表示されませんのでご注意ください

## 管理者パスワードの設定
RTX1210の管理者モードへの移行パスワードの設定を変更します。

### コマンド
```japanese:RTX1210
# administrator password
```
実際にやってみます。

```japanese:RTX1210
RTX1210# administrator password
Old_Password: //初期状態ではパスワードを設定されていないので「enter」で通る
New_Password:pass //任意のパスワードを設定
New_Password:pass //任意のパスワードを確認
Password Strength : Weak
RTX1210#
```
RTX1210の管理者パスワード`pass`が設定できました。
※コンソールでのパスワード入力部分は表示されませんのでご注意ください

## まとめ
今回設定したパスワードはあまりにも簡単で予測の付きそうなものなので、どちらとも`Password Strength : Weak（パスワード強度：弱）`と表示されています。
パスワードを設定する場合には、`ランダムな文字列の12桁`などの強度の高いのもいいですが、実際にはもう少し考えた運用しやすいものを設定することをオススメします。
わからなくなった場合は、ログインできなくなりすべてをリセットせざるおえない場合もあります。
パスワードもですがconfigのバックアップもお忘れなく。

## RTX1210設定Howto記事まとめ

できるだけ、初期設定から順にして掲載していく予定です。
[こちら](https://qiita.com/2480230/items/aecd2c86133f18e24332)に__RTX1210のHowto記事まとめ__ページがあります。

* [RTX1210設定Howto記事まとめ](https://qiita.com/2480230/items/aecd2c86133f18e24332)

RTX1210を中心に書いてはいますが、YAMAHAのRTXシリーズのコマンドはRTX5000、RTX3500、RTX1200、RTX830、RTX810でも同じコマンドで設定できることが多いので、ご参考になれば幸いです。
高位合成言語Polyphony (windowsでiverilog その15）

#概要

windowsでiverilogやってみた。
polyphony、やってみた。
全加算器、書いてみた。

#サンプルコード


```
import polyphony
from polyphony import testbench, module, is_worker_running
from polyphony.io import Port
from polyphony.typing import bit

@module
class fa:
	def __init__(self):
		self.a = Port(bit, 'in', protocol='valid')
		self.b = Port(bit, 'in', protocol='valid')
		self.c = Port(bit, 'in', protocol='valid')
		self.o = Port(bit, 'out', protocol='valid')
		self.s = Port(bit, 'out', protocol='valid')
		self.append_worker(self.worker)
	def worker(self):
		while is_worker_running():
			a0:bit = self.a.rd()
			b0:bit = self.b.rd()
			c0:bit = self.c.rd()
			s0:bit = a0 ^ b0
			c1:bit = a0 & b0
			s1:bit = s0 ^ c0
			c2:bit = s0 & c0
			c3:bit = c1 | c2
			self.o(c3)
			self.s(s1)

@testbench
def test(m):
	print("a b c o s")
	for i in range(2):
		for j in range(2):
			for k in range(2):
				m.a.wr(i)
				m.b.wr(j)
				m.c.wr(k)
				o = m.o.rd()
				s = m.s.rd()
				print(i, j, k, o, s)

m = fa()

test(m)




```

#python実行結果


```
a b c o s
0 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 1 1 1 0
1 0 0 0 1
1 0 1 1 0
1 1 0 1 0
1 1 1 1 1

```

#iverilog実行結果


```





a b c o s
0 0 0 0 0
0 0 1 0 1
0 1 0 0 1
0 1 1 1 0
1 0 0 0 1
1 0 1 1 0
1 1 0 1 0
1 1 1 1 1
 1410:finish


```

以上。
Installing the 64-bit OS (bate) on a Raspberry Pi

With the release of the 8GB version of the Raspberry Pi4, the 64-bit version (bate) of the operating system has been released to the public.
In this case, I'm going to install the 64bit version of the OS.

## Download.
[here](https://www.raspberrypi.org/forums/ viewtopic.php?f=117&t=275370) from the page ` Click `Download here` to download the zip file.

## Install.
- Write the downloaded file to the microSD card.  
- This time we will use Etcher. [here](https://www.balena.io/etcher/) Download and install it.
- After starting Etcher, click on `Flash from File` to download the OS's Select the zip file.  
- Check the installation location in the middle and if it is different, change it by `Change`.
- Click on `Flash` when the correct destination is selected.
- Wait for a while and close Etcher when the installation is complete.

## SSH Settings.
### Enable SSH.
- Immediately after installation, Windows probably doesn't recognize the microSD card. So point the microSD card again.  
- When you see the microSD card(boot), open it and make a ssh file (not ssh.txt). (Be careful not to turn it into ssh.txt)

The password is stored in clear text. It can be hashed later, but not for now.

### SSH Connection.
- Insert the microSD into the RaspberryPi and connect it to the power supply, and wait for a while until the system boots up. Wait.
- Start the command prompt or powershell and execute the following command Connect to the RaspberryPi.

```
ssh pi@raspberrypi
```

- You will be asked for yes/no?
- Type `ssh pi@raspberrypi` again and you will be connected to RasPi and SSH2 When you are asked for a password, enter the initial password `raspberry` to login You can.

## Default.
### raspi-config.

- The following command will display raspi-config.

````
sudo raspi-config
````

### Change Password.
- Please select 1 to change your password

### Update.
- Select 8 to start the update.

### Exit raspi-config.
- Press the right key and select `Finish` to exit raspi-config.
- Reboot when asked if you want to reboot.

### Update Package.
Update the package with the following command.

````
sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y
````

## My Accounts.

<a href="https://github.com/mao2009"><img src ="https://mao2009.github.io/qiita_template/GitHub-Mark-64px.png" width="50px"></a><a href="https://twitter.com/loach_mao"> <img src="https://mao2009.github.io/qiita_template/Twitter_Social_Icon_Circle_Color.png" width="50px"></a>
Translated with www.DeepL.com/Translator (free version)
Selenium::WebDriver::Error::UnknownError:の対処法（seleniumではなくrackを指定してテストを走らせる）

###環境
Rails5.2
Ruby2.5
rspec-rails 3.7
Docker


###状況
RspecでSystemSpecを実装中に下記のエラーに悩まされました。。

```ruby:terminal
Selenium::WebDriver::Error::UnknownError:
            unknown error: Chrome failed to start: exited abnormally.
              (unknown error: DevToolsActivePort file doesn't exist)
              (The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.)

日本語訳

Selenium::WebDriver::Error::UnknownError。
            不明なエラーです。Chrome の起動に失敗しました: 異常終了しました。
              (不明なエラー: DevToolsActivePortファイルが存在しない)
              (chromeの場所/usr/bin/google-chromeから起動したプロセスはもう実行されていないので、ChromeDriverはChromeがクラッシュしたと仮定しています)

```

調べてみるとこのエラーの対処法が記載されている記事は
有難いことに比較的色々と見つけることができました。
(chrome-driverをインストールしたり、googlechromeを最新バージョンにアップデートしたり、
rails_helper.rbの記載を修正したり、capybaraのバージョンを変更したりなど、
私も色々試しました・・・）


しかし`selenium`を用いずに`rack`を使って対処している記事は比較的少なかったと感じ、
対処法をまとめさせていただきます。

###対象
SystemSpecで同様のエラーが出ており、`JavaScriptのテストは不要な方`

→`JavaScriptの動作を確認するテストが必要な方はseleniumを用いる必要があるからです。`
私の場合は、JavaScriptの動作確認は不要だった為、
`selenium_chrome_headless`はあえて使用しない選択をとりました。
（JavaScriptを検証しないテストの場合、rackの方がテストを走らせる時間を短縮できるという
メリットもあるようです。）

###行ったこと

####①rails_helper.rbを修正

<img width="595" alt="スクリーンショット 2020-06-08 10.23.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565338/aed21276-c441-d46f-caf8-66258cd8275f.png">

修正したのは以下になります。

```spec/rails_helper.rb

  config.include FactoryBot::Syntax::Methods
  config.before(:each) do |example|
    if example.metadata[:type] == :system
      if example.metadata[:js]
        driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
      else
        driven_by :rack_test
      end
    end
  end
  # Capybara.javascript_driver = :selenium
  ↑ここを記載していましたが、コメントアウトにする
end
```

####②capybara.rbの修正
<img width="551" alt="スクリーンショット 2020-06-08 10.26.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565338/c0deec63-d02d-2958-3dd1-e4f6a96aed29.png">


```spec/capybara.rb

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end
end
Capybara.default_driver = :rack_test

↑rack_testで行うことを明示。

```

上記を行い、再度

```ruby:terminal

docker-compose exec 【アプリ名】　bundle exec rspec spec/system

```
を行うとテストがパスしました！

※念のため

```ruby:Gemfile

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'pry-rails'
  gem 'pry-doc'
  gem 'pry-byebug'
  gem 'rails-erd'
  gem 'annotate'
  gem 'rails-controller-testing'
  gem 'capybara', '~> 2.15'
  gem 'selenium-webdriver'
end
```


割と時間をかけて悩まされたのですが解決策は非常にシンプルでした・・・

そもそもの、Rspec、capybaraに対する知識やドライバの理解等、勉強する必要があると感じました。
Laravel の Tinker で DI

Laravel では Service Provider に依存解決のルールを記述することで、 Constructor Injection のような形で DI が実現可能です。
この DI を Tinker の実行時に行う方法がすぐ出てこなかったので、そのメモです。

## TL;DR

`app()` ヘルパー関数または `App` ファサードを使えば良い。

## 例

Repository パターンを用いる際に以下のような ServiceProvider を作成した場合について考えます。

```php:RepositoryServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Domain\User\UserRepository;
use Infra\User\UserRepository as EloquentUserRepository;

class RepositoryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(UserRepository::class, EloquentUserRepository::class);
    }
}
```

Tinker 中で以下のように DI が実現できます。

```php
$ php artisan tinker
Psy Shell v0.10.4 (PHP 7.4.1 — cli) by Justin Hileman
>>> $repository = app()->make(\Domain\User\UserRepository::class);
>>> // or
>>> // App::make(\Domain\User\UserRepository::class);
>>> $user = $repository->get(1);
```
小ネタ: Next.js のサーバサイド側の共通処理を括り出したい (Middlewareパターン)

## 困った共通処理

Next.js で、全ページ共通の処理がある場合、あんまり頭のよくないコードを書くことになります。

```js:pages/index.js
export const getServerSideProps = async (ctx) => {
  // （全ページ共通処理）共通情報を取得する。
  let commonInfo;
  try {
    commonInfo = await getCommonInfo();
  } catch (exception) {
    // 取得できなければ404で返す
    ctx.res.status = 404;
    return { props: { notFound: true } };
  }

  // いろいろな処理…
}
```

これを全ページにコピペして書くなんて信じられな〜い！

もちろん関数に抽出すればもっと短くなるかもしれないけど、 `try { } catch () { return ... }` は短くならないでしょう。　

## ミドルウェアとして共通処理を括りだす

そういうことで、共通処理を下記のようにミドルウェア(middleware - 関数を引数にとって関数を返す関数)として抽出してあげましょう。

```js:utils/addCommonInfo
export const addCommonInfo = (f) => async (ctx) => {
  // 共通情報を取得する。
  try {
    // ctx.commonInfo に共通情報を入れてあげる
    ctx.commonInfo = await getCommonInfo();
    return await f(ctx);
  } catch (exception) {
    // 取得できなければ404で返す。
    ctx.res.status = 404;
    return { props: { notFound: true } };
  }
}

// ※こういう共通処理のファイルはくれぐれも `pages/` 配下におかないように。 `next build` がよくわからないメッセージとともに失敗する原因になります。
```

抽出した関数を使うようにしてあげましょう。もともとのgetServerSidePropsの関数を包めばOKです。

```js:pages/index.js
import { addCommonInfo } from "../utils/addCommonInfo"

export const getServerSideProps = addCommonInfo(async (ctx) => {
  // 共通情報
  const {commonInfo} = ctx

  // いろいろな処理…
})
```

こういったミドルウェアがいくつかある場合は、ミドルウェアの塊になるミドルウェアを作るのも手です。

```js:utils/addStandardMiddlewares.js
export const addStandardMiddlewares = (f) => addCommonInfo(
  addCommonInfo2(
    addCommonInfo3(f)))
```

もちろん [Next.js を組み込む側のサーバをセットアップしてあげて](https://nextjs.org/docs/advanced-features/custom-server)、そこにミドルウェアを入れるのも手ですが、こちらのアプローチの方がNext.jsのレンダリング機構を利用することができます。ただ、全ページの関数に関数をミドルウェアで装飾するコードは書く必要は出てきます。要は使い分けですね。
EC2について

#前提
本日学んだことを書いていきます。

#本題

#EC2とは
Elastic Compute Cloudの略。
AWSの仮想サーバ。

##仮想サーバとは
サーバ上に論理的なサーバを作成したもので、サーバー上に必要なこれらの各種コンポーネントが論理的に作成されている。

OS　→　AMI
CPU・Memory　→　インスタンスタイプ
SSD　→　EBS
NIC　→　ENI（Elactic Network Interface）

サーバの土台はAWSが用意してくれるため、OSより上のレイヤーを自由に作り上げていくのが基本的な使い方。

##AMIとは
・Amazon Machine Image
・EC2インスタンスが起動する時に必要となる設定（テンプレート）ファイル。
・一つのAMIでEC2を何台も複製できる。
・OSやアプリケーションの情報が設定されている。
・OSは選択可能Amazon Linuxが一般的。
・各種Linuxディストリビュージョン
・WindowsServer

##インスタンスタイプとは
・EC2のCPU、メモリなどのスペックの構成パターン
・スペックの構成により、「インスタンスファミリー」と分類分け
・さらに、micro、small、medium、large、xlarge のように性能の選択肢が用意されている。

#EBSとは
・EC２インスタンスが使用するストレージのサービス
・AZ（データセンター）で冗長

種類は大きく２種類ある。
通常EC2を起動する際にはデフォルトで汎用SSDがアタッチされる。
IOPS　＝　1秒あたりどの程度のデータを書き込み、読み出しできるかの性能指標。

・ソリッドステートドライブ（SSD）
→汎用SSD
デフォルト・基本これを使う・容量課金

→プロビジョン度IOPS SSD（iol）
贅沢・IOPSを買える感じ・高性能DBへ接続する時・容量、IOPS課金

・ハードディスクドライブ（HDD）
→スルーブット最適化HDD
データウェアハウス・ビックデーター・ログ処理

→Cold HDD（sc1）
安い・アーカイブ保管

##EBSのスナップショット
###スナップショットとは
・EBSをバックアップとして取得したファイル
・保管場所はS３
・スナップショット自体をEC2にアタッチ/デタッチできない
・増分バックアップ方式
・AZサービス

###増分バックアップ方式
バックアップには２種類ある。
EBSは増分バックアップ方式。

初日に全てのデータをバックアップしてから、翌日以降のバックアップ対象に違いがある。
差分バックアップは初日からの変更点を基準とし、増分バックアップは前日からの変更点を基準にする。

差分バックアップの特徴
・バックアップ時間が長い
・容量を逼迫する、復元が楽

増分バックアップの特徴
・バックアップ時間が短い
・容量を逼迫しない
・クラウド向き

##EC2とAMIとEBSの関係
AMIとEBSのスナップショットを合わせたもの。
Chobiit for kintone～kintone連携サービス、webフォーム作成～

初めまして株式会社ノベルワークスです。
弊社はkintoneプラグインの開発や顧問エンジニアリングをしている大阪の会社です。

これから３回に渡ってノベルワークスが開発した
Chobiit　for kintone　の紹介をさせて頂きます！

# Chobiit for kintoneとは 
![chobiit_logo.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605275/dbfbe5e1-e7d8-cd4b-f01d-ff99760e9b6e.jpeg)


kintoneを外部から操作する、kintoneの連携サービス（kintoneプラグイン）です。Chobiit for kintoneを利用することで、kintoneのライセンスを持っていない外部の人と、kintoneの情報を共有することが出来ます。
[Chobiit for kintone]https://www.novelworks.jp/chobiit/

# ●そもそもkintoneって？
kintoneは組織の情報を集約するクラウド型のビジネスプラットフォームです。
日々の業務で発生する膨大な情報を一つにまとめ、情報を集約することで社内で効率良く業務を行うための情報共有（シェア）を得意とします。
さらにグループウェアとしてのコミュニケーション機能でチームワークも強化します。
[kintone] https://kintone.cybozu.co.jp/


#●kintoneってどんなことができるの？

データの入力、集計、共有、コミュニケーションが全てkintone上で完結します。
これにより業務の見える化が実現。進捗管理、ワークフローの流れも明確になります。


#●そこで発生する問題・・・

とっても便利ですがこれらの機能が使えるのはkintoneライセンスを持っている人だけ。
ライセンス発行数がネックになりkintoneを導入出来なかった人にもチームで使ってほしい、社内業務IT化の敷居やkintone導入の敷居をもっと下げる事ができないか・・

**そんな問題を解決するため、開発されたのがChobiit for kintone**	

#●コンセプト　～集まることで最強に～

もっと気軽にkintoneを使えるように！

kintoneは社内の情報を一つにまとめること、kintone上でのコミュニケーション機能に優れていますが、組織を支えるのは社内の人間だけではないはずです。
社内はkintoneでまとまり、Chobiitでまわりにいる仲間とつながる事で最強に！

![chobiit_img.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605275/1bc55bd5-08f3-c1c6-c54e-bdf2b39d9363.png)

#●３つのポイント

■kintone内のデータを外部公開
kintone の「レコード登録」「レコード編集」「レコード削除」
「一覧の表示」の操作がChobiit を介して外部から操作が行えます。

■1ライセンスで１０Chobiitユーザーの発行が可能
ユーザー認証の場合、kintone1ライセンスに対して10 Chobiitアカウントまで発行可能です。
また、Chobiitアカウントはkintoneライセンスのアクセス権限を継承します。

■従量課金で使った分だけ
Chobiit はリクエスト単位の従量課金。１リクエスト５円

#例えばこんな使い方ができます！

（１）イベント情報・申し込み・アンケートなどWebフォームとして利用

Chobiit外部公開機能を利用し、kintoneで管理しているイベント開催情報を自社のホームページ内に埋め込み、公開することが可能です。
またアクション機能を使用し、情報閲覧から申し込み、アンケート回収まで
Chobiitで完結します。イベントを共同開催した会社との情報共有もChobiitで！

![chobiit3情報公開.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605275/287ffc94-6e1d-f08e-bbd0-44a9588a3dc0.png)

（２）空き家調査など、現地での調査結果報告で利用


Chobiitのロケーション機能を利用し、多数のユーザーからの現地調
査報告、道路の補修箇所報告など、位置情報と写真を組み合わせ、
正確な情報把握が可能です。
さらにカンタンマップ（外部サービス）との連携で、より高機能なSFA（営業支援）としてもご利用いただけます。

![ロケーション機能.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605275/9583c407-22f7-ea79-a83c-0ec17340f3b6.jpeg)

（３）受発注など企業同士のやり取り

資材の発注や、商品の注文情報、案件情報の共有など
今までメールや電話で行っていたやり取りを、kintone内で完結し一元管理を行うことが可能です。

![chobiitdm_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605275/3bbe6b37-7a75-23f8-08d7-ab99fd620f77.png)

社内ではkintoneを使っているけれど取引先とはメールや電話で対応しないといけない。
その結果をkintoneに入力する必要がある為いまいち業務効率があがっている実感がない・・・という事、あると思います。
Chobiitを使えば
自社だけでなく周りも巻き込むことで一気に業務の効率化、IT化をすすめることができます！

しかも**従量課金制**の為、登録しても使わなければお金はかかりません。
導入する上での金額的なハードルがとても低いです！
※利用する分を先に購入いただく「リクエスト単位購入」もございます。

**１か月無料お試し**もできるので、kintoneをもっと上手く活用したい
という会社様、ぜひ検討してみてください！

▼詳細・１か月無料お試しのお申込みはコチラ
https://www.novelworks.jp/chobiit/


次の回では、もう少しChobiit for kintoneの機能に関して詳しくご紹介させていただきます!
ASP.NET Core と Vue.js を同梱して発行する方法

Angular, React はあるのに Vue がないので。

## 手順

- ASP.NET Core の API プロジェクトを作成
- ASP.NET Core のプロジェクトの下で `vue create someproject` のようにして Vue のプロジェクト作成
- Microsoft.AspNetCore.SpaServices.Extensions を ASP.NET Core のプロジェクトに追加
- ASP.NET Core のプロジェクトファイルを開いて以下のようにソリューションエクスプローラーへの表示や発行時に Vue のビルドと Vue のビルド成果物を含める構成を追加

```xml
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <SpaRoot>someproject\</SpaRoot> <!-- ここは SPA のプロジェクト名 -->
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.4" />
  </ItemGroup>

  <ItemGroup>
    <!-- Don't publish the SPA source files, but do show them in the project files list -->
    <Content Remove="$(SpaRoot)**" />
    <None Remove="$(SpaRoot)**" />
    <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
  </ItemGroup>

  <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />

    <!-- Include the newly-built files in the publish output -->
    <ItemGroup>
      <DistFiles Include="$(SpaRoot)dist\**" />
      <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
        <RelativePath>%(DistFiles.Identity)</RelativePath>
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>
</Project>
```

- Startup.cs の ConfigureServices と Configure に SPA 用の設定を追加

```csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddSpaStaticFiles(options => options.RootPath = "someproject/dist"); // SPA のプロジェクト名/dist
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

    app.UseSpaStaticFiles();
    app.UseSpa(spa =>
    {
        spa.Options.SourcePath = "someproject"; // SPA のプロジェクト名
        if (env.IsDevelopment())
        {
            // npm run serve で走らせる開発サーバーの URL
            spa.UseProxyToSpaDevelopmentServer("http://localhost:8080");
        }
    });
}
```

## 動作確認

Vue のプロジェクトフォルダーで `npm run serve` をしてローカルの開発用サーバーを立ち上げます。そして ASP.NET Core のプロジェクトをデバッグ実行すると

Vue の画面が表示されます。8080 ポートじゃない点に注目

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/fd5d23a5-69b1-08f6-4312-f7730b9ff74d.png)

そして `dotnet publis -c Release` か Visual Studio のウィザードから発行したフォルダーにある exe を起動すると 5001 ポートで待ち受けするサーバーが起動します。

ブラウザーでアクセスしてみると、ちゃんと表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/9acff904-79cc-fdbf-3527-d31f5728fa28.png)
jQuery での return false

jQuery の構文に見られる return false を決まり文句のように書いてるとこがあったが何のために記述を入れておくのか調べたのでメモ。



##■jQuery での return false

■ポイント
**基本的にjs動作では、ある要素で発火したイベントはその全ての親要素に伝播していく。**
と、いうところにあります。

**下記通り、JavaScript とは意味がことなるので注意。**

```
●jQueryでの return false
　親要素へのイベント伝播を止める


●JavaScript での return false
　親要素へのイベント伝播を止めない。


```
上記より、
return falseでイベントの伝播を止めたい場合は、jQueryを使う必要がある。




##■javascriptで伝播を止めたい時

stopPropagation()
（jQuery での return false と同じ動きになる。）


###■参照
https://freelance-jak.com/technology/javascript/1140/
GCPとは？アプリを簡単に作ってGAE上に上げてみたいけど...どんなイメージ?

#経緯

今後もっと成長していくであろうクラウドサービスを1から勉強しようと思い、
また、同じような境遇の方にむけて
超入門的なことを書いてみようと思いました。:relaxed:

なぜ、AWSではないかと言うと...前職で少々使っていたので
どうせならテレワーク中に未経験の新鮮なこと学びたくない?ってことでGCPを選択！

#1.GCPとは？

GCP（Google Cloud Platform）はGoogleが提供しているクラウド上のサービス集合体。
※クラウド上って?  →　とりあえずGoogleアカウント作れば、どのパソコン(オンライン)からもログインして、数多のサービスを使えるイメージ。:cloud:
インフラ、ソフトウェア等の物理的位置などを考慮せずに利用できます。
もちろんコンソール画面もあります。
各サービスのAPIを使うことでサービス間でのデータのやりとりが可能。

■主に２つが特徴
・トラフィックに俊敏に対応できる安定したインフラ環境を作成できる(色んなスマホゲームにも取り入れられている!)
・大量データを検索、解析するのに長けているサービスが豊富。(機械学習やAI技術に使われることもしばしば)

厄介な部分はAWS等と比べて、日本語表記がないので苦戦します:confounded:
→慣れてポジティブに英語が読めるようなるかもしれない!

#2.GAEとは？

GAE（Google App Engine）はGCPのサービス(PaaS)の一つ。
仮想サーバー(or Dockerコンテナ)をコマンド一つでcreateして
そこへソースをデプロイしたら、すぐにアプリケーションがインターネット経由で実装できるサービス。
※メンテナンスも簡単にできて、アクセス増減に対して簡単にスケーリングできるのも魅力！

書くプログラミング言語を指定して、実行環境を作成。(Python 、Java、Node.js、PHP 、Ruby、Go...)
→選択した言語でバックエンド処理を書くイメージ

ご時世でWeb化が進んでいくかもなので
構築作業と運用コストが省くことができ、お試しだったり安価に抑えたいアプリを作ることにも活用できるサービスです。

▼公式チュートリアルはこちら　(使用言語：Go)
[App Engine スタンダード環境での Go 1.12+ のクイックスタート](https://cloud.google.com/appengine/docs/standard/go/quickstart?hl=ja)
※フレキシブル環境は無料枠ではありません。
サンプルソースをGitHubからダウンロードして、GAE上にデプロイして、画面にHelloWorld!を出力するところまでできます

サンプルソースにapp.yamlと言う構成ファイルがあり、
上記必須ファイルなので中身見ておいて損はないかと思います。(言語Runtimeが書いてあるはずです)
[app.yaml構成ファイルとは](https://cloud.google.com/appengine/docs/standard/python3/config/appref?hl=ja#instance_class)

#3.GAEのデメリット
・使用できる言語が決まっているため、要件を満たせないことがある。(残念...)
・監視は別のクラウドサービスを行う必要がある。(Stackdriverなどで...)
・Google側で障害があった場合は、待つしかない。
etc...

GAE(PaaS)の仕様があるため
既存システムへ依存する移行、機能作成の場合は仕様が合わせれるか把握しておく必要があります。
※どうしても仕様に合わせられない場合は、フレキシブル環境を利用することで実現可能なのか視野に入れてみましょう。

#最後に...

まだまだ、未熟ですので
有識者の方々にオススメ、アドバイスだったり、
間違い箇所などを教えていただけたら幸いです。

こんなご時世だからこそ
知らないことを時間をかけて、ちょっと調べてみたりして
それが、成長とつながって毎日が楽しくなるようしていきたいですね！
GCP MonitoringのMetrics Explorerの項目覚え書き

GCPでサービスを運用しており、Monitoringを使って、調査する必要があったので調べたことをメモしていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88791/52f7c8bc-89b0-368c-8a58-3d7345573912.png)


## Find resource type and metric
省略
## Filter
省略
## Group By
省略
## Aggregator
説明そのままだと以下。


> An aggregator describes how to aggregate data points across multiple time series. By default, the aggregation results in a single line, in which the values are the result of applying the aggregator across all the time series. If Group By labels are selected, the aggregation results in a time series for each combination of matching labels.


指定された時系列を集計して出してくれる。
group byが設定されていると、一致したラベルの組み合わせごとの時系列になる。
`aggregator`に適した日本語訳って何なんだろう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88791/5eae4605-3dbb-033f-60dc-b669b87063eb.png)

## Aligner
> An aligner describes how to bring the data points in each individual time series into equal periods of time. Within each interval (determined by the alignment period) and for each time series, the data points are aggregated into a single point. The value of that point is determined by the type of aligner used.

Aggregatorで集計したデータを同期間にする方法？？
各期間の中で、データポイントは一つのポイントに集計される？？

## Alignment Period
集計期間

## Secondary Aggregator
省略

## Legend Template
凡例に命名できる。

## 参考
https://qiita.com/yokomotod/items/e29f0ffe08d38efe2d6b
https://cloud.google.com/monitoring/api/metrics
https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.alertPolicies
maven-formatterを使ってformatを自動化

# はじめに
元々はチームメンバー各々で、IDEにformatの設定をしてformatを行ってました。
githubにpushしたときに自動でformatされたら嬉しいよね。という話になり導入を行ったので、その方法をまとめます。
# 1.mavenの設定
今回は[formatter-maven-plugin](https://code.revelc.net/formatter-maven-plugin/)を使います。
## 1.1. pom.xmlにpluginを追記。
```
<project ...>
    ...
    <plugins>
      <plugin>
        <groupId>net.revelc.code.formatter</groupId>
        <artifactId>formatter-maven-plugin</artifactId>
        <version>2.11.0</version>
      </plugin>
    </plugins>
    ...
</project>
```

設定を行うと、以下のコマンドでformatまたはvalidateを実行することができるようになります。

```
// formatの実行
mvn formatter:format
// チェック
mvn formatter:validate
```


## 1.2. ディレクトリの設定
#### src/main/java または src/test/java 以外のディレクトリを指定する場合
```
<plugin>
  <groupId>net.revelc.code.formatter</groupId>
  <artifactId>formatter-maven-plugin</artifactId>
  <version>2.11.0</version>
  <configuration>
    <directories>
      <directory>${project.build.sourceDirectory}</directory>
      <directory>${project.build.directory}/generated-sources</directory>
    </directories>
  </configuration>
</plugin>
```

#### src/main/java または src/test/java の中でディレクトリやファイルを指定する場合
 - includes：対象にしたいディレクトリやファイル
 - excludes：対象外にしたいディレクトリやファイル  
**注：/までちゃんと書くこと**

```
<plugin>
  <groupId>net.revelc.code.formatter</groupId>
  <artifactId>formatter-maven-plugin</artifactId>
  <version>2.11.0</version>
  <configuration>
    <includes>
      <include>jp/****/****/****/formatter/</include>
    </includes>
    <excludes>
      <exclude>jp/relativitas/maven/plugins/formatter/special/</exclude>
      <exclude>**/*Test.java</exclude>
    </excludes>
  </configuration>
</plugin>
```
### 1.3. フォーマットファイルの設定
独自のformat設定を行うにはEclipse形式の設定ファイルを指定する必要があります。
おすすめは[googleが公開しているformat](https://github.com/google/styleguide/blob/gh-pages/eclipse-java-google-style.xml)です。

```
<plugin>
  <groupId>net.revelc.code.formatter</groupId>
  <artifactId>formatter-maven-plugin</artifactId>
  <version>2.11.0</version>
  <configuration>
    <configFile>${project.basedir}/eclipse-java-google-style.xml</configFile>
  </configuration>
</plugin>
```

# 2. Github Actionsの設定
## 2.1. YAMLファイルの作成
 `.github/workflows/` にYAMLファイルを作成します。
 実行内容は以下の3つです。
  - コードのformat
  - format後のチェック
  - formatしたコードのcommit

 ```
 name: auto-format
on:
  push:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 1.8
        uses: actions/setup-java@v1
        with:
          java-version: 1.8
      - name: format
        run: |
          mvn formatter:format
          mvn formatter:validate
      - uses: stefanzweifel/git-auto-commit-action@v3.0.0
        with:
          commit_message: Formatted code
```

# おわりに
上記の設定を行うと、githubにコードをpushしたタイミングでformatが行われたcommitが作成されます。
これによりローカルでのformat忘れがなくなり、快適にコードレビューを行うことができます。
【Javascript】CanvasをイメージPNGとして保存する

#結論

```html
<canvas id="canvas"></canvas>
<a id="download">download</a>

<script>
    const filename = "test.png";
    document.getElementById("download").onclick = (event) => {
        let canvas = document.getElementById("canvas");
        let link = document.createElement("a");
        link.href = canvas.toDataURL("image/png");
        link.download = filename;
        link.click();
    }
</script>
```