狛ログ

2020年10月27日火曜日

AWS Lambdaの関数コードでzipファイルをアップロードする。(DeprecationWarningの対応)

10月 27, 2020
オフィス狛 技術部のJoeです。

弊社のあるプロジェクトで、AWSのLambda関数でPOSTリクエストする機能があるのですが、
ある日、ログを確認すると以下のようなメッセージが出力されていました。

DeprecationWarning: You are using the post() function from 'botocore.vendored.requests'.  This dependency was removed from Botocore and will be removed from Lambda after 2021/01/30. https://aws.amazon.com/blogs/developer/removing-the-vendored-version-of-requests-from-botocore/. Install the requests package, 'import requests' directly, and use the requests.post() function instead.

【直訳】
非推奨警告:「botocore.vendored.requests」のpost()関数を使用しています。 この依存関係はBotocoreから削除され、2021/01/30以降にLambdaから削除されます。 https://aws.amazon.com/blogs/developer/removing-the-vendored-version-of-requests-from-botocore/。 リクエストパッケージをインストールし、「リクエストをインポート」して、代わりにrequests.post()関数を使用します。

このメッセージの対応として、Lambda関数のコードでzipファイルをアップロードして、
「botocore.vendored.requests」からPythonのRequestsモジュールに変更した手順をご紹介します。
(Windowsでの手順になります)


1.Pythonをインストール

公式サイトから、Pythonをダウンロードして、インストールしてください。
※今回はVersion 3.9.0 を使用しました

環境変数(Path)の追加をお忘れなく。
[インストールフォルダ]\Python39
[インストールフォルダ]\Python39\Scripts

2.pipでRequestsを指定のフォルダにインストール

適当な場所に作業用のフォルダを作成し、
コマンドプロンプトを起動し、作成したフォルダに移動してください。

以下のコマンドで、インストールします。
pip install requests -t .

成功すると、作業用フォルダの中に、以下のフォルダが作成されます。
bin
certifi
certifi-2020.6.20.dist-info
chardet
chardet-3.0.4.dist-info
idna
idna-2.10.dist-info
requests
requests-2.24.0.dist-info
urllib3
urllib3-1.25.11.dist-info

3.zipファイルの作成

Lambda関数にアップロードするzipファイルを作成します。
上記2のフォルダをzipファイルにするだけなのですが、注意点があります。

①「.dist-info」のフォルダは不要なので削除します。

②今回のように、zipファイルをアップロードするLambda関数に、既に関数コードを記述している場合、
アップロードしたタイミングで、消えてしまいます。

既存のコードを使用する場合、「2」で作成したの作業フォルダの中に、
新規で「lambda_function.py」ファイルを作成して、
既存の関数コードをコピーしてください。

上記①、②の対応をした場合、zipファイルの中身は以下のようになります。
bin
certifi
chardet
idna
requests
urllib3
lambda_function.py

4.zipファイルのアップロード

Lambdaの関数コードのアクションで、「.zip ファイルをアップロード」を選択して、
「3」で作成したzipファイルをアップロードします。

Environmentにzipしたファイルとフォルダが表示されていると思いますので、
これでPythonのRequestsモジュールが使用できます。

最後にimportを修正して、「Deploy」すれば完了です。
修正前:from botocore.vendored import requests
修正後:import requests



上記の対応で、ログから警告メッセージが消えて、問題なく動作しました。


2020年10月23日金曜日

Angular・リアクティブフォームにおけるdisabled属性の扱いについて。

10月 23, 2020

はじめまして、オフィス狛 技術部のpinoと申します。

今回は、Angularのリアクティブフォームにおけるdisabled属性の扱いについて書いてみたいと思います。

先日、Angularの実装中に以下のWarningに出会いました。
It looks like you're using the disabled attribute with a reactive form directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.

Example:
form = new FormGroup({
     first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
     last: new FormControl('Drew', Validators.required)
});

(かなり砕いた感じに)意訳すると、リアクティブフォームを使っているのに、disabled属性をHTMLに直に設定していませんか?例の書き方ならdisabled属性をDOMで設定することができます!といった具合でしょうか。

Warningなので最悪このままにしておいてもいいかもしれませんが、できることなら解消しておきたいですよね。

上記のWarningは、文言に忠実に従うことで回避できます。
以下、応用も含めて例を紹介します!

Formの定義

修正前は、以下のFormを定義していることにします。
(わかりやすく、Warningの文中にあった例を流用したいと思います。)
HTML
... // 中略

  <form [formGroup]="form">
    <input type="text" disabled formControlName="first">
    <input type="text" formControlName="last">
  </form>

...
TS
... // 中略

  form = new FormGroup({
    first: new FormControl('Nancy', Validators.required),
    last: new FormControl('Drew', Validators.required)
  });

...
FormBuilderを使用する場合
... // 中略

  form = this.fb.group({
    first: ['Nancy', Validators.required],
    last: ['Drew', Validators.required]
  });

  constructor(private fb: FormBuilder) { }

...
* FormBuilderを使用する場合は、DIが必要です。 以降は、記述を省略します。

Warningの出る書き方

HTMLで以下のようにdisabled属性を設定していると、上記のWarningが出ます。
例)
  // ①HTMLの書き方そのまま
  <input type="text" disabled>

  // ②バインディング構文使用、設定値は固定
  <input type="text" [disabled]="true">

  // ③バインディング構文使用、設定値は可変
  <input type="text" [disabled]="form.invalid">

③もダメなんですね...。


解消方法

1. 基本に従う
Warningの文言にある通りに書き換えます。
HTML
... // 中略

  <form [formGroup]="form">
    <input type="text" formControlName="first">
    <input type="text" formControlName="last">
  </form>

...
TS
... // 中略

  form = new FormGroup({
    first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
    last: new FormControl('Drew', Validators.required)
  });

...

HTMLからdisabledの記述を無くし、
コンポーネント側に {value: 'Nancy', disabled: true} という記述を増やしました。
ちなみにdisabled: trueで非アクティブ、disabled: falseでアクティブとなります。

FormBuilderを使用する場合は、以下となります。
... // 中略

  form = this.fb.group({
    first: [{value: 'Nancy', disabled: true}, Validators.required],
    last: ['Drew', Validators.required]
  });

...
こちらも、{value: 'Nancy', disabled: true} を追記すればOKです。

2. 動的に設定する
動的に切り替えたい時の方法です。
HTML
... // 中略

  <input type="text" formControlName="first">
  <input type="text" formControlName="last">
  <input type="checkbox" (click)="switchDisabled()">チェックするとfirstが非アクティブになります

...
TS
... // 中略

  isDisabled = true;

  form = this.fb.group({
    first: [{value: 'Nancy', disabled: this.isDisabled}, Validators.required],
    last: ['Drew', Validators.required]
  });

...

  switchDisabled() {
    if (this.isDisabled) {
      this.form.controls.address.enable();
    } else {
      this.form.controls.address.disable();
    }
    this.isDisabled = !this.isDisabled;
  }

...

切り替え用のチェックボックスを置いてみました。
ポイントは、コンポーネント側のenable()disable()です。
これにより、アクティブ・非アクティブを切り替えることができます。

今回はチェックボックスを使用しましたが切り替えるためのトリガーはなんでもOKですし、
切り替えが必要ない場合は、ngOnInit()に処理を置いて初期状態の判断に使用するのも良さそうです!

補足

ここまで説明してきましたが、実はdisabledをこのように指定しなくてもWarningが出ない要素もあります。
今のところそのように把握しているのは、button要素とoption要素です。

さらに、前述したWarningの出る書き方の例でいうと

button要素 → ①, ②, ③ どの書き方をしても怒られない
option要素 → ①は怒られる。②と③なら怒られない

という結果になりました。

この辺りは、余裕ができたらもう少し詳しく調べてみたいですね。


今回はここまで。
簡単な内容だったと思いますが、参考になれば嬉しいです。

2020年10月21日水曜日

Angular・HttpParams を動的に追加したい。

10月 21, 2020

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。


Angularで、バックエンドAPIとやり取りする時は、
HttpClient」と「HttpParams」を使うと思います。

「HttpParams」はGETやDELETEの時のクエリパラメータを設定する時に使いますね。

  getHogeInfo(req: HogeInfoRequestModel): Observable<HogeInfoModel> {
    const options = {
      params: new HttpParams()
        .set('hogeId', req.hogeId)
        .set('testCode', req.testCode)
    };

    return this.http.get('xxxxx/yyyyy/hoge/info', options).pipe(tap(console.log));
  }

この時、クエリパラメータ「koma」が配列型式で追加になったとします。

よっしゃ、追加したろ、という感じで、
  getHogeInfo(req: HogeInfoRequestModel): Observable<HogeInfoModel> {
    const options = {
      params: new HttpParams()
        .set('hogeId', req.hogeId)
        .set('testCode', req.testCode)
    };

    body.komaArray.forEach((item, index) => {
      options.params.append(`koma[${index}]`, item);
    });

    return this.http.get('xxxxx/yyyyy/hoge/info', options).pipe(tap(console.log));
  }
とやると、うまくいきません・・・・。

ちょっとハマったのですが、

HttpParamsのappend()は、appendを実施したHttpParams自身には影響を及ぼさず、単純に追加したオブジェクトを返却するのみ。

と言う事です。つまり、
    body.komaArray.forEach((item, index) => {
      options.params = options.params.append(`koma[${index}]`, item);
    });

こんな感じで、「options.params」に値を再設定しないといけません。

結構単純なところにハマってしまうものですね。

Amazon WorkSpaces で気になったことを調べてみました。

10月 21, 2020

オフィス狛 技術部のJoeです。

先日、担当したプロジェクトの開発で、AWS仮想デスクトップサービスの WorkSpaces を利用しました。

WorkSpacesの概要は、Amazon WorkSpacesのページを見ると判るのですが、
利用するにあたり、事前に気になって調べた内容を共有しようと思います。
※こちらは、2020年7月時点の情報になります


1.どのOSが選択できるのか?

以下から選択可能です。
・Windows 10(バンドルでoffice2010、2013、2016を選択できました)
・Linux2

今回はWindows10を選択して使用したのですが、
実際にWorkSpacesに接続して、OSのシステムを確認すると、
なぜか「Windows Server 2016 Datacenter」になっていました。

2.ボリュームサイズは拡張できるのか?

WorkSpacesを起動(作成)する時に、ユーザーボリュームサイズを10~100GBを選択できます。
WorkSpacesを起動(作成)後も、最大2,000GBまで拡張できます。
注意点としましては、サイズを縮小することはできませんでした。

3.接続方法は?

以下で接続可能です。
ただし、WorkSpacesを起動(作成)するディレクトリの「アクセス制御のオプション」で、有効化する必要があります。
・クライアント
 ※WorkSpacesのページからダウンロードできます。(Windows版、Mac版などあります)
・Webブラウザ

4.1つのWorkSpacesに複数ユーザーで同時接続できるか?

できませんでした。1セッションのみ有効です。

5.固定IP(Elastic IP)を利用できるか?

利用できます。
ただし、注意点としまして、WorkSpacesを起動(作成)するディレクトリの「インターネットへのアクセス」が有効になっていると、
WorkSpacesを起動(作成)時に、パブリック IPが自動で割り当てられてしまうため、
Elastic IPを関連付けすると、エラー「既にパブリックIPが関連付けられています」になってしまいました。

いろいろ試してみましたが、新たにElastic IPを関連付けできず、結局、下記のように新たにWorkSpacesを起動して対応しました。
①WorkSpacesを起動(作成)するディレクトリの設定で「インターネットへのアクセス」を無効化する
②WorkSpacesを起動(作成)する
②WorkSpacesにElastic IPを関連付けする

6.WorkSpacesに接続するIPアドレスを制限できるか?

制限できます。
まず、WorkSpacesサービスのIPアクセスコントロールで、接続するIPの「IPグループ」を作成して、
WorkSpacesを起動(作成)するディレクトリの「IPアクセスコントロールグループ」で、
作成した「IPグループ」を選択することで制御できます。

7.クライアントからWorkSpacesにファイルをコピーできるのか?

直接ファイルをコピーすることはできません。
Amazon WorkDocsを利用することで、ファイルの共有が可能になります。


WorkSpacesをご利用される際に、お役に立てれば幸いです。


2020年9月30日水曜日

Photoshopで出会った謎の「インデックス」レイヤーを編集できるようにする!

9月 30, 2020

こんにちは、オフィス狛 デザイン部のSatoです。


先日pngファイルをPhotoshopで開いて編集しようと思った際に編集できず、困ったことがありました。

レイヤーロックを解除しようとレイヤーの上でダブルクリックしても解除できないのです。

よく見てみるとロックされているレイヤー名が「インデックス」になっています。

「インデックス」レイヤーは普段Photoshopでよく見る背景レイヤーと違い、レイヤーパネルの操作だけではロックが解除できずどうしたら編集できるのか分かりづらかったので、ロック解除のしかたを記載します。


まずレイヤーパレットからはインデックレイヤーを普通のレイヤーにすることはできません。レイヤーのカラーモードが関係しているので、レイヤーのカラーモードを通常のカラーモード(RGBやCMYKなど)に変更する必要があります。

Photoshopメニューバーの「イメージ」内の「モード」にマウスカーソルを合わせます。

インデックスカラーにチェックがついているので「RGBカラー」や「CMYKカラー」など使用する用途に合わせた項目をクリックします。

この操作で、今までインデックスレイヤーとして変更ができなかったレイヤーが通常レイヤーに変わり、ロックも外れて編集ができるようになります。


インデックスカラーの事を今回初めて知ったので、Adobeのカラーモードの説明を読んでみた所、

インデックスカラーモードは使用できる色を制限する事で目に見える画質を維持しながらファイルサイズを小さく保存できるというなかなか魅力的なカラーモード……。のようですがレイヤーが編集できないので何も知らないまま開くと少しびっくりしますね。

2020年6月30日火曜日

angularでの複数チェックボックスの取り扱い。

6月 30, 2020

オフィス狛 技術部のHammarです。

最近久々にAngularプロジェクトを開発する機会があり、そこでまたいろいろと勉強になったことがあったので、ご存知の方には基本的なお話しなのかとは思いますが、今回は「angularで複数のチェックボックスを使った記述方法」の一例をご紹介できればと思います。

■作りたい画面

今回作る画面のイメージとしては、下記のような登録画面で、基本情報の入力と共にチェックボックスが複数設置され、その内容をフォーム送信して登録というようなシンプルな画面構造を想定します。

画面イメージ



■コンポーネント

まずコンポーネント側の記述ですが、今回フォーム送信を行うので、Angularのリアクティブフォームから、FormControl、FormGroup、FormBuilder、FormArrayクラスをそれぞれ利用します。 各クラスの細かな説明はAngularの公式ドキュメント内のリアクティブフォームの説明を見ていただいたほうがわかりやすいかもですが、めちゃくちゃざっくり書くとこんな感じかなと思います。
FormControl:単一入力欄の制御
FormGroup:FormControlのグループ化
FormBuilder:FormControlの作成に使う(FormControlをたくさん扱う場合にインスタンス不要)
FormArray:FormGroup内の配列要素コントロールの制御
さらに、今回は入力の必須チェックも行いたいので、Validatorsクラスもインポートします。

では、実際に上記をインポートし、コンポーネント側を記述します。
export class AppComponent implements OnInit {
  favoriteFruitsList: {key: string, value: string}[] = [
    {key: '1', value: 'りんご'},
    {key: '2', value: 'バナナ'},
    {key: '3', value: 'みかん'},
    {key: '4', value: 'ぶどう'},
  ];

  checkBoxFormArray: FormArray;
  groups: FormGroup[] = [];

  form: FormGroup = this.formBuilder.group(
    {
      name: ['', [Validators.required]],
      age: ['', [Validators.required]],
      tel: ['', [Validators.required]],
      formFavoriteFruitsList: this.formBuilder.array(this.groups, [Validators.required, this.checkBoxValidator]),
    }
  );

  constructor(
    private formBuilder: FormBuilder,
    public v: UserValidator,
  ) {}

  ngOnInit() {
    this.v.formGroup = this.form;
  }

  checkBoxValidator(control: FormArray) {
    for (let i = 0, loop_cnt = control.length; i < loop_cnt; i++) {
      if (control.at(i).value) {
        return null;
      }
    }
    return { unChecked: true };
  }

  onChange(value, isChecked: boolean) {
    this.checkBoxFormArray = this.form.get('formFavoriteFruitsList') as FormArray;
    if (isChecked) {
      this.checkBoxFormArray.push(new FormControl(value));
    } else {
      const index = this.checkBoxFormArray.controls.findIndex(x => x.value === value);
      this.checkBoxFormArray.removeAt(index);
    }
  }

  onSubmit() {
   // 登録処理
  }

}

form: FormGroup = this.formBuilder.group(...
の記述で、まずはフォームの初期設定をおこないます。
ここでチェックボックスとなるformFavoriteFruitsListはarray()メソッドでフォーム配列とし、さらにその中でチェックボックスをグループ化しています。これはcheckBoxValidatorでチェックボックスの必須チェックの為にこのような記述にしておきます。
ちなみにここで作成したcheckBoxValidatorは、チェックボックスのいずれかにチェックが入っているかのバリデーションで、Validatorsクラスには無いオリジナルのバリデーションになります。
onChangeについてはhtml側の記載で説明します。

■html

<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <h2>名前</h2><input type="text" formControlName="name">
  <h2>年齢</h2><input type="text" formControlName="age">
  <h2>電話</h2><input type="text" formControlName="tel">
  <h2>好きな果物</h2>
  <ng-container formArrayName="formFavoriteFruitsList">
    <ng-container *ngFor="let item of favoriteFruitsList;">
      <label>
        <input type="checkbox" [value]="item.key" (change)="onChange(item.key, $event.target.checked)"><span>{{ item.value }}</span>
      </label>
    </ng-container>
  </ng-container>
  <button type="submit">登録</button>
</form>

ここでformArrayNameにコンポーネント側で設定したformFavoriteFruitsListを記載します。
これによりこのチェックボックスは配列要素のフォームとして扱います。
またここで、コンポーネント側に作成した、チェックボックスをチェックした時に発火するonChangeを使います。
チェックをON、OFFする際に、checkBoxFormArrayの配列内で、値を追加、削除する関数になります。
これにより、submitした時に、実際にチェックした値だけフォーム送信されます。


今回記載した内容はあくまで一例で、結構他の記載方法もあるようで、もしかしたらもう少し簡単に書ける部分もあるかもしれません。
また画面的には非常にシンプルなのですが、まだAngularのリアクティブフォームの使い方をうまく把握できていないと最初は難しく、結構苦戦しました(苦笑)。

自分のようなAngular初心者の方で、もしご参考になればと思います。

2020年6月4日木曜日

nodemonを使用して、コード変更後に自動でNode.jsのアプリを再起動する方法。

6月 04, 2020

オフィス狛 技術部のmmm(むー)です。

Node.jsで動いているアプリケーションのコードの修正をした後、サーバーの再起動が必要になリます。
コードを修正した後、毎回手動で再起動するのは面倒ですし、再起動し忘れることもあります。
nodemonを使用すれば、その煩わしい作業を自動化できます😶

nodemonとは
nodemonとはNode.jsベースのアプリケーションのコードの変更を監視し、変更があった際に自動でサーバーの再起動をしてくれるツールです。
参考:https://nodemon.io/

【前提条件】
Nodeとnpmがインストールされていること

1. nodemonのインストール

使用したいプロジェクト配下に移動し、下記を実行します。
devDependencies オプションは開発環境でのみ使用するため、今回つけています。
$ cd ~/<プロジェクトパス>
$ npm install nodemon --save-dev

--save-dev オプションを使用してインストールすると、 package.json に下記のように追記されます。
devDependencies 配下のパッケージは本番環境で npm install --production コマンドを実行すると除外されます。
// package.json

"devDependencies": {
    "nodemon": "^2.0.3"
  }

2. nodemonでアプリケーションを実行する

package.jsonに nodeコマンドの代わりにnodemonで起動するscriptを追加します。
script名(start-watch)は自分可変です。
   // package.json

"scripts": {
     "start": "node src/index.js",
     "start-watch": "nodemon src/index.js", // 🌟追加
     "test": "echo \"Error: no test specified\" && exit 1"
   },

nodemonを使用して、アプリを起動します。
$ npm run start-watch

下記のように起動ログが出力されれば、起動成功です。
 [nodemon] watching path(s): *.*
 [nodemon] watching extensions: js,mjs,json
 [nodemon] starting `node src/index.js`

コードに何か変更があった場合、下記のようにログが出力されます。
 [nodemon] restarting due to changes...
 [nodemon] starting `node src/index.js`

また手動で再起動したい場合は、 rs コマンドを打ってください
$ rs
 
 [nodemon] starting `node src/index.js`

以上となります。
インストールして少し設定するだけで、開発が楽になるのでまだ使用していない方はぜひお試しください🍭