新型コロナウイルス(COVID-19)の Lightning Web Component を作ってみた感触


背景

最近Lightning Web Component(LWC)を触ってみて、実践的な開発経験をしてみたいと思ってました。そして現在新型コロナウイルスが広がり、何か自分でも社会貢献にできることを考えました。

アイデア

Salesforceオブジェクトのレコードページ内にレコードの国名に関連する感染者情報を表示させたい。更に、感染者情報のトレンドも一目でわかるようにグラフで表現させたい。

やり方

国名データ抽出

そもそも国名の情報を持ってるSalesforceオブジェクトは取引先と取引先責任者のみなので、これらのオブジェクト限定で対応する形になります。デベロッパーエディションなどで、サンプルデータをみてみたら取引先オブジェクトの中に請求先の国と市区郡(BillingCountryと BillingCity)が一番記入されましたし、そして取引責任者は郵送先の国と市区郡(MailingCountryとMailingCity)が一番多かったと見てましたので、「BillingCountry、BillingCity、MailingCountryとMailingCity」を元に国名のデータを抽出します。

COVID-19データソース

色々調査し、比較した結果今回こちらのデーターソースを利用してます。毎日3回アップデートされるので、最新データを取得できるかと思います。

グラフ描画

ChartJSを利用します。こちらの公式記事にも書いてありますので、LWCでグラフを描画させるのはChartJSが一番最適です。

ロジックと流れ

こんな感じでロジックと流れを考えました。

アーキテクチャー

データソースはAPIではなくJSONファイルのため、LWCに渡る前に事前処理をしないといけません。そのために、中間APIが必要です。中間APIは事前処理のためだけではなく、キャッシュをさせるためもできます。それを実現したら、こんな感じになります。

直面した課題

オブジェクトによりレコードを取得

LWCでレコードを取得するのは@wireで取得する必要があります。しかし@wireでのオブジェクトカラムの定義、普通は静的でないといけないので、今回のように取引先オブジェクトかどうかを検出し定義するカラムを決める、という動的な定義はできませんでした。


import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name';

export default class Record extends LightningElement {
    @api recordId;
    // 静的でアブジェクトカラムを定義
    @wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME_FIELD] })
    record;
}

解決方法 - $

$プリフィックスはよく@wireの中にレコードIDなどに取得するために使用すると思いますが、実はこの記号を付けると変数をリアクティブかつ動的にすることができたと以下の記事に書いております
wireサービスの公式記事
つまり、変数をfieldsに入れて値が変わる毎にwireを動的に実行するという実装ができました。こんな感じです。

import { LightningElement, wire, api } from 'lwc';

export default class Covid19 extends LightningElement {
  @api recordId;
  @api objectApiName;
  fields;

  @wire(getRecord, { recordId: '$recordId', fields: '$fields' })
  load(result) {
    if (result.data) {
      this.record = result.data.fields;
      this.fetchApi()
    }
  }
  async connectedCallback() {
    if (this.objectApiName === 'Account')
      this.fields = [ACCOUNT_BILLING_CITY, ACCOUNT_BILLING_COUNTRY];
    else
      this.fields = [CONTACT_MAILING_CITY, CONTACT_MAILING_COUNTRY];
  }
}

後、ご存知ない方も居るかもしれないので、htmlに使用する変数は@track必要ありません。こちらのプルリクエストを参考して下さい。

レコードに国名が含まれない時

必ずやレコードの中に国名が含まれるわけではないですが、JSONでは国名がキーになってます。

解決方法

all-the-citiesというnpmパッケージを中間APIに導入し、フィルタリングします。コードはこんな感じです。

import Cities from 'all-the-cities'

export const searchCountry = (city, flags) => {
  const dataList = Cities.find((cityName) => {
    return cityName.name.toLowerCase().match(city.toLowerCase())
  })
  const countryCode = dataList.country
  const flagsKeys = Object.keys(flags)
  const flagsValues = Object.values(flags)
  const idx = flagsValues.findIndex((country) => {
    return country.code.match(countryCode)
  })
  return flagsKeys[idx]
}

国名の類義語 (サーバー側課題)

今回中間APIを使って、データソースのJSONリストから該当の国のデータのにを抽出します。しかし、世界には国の名前の呼び方がいくつかあるという国が存在してます。日本ではJapanということで完結できましたが、例えば米国とかだと色々な国名があります。USUSAUnited StatesUnited States of Americaなどでも同じ国を指してます。JSON側は一つしか存在してないので、問題が起こりました。実施にこちらのissueにこのような問題が上がりました。

解決方法 - 類義語システム

上記の問題にならないために、中間サーバーに類義語システムを実装しました。例:先ほどの米国とかはUSUSAUnited StatesUnited States of AmericaなどをUSに変換し、JSONからデータを抽出されます。

ソースコード

完成したソースコードはこちらGithubにありますので、どうぞ自由に使ってください。

フィードバックやプルリクエスト

何かありましたらこちらのGithub IssueGithub PRを活用してください。

いやあ、久々に書きました。そのためまだ書き慣れてないので、何か訂正があればコメントをお願いします。