Datadogリアルユーザーモニタリングを試してみました

タレンティオの森脇です。

今回は、Datadogの機能の一つである、リアルユーザーモニタリングについて簡単に試してみたので紹介します。

docs.datadoghq.com

リアルユーザモニタリングとは

リアルユーザーモニタリング (以下 RUM) は、Webブラウザ・モバイルアプリユーザのアクティビティ・パフォーマンス・エラー状況などを可視化することができます。

以降では、Webブラウザの機能について紹介します。

導入手順

Datadog上のナビゲーションに沿って、必要事項を指定することで導入用のコードが発行できます。
インストールタイプにCDNを選択した場合は、以下のようなコードが発行されるため、アプリケーションの適切な箇所に埋め込みます。

<script>
  (function(h,o,u,n,d) {
    h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
    d=o.createElement(u);d.async=1;d.src=n
    n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
  })(window,document,'script','https://www.datadoghq-browser-agent.com/us1/v5/datadog-rum.js','DD_RUM')
  window.DD_RUM.onReady(function() {
    window.DD_RUM.init({
      clientToken: '[CLIENT_TOKEN]',
      applicationId: '[APPLICATION_ID]',
      site: 'datadoghq.com',
      service: 'my-application',
      env: 'development',
      sessionSampleRate: 100,
      sessionReplaySampleRate: 100,
      trackUserInteractions: true,
      trackResources: true,
      trackLongTasks: true,
      defaultPrivacyLevel: 'mask-user-input',
    });
  })
</script>


RUMはセッション数に応じた従量課金ですが、sessionSampleRateで追跡対象とするセッション比率を制限できます。

主な機能

RUMの主な機能について紹介します。

ダッシュボード

RUMが有効になると、ダッシュボードで主要メトリクスが表示されます。

ダッシュボード

ページのパフォーマンスの監視

RUMでは、Web Vitals  |  Articles  |  web.devで説明されている、以下の3つのメトリクスを収集しています。

  • Largest Contentful Paint (LCP)
    • ページ読み込みタイムライン上で、ビューポート内の最大の DOM オブジェクトがレンダリングされた時点を示します。
    • 読み込みのパフォーマンスを測定するための指標です。
  • First Input Delay (FID)
    • ユーザーがページを最初に操作してからブラウザが応答するまでの経過時間です。
    • インタラクティブ性を測定するための指標です。
  • Cumulative Layout Shift (CLS)
    • 動的に読み込まれるコンテンツ (サードパーティの広告など) による予期しないページ移動を定量化します。0 はシフトが発生していないことを意味します。
    • 視覚的な安定性を測定するための指標です。

ページごとに、上記メトリクスの 75 パーセンタイルの値を確認できます。

パフォーマンスメトリクス

ユーザーアクションの追跡

ユーザがブラウザ上で行ったアクションを自動で検出できます。

ユーザアクション

ブラウザエラーの収集

RUMでは、フロントエンドのエラーを収集します。発生要因に応じて以下の3つのカテゴリに分類されます。

  • source: 未処理の例外または未処理のプロミス拒否
  • console: console.error() API 呼び出し
  • custom: addError API を利用して送信されるエラー

エラーを手動で収集する場合は、addError APIを利用します。

fetch('https://example.com/hoge').catch(function(e) {
     window.DD_RUM.onReady(function () {
          window.DD_RUM.addError(new Error(e), {
             hoge: 'fuga',
          })
     })
})

エラー内容とエラー発生時のユーザアクションが確認できます。

以下の例では、「削除」ボタンをクリック時に、エラーが発生していることが確認できます。

custom error

セッションリプレイ

セッションリプレイは、ユーザーのブラウザ操作をキャプチャして視覚的に再生できるようにする機能です。
以前に紹介したSentryのセッションリプレイと同じ機能です。Sentryのセッションリプレイについては、以下の記事をご覧ください。

Sentry Session Replay機能の紹介 - Talentio Tech Blog

以下は、Datadog RUMをTalentioで動かしたときのイメージです。

session reply
オプション

初期時のsessionReplaySampleRateで、セッションリプレイ対象とするセッション比率を指定できます。これは、sessionSampleRateに対するパーセンテージを指定します。

例えば、sessionSampleRate を 60、sessionReplaySampleRate を 50 に設定すると、全体の60%のセッションがRUM対象となり、その50%である全体の30%がセッションリプレイ対象となります。

  window.DD_RUM.onReady(function() {
    window.DD_RUM.init({
      .....
      sessionSampleRate: 60,
      sessionReplaySampleRate: 50
    });

セッションリプレイでは、機密データや個人データを自動的にマスクする機能が提供されています。

defaultPrivacyLevelmaskにすると、すべての HTML テキスト、ユーザー入力、画像、リンクがマスクされます。defaultPrivacyLevelmask-user-inputにすると、入力フォームフィールドの内容がマスクされます。

  window.DD_RUM.onReady(function() {
    window.DD_RUM.init({
      .....
      defaultPrivacyLevel: 'mask',
    });

ヒートマップ

RUMには、セッションリプレイデータにユーザーのインタラクションを重ねて視覚化した、3 種類のヒートマップがあります。

Click map

特定のページのユーザーのクリックアクションを集計し、視覚的に表示します。

click map


アクションごとのクリック数・比率を、数値で確認することもできます。

click map action
Top Element

特定のページで最もインタラクションがあった要素の上位 10 位までの順位を表示します。

Top Element
Scroll map

特定のページのスクロールアクティビティの集計を視覚的に表示します。

ページの平均折り返し位置など、ユーザーがページをどこまでスクロールしたかを表示します。ページの平均折り返し位置や、指定した深さまでスクロールしたユーザーの数を確認できます。

scroll map

フラストレーションシグナル

ユーザーがフラストレーションを感じる箇所をシグナルとして収集します。

  • レイジクリック
    • 1秒間のスライディングウィンドウの中で、ユーザーが 3 回以上要素をクリックした場合。
    • 反応が悪いので、同じ箇所何度もクリックしている状態。
  • デッドクリック
    • ユーザーが静的な要素をクリックしても、そのページでは何のアクションも起こらないこと。
  • エラークリック
    • JavaScript のエラーが発生する直前に、ユーザーがある要素をクリックした場合。

以下の例では、デッドクリックが発生していることがわかります。

フラストレーションシグナル

利用料金

料金は1,000セッションごとの従量課金となっており、RUMのみまたはRUMとセッションリプレイを組み合わせたプランがあります。


www.datadoghq.com

RUMのみ(月額)
  • $1.50 / 1,000 セッション(年払い)
  • $2.20 / 1,000 セッション(オンデマンド)
RUM & セッションリプレイ(月額)
  • $1.80 / 1,000 セッション(年払い)
  • $2.60 / 1,000 セッション(オンデマンド)

セッションは、以下のように定義されています。

  • 15 分間操作が行われないとセッションが切れる
  • セッションの持続時間は 4 時間で、4 時間経つと新しいセッションが自動的に作成される

前述の通り、sessionSampleRateパラメータでサンプリングするセッションの比率を制限することで、利用料金を抑えることも可能です。

まとめ

本記事では、Datadogのリアルユーザーモニタリング(RUM)について紹介しました。
RUMを利用すると、フロントエンドのエラーの解決だけではなく、UXの改善にも役立ちそうです。

Amazon SNSからSlackに通知する仕組みを導入した話(実装編)

宮下(mizzy)です。

Amazon SNSからSlackに通知する仕組みを導入した話(概要編)でご紹介した仕組みの実装面について解説します。


TerraformによるAWS上のリソース設定

SNS TopicとLambda Functionの関連づけ

Slack通知用SNS Topicの作成と、実際に通知を行うLambda Functionの関連づけは、Terraformコードでこのように記述しています。

ただし、Lambda FunctionはTerraformではなくecspressoで管理しているため、Lambda Functionの情報はData Sourceで読み込んでいます。

data "aws_lambda_function" "sns_to_slack" {
  function_name = "sns-to-slack"
}

resource "aws_sns_topic" "slack_notification" {
  name = "slack-notification"
}

resource "aws_sns_topic_subscription" "slack_notification" {
  endpoint  = data.aws_lambda_function.sns_to_slack.arn
  protocol  = "lambda"
  topic_arn = aws_sns_topic.slack_notification.arn
}

resource "aws_lambda_permission" "slack_notification" {
  action        = "lambda:InvokeFunction"
  function_name = data.aws_lambda_function.sns_to_slack.function_name
  principal     = "sns.amazonaws.com"
  source_arn    = aws_sns_topic.slack_notification.arn
}

Bot User OAuth Tokenの格納場所作成とアクセス権の設定

Lambda FunctionからSlackへの接続時に必要な、Bot User OAuth Tokenを格納するためのSSMパラメータを作成し、Lambda Functionから値を読み取れるよう権限設定しています。

Bot User OAuth Tokenの値はTerraformでは設定しないため、ダミーの値を設定し、ignore_changesで変更を無視するようにしています。

resource "aws_ssm_parameter" "talentio_ruboty_bot_user_oauth_token" {
  name  = "/slack/bot-user-oauth-token/TalentioRuboty"
  type  = "SecureString"
  value = "dummy"

  lifecycle {
    ignore_changes = [value]
  }
}

resource "aws_iam_role" "sns_to_slack" {
  name               = "sns-to-slack"
  assume_role_policy = data.aws_iam_policy_document.sns_to_slack_assume_role.json
  inline_policy {
    name   = "inline"
    policy = data.aws_iam_policy_document.sns_to_slack.json
  }
}

data "aws_iam_policy_document" "sns_to_slack_assume_role" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      identifiers = ["lambda.amazonaws.com"]
      type        = "Service"
    }
  }
}

data "aws_iam_policy_document" "sns_to_slack" {
  statement {
    effect  = "Allow"
    actions = ["ssm:GetParameter"]
    resources = [
      aws_ssm_parameter.talentio_ruboty_bot_user_oauth_token.arn,
    ]
  }
}

SNSにPublishする際、以下のようにSlackAppでSlack App名を指定するようにしていますが、ここで指定された値によって、Tokenを読み取るSSMパラメータを変更することで、複数のSlack Appに対応できるようにしています。

aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxxx:slack-notification \
  --subject 'Message from CLI' \
  --message '{
    "SlackApp": "TalentioRuboty",
    "SlackChannel": "mizzy-test"
  }'

通知用Lambda Function

Slackへ通知を行うLambda Function本体は、以下のような125行のRubyコードです。

require 'json'
require 'aws-sdk-ssm'
require 'uri'
require 'net/http'
require 'slack-ruby-client'

def handler(event:, context:)
  event = event['Records'][0]

  subject = event['Sns']['Subject']
  message = JSON.load(event['Sns']['Message'])

  if message['Status'] == 'Success'
    title = ":white_check_mark: #{subject}"
  elsif message['Status'] == 'Fail'
    title = ":x: #{subject}"
  elsif message['Status'] == 'Warning'
    title = ":warning: #{subject}"
  else
    title = ":information_source: #{subject}"
  end

  blocks = [{
              type: 'header',
              text: {
                type: 'plain_text',
                text: title,
              },
            }]

  if !message['User'].nil?
    blocks.append(
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: "<@#{message['User']}>",
        },
      }
    )
  end

  fields = []

  if !message['Parameters'].nil?
    message['Parameters'].each do |key, value|
      fields.append({
        type: 'mrkdwn',
        text: "*#{key}:*\n #{value}",
      })
    end
  end

  if !message['StartTime'].nil?
    start_time = Time.parse(message['StartTime'])
    end_time = Time.now
    fields.append({
      type: 'mrkdwn',
      text: "*Elapsed time:*\n #{((end_time - start_time) / 60).round(1)} minutes",
    })
  end

  # fieldsが空の場合Slackがinvalid_blocksエラーを返すので空白文字で埋める
  if fields.size == 0
    fields.append({
      type: 'mrkdwn',
      text: ' ',
    })
  end

  blocks.append({
    type: 'section',
    fields: fields,
  })

  if !message['Arn'].nil?
    link = arn_to_url(message['Arn'])
    blocks.append(
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: "*Link:*\n<#{link}|#{link}>",
        },
      }
    )
  end

  if !message['Url'].nil?
    blocks.append(
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: "*Link:*\n<#{message['Url']}|#{message['Url']}>",
        },
      }
    )
  end

  ssm = Aws::SSM::Client.new

  req = {
    name: "/slack/bot-user-oauth-token/#{message['SlackApp']}",
    with_decryption: true,
  }

  res = ssm.get_parameter(req)

  Slack.configure do |config|
    config.token = res.parameter.value
  end

  client = Slack::Web::Client.new
  client.auth_test
  client.chat_postMessage(channel: message['SlackChannel'], text: title, blocks: JSON.dump(blocks), as_user: true)

  return
end

def arn_to_url(arn)
  elements = arn.split(':')
  region = elements[3]
  "https://console.aws.amazon.com/go/view?arn=#{arn}"
end

おわりに

Amazon SNSからSlackに通知する仕組みは、各社さんそれぞれ実装していると思いますし、検索すると色々と記事が出てくるのですが、実運用レベルでどのように実装しているか、といった記事はあまりなさそうでしたので、実例として紹介してみました。

通知にはLambda FunctionではなくAWS Chatbotの利用も考えましたが、通知内容のカスタマイズを柔軟に行うことができなさそうだったので、Lambda Functionを利用する形になりました。

Amazon SNSからSlackに通知する仕組みを導入した話(概要編)でも書きましたが、元々、このような仕組みを導入したのは、AWS Step Functionsから簡単に通知できるようにしたかったからです。

Step Functionsを実際の運用でどのように利用して、どのように通知しているか、という具体例については、また後日ご紹介できればと思います。

Amazon SNSからSlackに通知する仕組みを導入した話(概要編)

宮下(mizzy)です。

タレンティオ社内で、Amazon SNSにPublishするとSlackに通知できる仕組みを導入しました。このような仕組みの事例は検索するとたくさんでてきますが、タレンティオではこうやってますよ、という例としてご紹介したいと思います。

このエントリでは、利用者視点で具体的にどのようにSNSにPublishし、どのような通知が得られるのか、について解説します。実装の詳細についてはAmazon SNSからSlackに通知する仕組みを導入した話(実装編)で解説します。


通知の投げ方

AWS CLIを利用する場合は、次のようにSNSにPublishします。

aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:slack-notification \
  --subject 'Message from CLI' \
  --message '{
    "SlackApp": "TalentioRuboty",
    "SlackChannel": "mizzy-test"
  }'

タレンティオではRubyをメインで利用しているのですが、Rubyの場合は次のようにPublishします。

require 'aws-sdk-sns'

sns = Aws::SNS::Client.new

sns.publish(
  {
    topic_arn: 'arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:slack-notification',
    subject: 'Messagge from Ruby',
    message: JSON.dump(
      {
        SlackApp: 'TalentioRuboty',
        SlackChannel: 'mizzy-test',
      }
    ),
  }
)

このようにPublishすると、次のように通知されます。


通知内容のカスタマイズ

各種パラメーターを指定することで、通知内容カスタマイズすることが可能です。

Status

Statusを指定すると、タイトルの先頭の絵文字を変えることができます。

aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:slack-notification \
  --subject 'Message from CLI' \
  --message '{
    "SlackApp": "TalentioRuboty",
    "SlackChannel": "mizzy-test",
    "Status": "Warning"
  }'

このようにStatusWarningを指定すると、次のように通知されます。

Statusに指定するパラメータによって、アイコンは以下のように変わります。

  • 何も指定しない場合はℹ️
  • Successの場合は✅
  • Failの場合は❌
  • Warningの場合は⚠️

Parameters

Parametersでは、任意で表示したい内容を、キーと値の形で指定します。キーや値は文字列であれば何でも大丈夫です。

aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:slack-notification \
  --subject 'Message from CLI' \
  --message '{
    "SlackApp": "TalentioRuboty",
    "SlackChannel": "mizzy-test",
    "Status": "Success",
    "Parameters": {
       "Date": "2023/06/28",
       "Area": "Kanagawa",
       "Weather": ":sun_behind_rain_cloud:"
    }
  }'

このようにPublishすると、次のように通知されます。

User

Userで指定したユーザに対してメンションします。

aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:slack-notification \
  --subject 'Message from CLI' \
  --message '{
    "SlackApp": "TalentioRuboty",
    "SlackChannel": "mizzy-test",
    "Status": "Fail",
    "Parameters": {
       "Date": "2022/09/15",
       "Area": "Kanagawa",
       "Weather": ":sun_behind_rain_cloud:"
    },
    "User": "mizzy"
  }'

このようにPublishすると、次のように通知されます。

Url

指定したURLを表示します。Parametersの中に入れても良いのですが、見にくくなりそうなので独立したブロックに表示するようにしています。

aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:slack-notification \
  --subject 'Message from CLI' \
  --message '{
    "SlackApp": "TalentioRuboty",
    "SlackChannel": "mizzy-test",
    "Status": "Success",
    "Parameters": {
       "Date": "2022/09/15",
       "Area": "Kanagawa",
       "Weather": ":sun_behind_rain_cloud:"
    },
    "User": "mizzy",
    "Url": "https://www.talentio.co.jp/"
  }'

このようにPublishすると、次のように通知されます。


おわりに

元々、このような仕組みを導入したのは、AWS Step Functionsから簡単に通知できるようにしたかったからです。

Step Functionsを実際の運用でどのように利用して、どのように通知しているか、という具体例については、また後日ご紹介できればと思います。

Zoom App Marketplace へのアプリ公開でハマったポイント

こんにちは。タレンティオの大木です。

採用管理システムのTalentioでは、オンライン面接をサポートするために、当社のサービス上でZoom Meetingを生成する機能を提供しています。

この機能を開発する過程で、OAuth AppをZoom App Marketplace に公開する必要があります。
今回は、OAuth Appの作成・公開までのプロセスと、その過程で詰まってしまったポイントを共有したいと思います。

Zoom App Marketplace とは

Zoom App Marketplaceは、Zoomが提供する開発者向けのプラットフォームです。サードパーティが開発したZoomの機能を拡張するアプリケーションやプラグインを探したり、自分が開発したアプリケーションを公開・管理したりできる場所です。

アプリケーションを公開する場合、用途に応じたタイプを選択する必要があります。
選択できるアプリの種類には、Zoom App・OAuth・JWTなどがあります。

それぞれのタイプについて簡単な説明を記載します。

  • Zoom App:Zoomミーティングやチャット上で利用可能な追加機能やサービスの提供を行うことができます
  • JWT:生成されたJWTを利用して、Zoom APIを使用することができます ※ 2023年9月1日からは使用不可となります
  • OAuth:ユーザーの許可を得てZoomアカウントにアクセスし、アプリケーションからAPIのリクエストを送ったり、webhookのイベントを購読できます

Talentioでは、各ユーザーのZoomアカウントにアクセスして、Zoom Meetingを作成することを目的としているため、OAuthタイプを選択しました。

1. アプリケーションの作成方法

アプリケーションの作成は、Zoom App Marketplaceの管理画面から行うことができます。

アプリケーションを作成すると、認証情報が発行され、その情報をもとに開発を進めることができます。

開発を進めているとさまざまな疑問が出てくると思いますが、その際はZoom Developer Forum で質問ができます。
質問を投稿するとおおよそ1〜2営業日以内に返信が届くため、作業中に詰まってしまった際は、すぐに質問を投稿することで作業をスムーズに進められるかもしれません。

2. Zoom Market Placeへの公開申請

作成したアプリケーションを外部のZoomアカウントにインストールさせるためには、Zoom App Marketplaceへ公開する必要があります。

公開までの道のりは Zoom App Marketplace App Review Process にまとめられているステップになります。

申請時に詰まってしまったポイント

申請時に必要な対応などは、Submission Checklist にまとめられています。
これを参考に対応したものの、申請時にいくつかつまづくポイントや、審査で不合格となった箇所がありました。

1. 審査用に本番環境、あるいはそれと同等の環境の準備

Zoomのレビュアーが操作確認を行うため、今回の開発箇所を外部から操作可能な状態である必要があります。
しかし、申請時にはZoom APIを利用した機能はまだリリース前でした。

そのため、デモ環境に審査者がアクセスできるような準備をしました。
また、本番環境では非公開にしたかったため、フィーチャートグルを設けるなど、いくつかの対応を行いました。

2. 関連するページのエンドポイントは同一ドメインでなければならない

申請時には、ヘルプページや利用規約などのURLの提供が求められます。
これらのURLはリダイレクトURLに設定したドメインと一致している必要があります。

Talentioでは、サービスのドメインtalentio.com ですが、ヘルプページや利用規約ドメインtalentio.co.jp です。

これらのページのドメインを変更することは避けたかったため、
talentio.com/ヘルプページ へのアクセスがヘルプページにリダイレクトされるように設定しました。

3. 利用イメージが伝わる画像の作成

アプリのアイコンや機能の使用イメージを示す画像が求められます。 明確な規定がなかったため、公開されているZoom Appを参考に画像を作成しましたが、「画像が不十分である」という理由で一度は審査に通りませんでした。

対策として、操作手順がより理解しやすいように、以下の4枚の画像に変更しました。

  1. Zoomとの連携設定
  2. Zoomとの認証
  3. Zoom Meetingの発行
  4. 作成されたZoom URLの確認

具体的な画像については、App Marketplace を参照ください。 これらの4つの画像に変更したところ、無事に審査が通りました。

4. アンインストール時の認証情報の管理

審査段階でアンインストール時の処理が適切に考慮されていないと指摘を受けました。
そのため、認証解除の通知をWebhookで受け取ると、データベースに保存していた認証情報を削除するような仕組みを設けることにしました。

該当するチェックリストの項目は、Submission Checklist に記載されています。

おわりに

今回は、OAuth Appの作成・公開までのプロセスと、その過程で詰まってしまったポイントについてご紹介しました。

この記事が、Zoom MarketplaceへのOAuth App公開に向けた参考となれば幸いです。

TalentioがoEmbedに対応した話

こんにちはTalentioの佐藤です。

採用管理システムのTalentioでは採用ページを作ることができます。

先日この機能をアップデートし、ブログ等にURLを貼り付けるだけで採用ページを埋め込めるようにしました。これにより採用ページの共有が簡単にできるようになります。

例えば、このブログ ( はてなブログ ) にTalnetioで作成したページのURLを貼るとこのようになります。

これはoEmbedという規格に沿って実装することで実現しています。

oEmbedとは

埋め込みコンテンツの規格です。メタデータのフォーマットや取得フローを定義しています。ドキュメントはこちらです。

コンテンツの提供者と利用者がこの規格に沿って実装をすることで、通常ページのURLから埋め込み用のコンテンツを取得できるようになります。今回のアップデートでTalentioはコンテンツの提供者として必要な実装をしました。具体的には次の実装をしています。

  1. 埋め込みコンテンツを表示するURLを追加
  2. 埋め込みコンテンツのメタデータを返すAPIを実装
  3. メタデータ取得APIと採用ページの紐付け

それぞれについて詳しい内容を見ていきます。

1.埋め込みコンテンツを表示するURLを追加

埋め込み時に表示する画面のURLです。今回のケースではこのURLを追加しています。

2.埋め込みコンテンツのメタデータを返すAPIを実装

埋め込みコンテンツの表示に必要なデータを返すAPIを作ります。Talentioでは以下のようなデータを返すAPIを用意しています。

{
  "type": "rich",
  "version": "1.0",
  "provider_name": "Talentio",
  "provider_url": "https://www.talentio.co.jp",
  "html": "<iframe src=\"https://recruit.talentio.co.jp/r/1/c/talentio/embed/pages/21466\" width="100%" height=300 frameborder=0 title=\"株式会社タレンティオ | カスタマーサクセス\"></iframe>",
  "url": "https://recruit.talentio.co.jp/r/1/c/talentio/embed/pages/21466",
  "width": "100%",
  "height": 300
}

htmlには表示したいコンテンツを指定します。Talentioでは1で作成した画面を表示するiframeを設定しています。widthやheightは実際に描画する際のサイズを指定します。

APIのレスポンスに含めるデータについて、詳細はこちらを参照してください。

3.メタデータ取得APIと採用ページの紐付け

オリジナルのページにAPIのURLを指定したタグを設定し、2つを関連づけます。

実際にTalentioで作成した採用ページには以下のようなタグが設定されています。hrefには2で作成したAPIが指定されており、これにより採用ページとAPIが関連づいています。

<link href="https://open.talentio.com/oembed?url=https://recruit.talentio.co.jp/r/1/c/talentio/pages/21466&format=json" rel="alternate" title="oEmbed Profile of カスタマーサクセス" type="application/json+oembed">

紐付け方法について、詳しい仕様はこちらを参照してください。

埋め込まれるまでの流れ

以上でコンテンツ提供者の実装は完了です。後はURLをoEmbedに対応したサービスに貼り付ければOKです。利用者側のサービスが規格に沿ってデータを取得し、コンテンツを埋め込んでくれます。

具体的には以下の流れでデータを取得します。

  1. 貼り付けたURLにアクセス。仕様に沿ってタグから対応するAPIを探す
  2. 対応するAPIからメタデータを取得
  3. メタデータに沿って埋め込みコンテンツを描画

補足: oEmbedに対応しているサービス

最後にコンテンツの利用者側で対応しているサービスを調べてみました。

見つけられた範囲では、以下のサービスがoEmbed表示に対応しているようです。

毎月1回を1年間続けてきたタレンティオのTechDay運用

こんにちは、管野です。

タレンティオでは月に1回TechDayを開催しています。この日は普段の業務をお休みし、各自が好きなことを学びます。
そして1日の終わりにクロージング(発表)を行い、それぞれの成果を共有します。

2022年3月に始めて1年が過ぎました。そこで今回はTechDayの運用について振り返ってみます。

【目次】

  1. どうしてTechDayをするのか
  2. どうやってTechDayを運用しているのか
  3. TechDayを1年間やってみてどうだったか

1. どうしてTechDayをするのか

アウトプットに関する学習促進の機会を設けるため

発案当初の意図は、会社として学習促進の機会を整えることでした。Techメンバーはそれぞれ色々なインプットを行っていますが、その学びを全て今の業務に活用できるとは限りません。
活用できるかどうかは、本人が着手しているタスクや現在のアーキテクチャに影響されます。

そこでTechDayとして「各自が自由に作業できる日」を設けることになりました。
お試しで始まりましたが、今では後述する他の理由もあって継続しています。

インプットに関する学習促進の機会を設けるため

TechDayはインプットの機会としても有効です。
例えば新機能に関する技術調査や、既存のアーキテクチャで理解が弱い部分を調べることが出来ます。

後述するようにTechDayの終わりにはクロージングとして、ドキュメントにまとめて発表します。
人に伝えることを意識しながら学ぶため、情報を整理したり取捨選択することになり、インプットの質が上がります。

技術的なコミュニケーションの機会を設けるため

タレンティオでは現在フルリモートワークです。
オフィスに出勤していた頃とは違い、気軽に話せるタイミングは減っています。

みんなで集まって話し合うオンラインMTGはいくつかありますが、技術的な関心をテーマに話す場はほぼありません。
そんな中、TechDayのクロージングは各メンバーの関心事や得意分野を知れる機会になっています。

2. どうやってTechDayを運用しているのか

開催頻度は月1にしている

毎月の最終水曜日をTechDayとしています。メイン業務の開発速度を落とさないことが大前提です。
頻度を高くすると普段の業務に支障が出ますし、逆に低くするとTechDayに求める効果が薄れます。今のところ月1が丁度良いと感じています。

曜日はいつでも構わないのですが、今のチームメンバーにとって一番都合が良い水曜日にしています。

何をテーマにするかは各自の自由

GitHub DiscussionsにTechDayのトピックを一つ作り、月ごとにスレッドを立てています。 最初はGitHub Issuesを使っていましたが、業務でもDiscussionsを使うようになってからはそちらに移行しました。

メンバーは前日に「今回何をやるか」をコメントします。記載したテーマを必ずしも守る必要はなく、ここでのコメントはただの周知です。私はテーマがかぶらないようにしたいので、メンバーのコメントを見てテーマを変えたりしたこともあります。

TechDayで何をするか予定を書く

またテーマに厳密なルールはありませんが、なるべく業務に活かせるものを選ぶ傾向にあります。1日という時間制限のため調査や検証系が多くなりますが、「作ってみる」というテーマに取り組むこともあります。

当日は学習に専念して最後にチームでクロージングを行う

当日は始業から18:00までTechDayに専念しています。
もちろんサポート対応や障害など急ぎの時はそちらを優先しますが、基本的に普段の業務は進めません。そのため例えばコードレビューを依頼されることもありません。この日のSlackはとても静かです。

18:00になるとZoom上でクロージング(発表会)が始まります。
この時までにそれぞれ学んだことをKibelaに投稿します。このKibelaをもとに1人10分ほどの発表を行います。

3. TechDayを1年間やってみてどうだったか

TechDayの成果物

ここ半年のTechDayで取り組んだタイトルの一覧を、この記事の最後に載せておきます。そちらを見ると調査系が多いものの、作ってみたりチーム改善に貢献しているのもあるのが分かります。

例えば以下のような成果が生まれました。

CIや単体テストの改善

  • CI上の単体テスト(RSpec)を高速化するため並列実行を導入、12分 -> 3分に短縮した
  • (上記とは別の開催日で)遅い単体テストのコードを見直して、1~2分短縮した

開発環境の改善

  • Pull Requestに出してるブランチをステージング環境に構築できるが、その手順を簡略化した
  • タレンティオの状況に併せたER図ビューワーを作った

チームメンバーの感想

良い点

  • 普段まとまった勉強時間を取るのが難しいこともあるので、業務時間内に学習機会があるのは助かる
  • CIの高速化とか、普段の開発体験につながる成果が出てるのは良いこと
  • クロージングでの共有を前提に調べるため、いつもより情報を整理しようとして理解度が増す
  • クロージングで他の人の話を聞くだけでも、普段行っているWebでの軽い情報収集より理解が深まる
  • 取り組むテーマが限定されていないこと。幅広いテーマの話を聞けるのは興味深いし、楽しい

改善点

  • 現状でも不満ないが、強いてあげるなら1日以上あると嬉しい
    • 1日以内に終わるネタに限るので、何か作ってみるとか大きなことはしにくい
    • 毎回は難しくても半年とか年に1回だけでも、1日以上の開催があると嬉しい
  • クロージングで発表できるテーマに絞ってしまう
    • まとめ方が見えないテーマに関して「とりあえず触ってみる」ということが出来ない

おわりに

TechDayはとても良い取り組みです。個人のスキルアップだけでなく、チームの生産性や、お客様への価値提供に繋がる可能性があります。運用方法はチームの状況によって調整しつつ、引き続き継続していきます。

参考: 直近半年のTechDay記事タイトル一覧

- Amazon Comprehend の調査
- Zigについて調べてみた
- Rails7 主要な新機能の検証
- Bundle Sizeの調べ方
- タレンティオ用ER図Viewerを作ってみた
- 開発環境へのビルド・デプロイプロセスの改善
- 書籍『Webブラウザセキュリティ』を読んだ
- Rails7 のフロントエンド
- WASIについて調べてみた
- Notion APIを調べた
- 開閉アニメーションについて
- Techブログを書いてみた
- Sentry Session Replay機能の検証
- ハッシュ関数と暗号化/復号について
- SendGrid APIの活用
- Source Map
- ワンタイムパスワードを調べた
- 遅い単体テストを改善して少しだけ早くした
- 機械学習にトライ
- zustand を調べてみた
- Sentry Session Replayに関するブログを書きました
- intercomのお問い合わせをスプシに集計する
- Zodの調査
- 「React でのドラッグ&ドロップライブラリを比較してみた」というブログ記事を書いた(ている)
- ChatGPT and Whisper APIsの活用
- 仕事ではじめる機械学習を読む
- Remix を調べた

Reactのドラッグ&ドロップソートライブラリを比較

はじめに

タレンティオの宇野です。 先日、Talentio Hire で React のドラッグ&ドロップライブラリを実装する機会があったのですが、React でのドラッグ&ドロップソートライブラリは数多くあり、どれが要件にマッチするかがわかりづらかったので、比較検討した際の情報をご紹介できればと思います。

対象読者

この記事は以下の読者におすすめです。

  • React でドラッグ&ドロップソートを実装したい方
  • ライブラリを比較する際の基準の例を知りたい方

今回実装する機能

以下のようなドラッグ&ドロップで並び替えを行うオーソドックスなソート機能です。

ソート中

TLDR

今回の実装では、 dnd-kit を選択しました。以下は、その選定理由になります。
サンプルコードを見たい方はこちらをクリックしてください。

注意

この記事は 2023/03 時点の情報です。

候補ライブラリ

ざっと調べた限り、下記のライブラリがドラッグ&ドロップでのソートに対応していそうでした。
もちろん下記以外にもソート用のライブラリは存在するかと思いますが、今回はこれらを比較検討してみたいと思います。

人気度

ライブラリの利用数(ダウンロード数)や GitHub での人気度を見てみます。

ダウンロード数では react-draggable が突出して人気です。
ただし、最近はダウントレンド傾向なので、他のライブラリへの置き換えが進んでいそうです。
次点では、 react-dnd, react-beautiful-dnd が多く利用されています。

npm trends

スター数

Github でのスター数では、 react-beautiful-dnd が人気です。
Attlasian が開発元で Jila 等にも使われているようなので、そちらの信頼度も影響していそうです。

Stars
react-beautiful-dnd 29,353
react-dnd 18,885
react-sortable-hoc 10,378
react-draggable 8,153
@dnd-kit/core 7,005

メンテナンスされているか

  • dnd-kit が最もアクティブにメンテナンスされています。
    • react-sortable-hoc の後継ライブラリでもあり、初回リリースが 2021/01 と後発のライブラリではあるものの活発に開発されています。
  • react-sortable-hoc はすでにアップデートはされていませんでした。
  • react-beautiful-dnd も部分的なアップデートはされていますが、現在は機能開発、改善等のアップデートはされていません。
Version Updated Created
@dnd-kit/core 6.0.8 2023-02-19T14:48:34.147Z 2021-01-02T02:07:10.605Z
react-beautiful-dnd 13.1.1 2022-08-30T04:15:39.512Z 2017-08-10T07:15:47.946Z
react-draggable 4.4.5 2022-04-26T18:04:09.131Z 2014-07-25T21:58:31.684Z
react-dnd 16.0.1 2022-04-19T18:05:21.375Z 2014-10-19T13:55:23.335Z
react-sortable-hoc 2.0.0 2021-03-19T02:56:51.618Z 2016-06-08T03:30:55.021Z

ライブラリの状況を踏まえた評価

上記を踏まえ、この時点で、下記のライブラリは選定対象から外しました。

  • react-draggable
    • 利用者数が多いものの、現在は利用者数が減ってきている。
  • react-beautiful-dnd
    • Atlassian (Jira などの開発)で開発されているので品質には期待できそうだが、 React 18 に未対応、機能面でのアップデートがされていないなど、今後、長く利用し続けるのは難しそう。
  • react-sortable-hoc
    • 機能の要件的には問題ないが、こちらも react-beautiful-dnd と同じくアップデートされていない。

実装してみる

ライブラリの状況は把握できたので、実際にサンプルを実装して検証してみました。
ここでは、前段の選定対象で残った、react-dnddnd-kit の動作を確認します。

react-dnd

codesandbox でのサンプル
(ソートがうまく動作しない場合は、 Open preview in new window からお試しください)

dnd-kit

  • ソート用の preset が用意されているので、比較的簡単に実装が可能
  • ソート時のアニメーションも標準で付与される
  • ドラッグ時にオーバーレイする要素の位置を自由にカスタマイズ可能
  • スマートフォンにも対応している

codesandbox でのサンプル

まとめ

  • アニメーションや細かい要素のカスタマイズ等の要件にマッチ。
  • react-sortable-hoc からの改善点も多く盛り込まれている。
  • 後発のライブラリで現状利用者数は多くないが、頻繁にアップデートされており、これからも改善が期待できそう。

ということで、今回の実装では dnd-kit を採用しました。

npm trends や Google 検索では、過去に人気(ダウンロード)があったものはわかるものの、
新規採用が増えてきていたり、今後も利用しつづけられそうかどうかは判断できないので、これらの情報を踏まえつつ、実際に実装して感触を確かめるのが重要と感じました。

今回は、縦のリスト要素を並び替える目的で検証しました。
カンバンのような横も含めたドラッグ&ドロップでは、また要件が変わり、結果も変わってくるかと思うので、こちらも機会があれば試してみたいと思います。

この記事が、 React でドラッグ&ドロップソート機能を実装する際の参考になれば幸いです。