2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ClickUp】ClickUp APIを使って稼働時間とタスク一覧を取得してみた

Last updated at Posted at 2025-03-24

はじめに

自分用の使いやすいダッシュボードの制作を進めています。

今回は担当プロジェクトの稼働ペース予測処理を簡易的に実装していきます。

スプレッドシートでも作成は可能です。
日付は今日と前日の2パターン対応できる形にしています。
Cursor_と_勤怠時間計算_-_Google_スプレッドシート.png

項目 数式
今日で計測 or 昨日で計測 =IF(F6="今日で計測", TODAY(), TODAY()-1)
営業日数 =NETWORKDAYS(G4,G6,E14:E32)
残営業日数 =NETWORKDAYS(G4,H4,E14:E32)-G7
現稼働時間 =H9+I9
増加値 =(G9/G7)*G8
予測合計値 =G9+G10

[土日祝を除いた平日]を計算する数式の存在を初めて知りました。

= NETWORKDAYS(開始日,終了日,祭日)

上記の実装を、ClickUpと連携して自動出力できるようにしたいので、Railsで試していきます。

完成図

Science_App.png

Controllerの作成

まずは、手動で現在の累計稼働時間を入力する形で実装します。

現在の累計稼働時間から増加値予測と合計値予測を算出します。

祝日判定にはgemのholidaysを使用します。

$ bundle add holidays
require "holidays"

class AnalyticsController < ApplicationController
  def index
    @users = User.all

    @today = Date.current

    calculate_until = params[:calculate_until] || "today"
    target_date = calculate_until == "yesterday" ? @today.yesterday : @today
    @today_formatted = target_date.strftime("%-m月%d日 (%a)")

    @hours_u1 = params[:total_hours_user1].to_f || 0.0
    @hours_u2 = params[:total_hours_user2].to_f || 0.0

    start_date = target_date.beginning_of_month
    end_date = target_date.end_of_month

    @business_days = (start_date..target_date).count { |date| business_day?(date) }
    @remaining_days = (target_date.next_day..end_date).count { |date| business_day?(date) }

    @per_day_u1 = @business_days > 0 ? (@hours_u1 / @business_days) : 0
    @predicted_u1 = @per_day_u1 * @remaining_days
    @final_u1 = @hours_u1 + @predicted_u1

    @per_day_u2 = @business_days > 0 ? (@hours_u2 / @business_days) : 0
    @predicted_u2 = @per_day_u2 * @remaining_days
    @final_u2 = @hours_u2 + @predicted_u2

    @total_final = @final_u1 + @final_u2
  end

  private
  def business_day?(date)
    !date.saturday? && !date.sunday? && Holidays.on(date, :jp).empty?
  end
end

Viewの作成

手動で入力した値を使って、稼働時間予測値を表示します。

  • 対象ユーザーは2名
  • 残り営業日数から増加予測値を算出
  • 現在の稼働時間と合算した値をペース予測として出力する
<div class="mt-10 max-w-lg mx-auto">
  <h2 class="text-xl font-bold text-center mb-4">稼働時間計算</h2>
  <div class="bg-white p-6 rounded-lg shadow-lg">
    <form method="get" action="<%= analytics_path %>">
      <div class="mb-4">
        <label for="total_hours_user1" class="block text-gray-700 font-bold mb-2">User1の累計稼働時間(h)</label>
        <input type="number" step="0.01" name="total_hours_user1" id="total_hours_user1" value="<%= @hours_u1 %>" class="w-full p-2 border rounded">
      </div>
      <div class="mb-4">
        <label for="total_hours_user2" class="block text-gray-700 font-bold mb-2">User2の累計稼働時間(h)</label>
        <input type="number" step="0.01" name="total_hours_user2" id="total_hours_user2" value="<%= @hours_u2 %>" class="w-full p-2 border rounded">
      </div>
      <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">計算</button>
      <button type="submit" name="calculate_until" value="yesterday" class="bg-gray-500 text-white px-4 py-2 rounded">昨日の日付で計算</button>
    </form>
    <table class="table-auto w-full bg-white rounded-lg shadow-lg mt-4">
      <tbody>
        <tr class="border-t">
          <td class="px-4 py-2">日付</td>
          <td class="px-4 py-2"><%= @today_formatted %></td>
        </tr>
        <tr class="border-t">
          <td class="px-4 py-2">営業日数</td>
          <td class="px-4 py-2"><%= @business_days %></td>
        </tr>
        <tr class="border-t">
          <td class="px-4 py-2">残り営業日数</td>
          <td class="px-4 py-2"><%= @remaining_days %></td>
        </tr>
      </tbody>
    </table>
    <p class="text-lg mt-4">累計稼働時間: <span class="font-bold"><%= (@hours_u1 + @hours_u2).round(1) %> h</span><span class="text-sm"> (現在)</p>
    <table class="table-auto w-full bg-white rounded-lg shadow-lg mt-4">
      <thead>
        <tr class="bg-gray-300">
          <th class="px-4 py-2">項目</th>
          <th class="px-4 py-2">User1</th>
          <th class="px-4 py-2">User2</th>
          <th class="px-4 py-2">合計</th>
        </tr>
      </thead>
      <tbody class="text-center">
        <tr class="border-t">
          <td class="px-4 py-2 text-left">
            <div class="flex items-center">
              <span>稼働時間</span>
              <span class="ml-2 text-sm text-gray-500">(h/日)</span>
            </div>
          </td>
          <td class="px-4 py-2"><%= @per_day_u1.round(1) %></td>
          <td class="px-4 py-2"><%= @per_day_u2.round(1) %></td>
          <td class="px-4 py-2 font-bold"><%= (@per_day_u1 + @per_day_u2).round(1) %></td>
        </tr>
        <tr class="border-t">
          <td class="px-4 py-2 text-left">
            <div class="flex items-center">
              <span>増加予測</span>
              <span class="ml-2 text-sm text-gray-500">(h/月)</span>
            </div>
          </td>
          <td class="px-4 py-2"><%= @predicted_u1.round(1) %></td>
          <td class="px-4 py-2"><%= @predicted_u2.round(1) %></td>
          <td class="px-4 py-2 font-bold"><%= (@predicted_u1 + @predicted_u2).round(1) %></td>
        </tr>
        <tr class="border-t-3 border-double">
          <td class="px-4 py-2 text-left">
            <div class="flex items-center">
              <span>累計予測</span>
              <span class="ml-2 text-sm text-gray-500">(h/月)</span>
            </div>
          </td>
          <td class="px-4 py-2"><%= @final_u1.round(1) %></td>
          <td class="px-4 py-2"><%= @final_u2.round(1) %></td>
          <td class="px-4 py-2 font-bold"><%= (@final_u1 + @final_u2).round(1) %></td>
        </tr>
      </tbody>
    </table>
    <p class="text-lg mt-4">累計稼働時間: <span class="font-bold"><%= (@final_u1 + @final_u2).round(1) %> h</span><span class="text-sm"> (予測)</p>
  </div>
</div>

ClickUp API Tokenの取得

現在の累計稼働時間の入力欄に、ClickUpから取得した値を初期値として表示させます。
自分以外のユーザー情報は管理者権限が必要だったため、自分の稼働時間のみ取得します。

ClickUpAPIのRate Limitsはワークスペースのプランによって異なります。

  • 永久無料、無制限、ビジネス: トークンあたり 1 分あたり 100 リクエスト
  • Business Plus : トークンあたり 1 分あたり 1,000 件のリクエスト
  • エンタープライズ: トークンあたり 1 分あたり 10,000 件のリクエスト

ClickUpの右上のプロフィールメニューから[Setting]を選択します。

[Apps]から[Generate]を選択すると、API Tokenが生成できます。
Cursor_と_App_Settings___HAB_Co_.png

稼働時間の取得

ClickUp API Referenceの[Time Tracking]セクションの[Get time entries within a date range]を参考に作成します。
Get_time_entries_within_a_date_range.png

モデルを作成してClickupAPIで稼働時間を取得します。

データベースの構造変更はないので、migrationファイルは作成せずに、modelを作成します。

$ rails g model clickup --skip-migration
require "uri"
require "net/http"
require "json"

class Clickup < ApplicationRecord
  def self.base_url
    ENV["CLICKUP_API_BASE_URL"]
  end

  def self.list_id
    ENV["CLICKUP_LIST_ID"]
  end

  def self.api_key
    ENV["CLICKUP_API_KEY"]
  end

  def self.perform_request(url)
    http = Net::HTTP.new(url.host, url.port)
    http.use_ssl = true

    request = Net::HTTP::Get.new(url)
    request["accept"] = "application/json"
    request["Authorization"] = api_key

    begin
      response = http.request(request)
      if response.code == "200"
        JSON.parse(response.body)
      else
        Rails.logger.error("ClickUp API Error: #{response.code} - #{response.body}")
        nil
      end
    rescue StandardError => e
      Rails.logger.error("Failed to perform request to ClickUp: #{e.message}")
      nil
    end
  end

  def self.fetch_tracked_time
    team_id = ENV["CLICKUP_TEAM_ID"]
    assignee_id = ENV["CLICKUP_ASSIGNEE_ID"]
    start_date = (Date.today.beginning_of_month.to_time.to_i * 1000).to_s
    end_date = (Date.today.end_of_month.to_time.to_i * 1000).to_s

    url = URI("#{base_url}/team/#{team_id}/time_entries?assignee=#{assignee_id}&list_id=#{list_id}&start_date=#{start_date}&end_date=#{end_date}")
    response = perform_request(url)

    if response
      time_entries = response["data"]
      filtered_entries = time_entries.reject do |entry|
        entry.dig("task", "status", "status") == "未着手"
      end

      total_duration_ms = filtered_entries.sum { |entry| entry["duration"].to_i }
      (total_duration_ms / (1000 * 60 * 60.0)).round(2)
    else
      0.0
    end
  end
end

API_KEY, API_BASE_URL, LIST_ID, TEAM_ID, ASSIGNEE_IDは.envファイルに記載します。

CLICKUP_API_KEY='ab_1231234_ABCXXXXXXXXXXXXXXX'
CLICKUP_API_BASE_URL='https://api.clickup.com/api/v2'
CLICKUP_LIST_ID='12345678'
CLICKUP_TEAM_ID='11112222'
CLICKUP_ASSIGNEE_ID='1212121'

稼働時間を取得する期間の指定ができます。

url = URI("#{base_url}/team/#{team_id}/time_entries?assignee=#{assignee_id}&list_id=#{list_id}&start_date=#{start_date}&end_date=#{end_date}")

start_dateとend_dateはUNIX時間で表記する必要があるので、変換します。
Cursor_と_Get_time_entries_within_a_date_range.png

begining_of_monthend_of_monthで現在の月の月初と月末の日付を取得します。
(今日が2025年3月25の場合、3月1日と3月31日をそれぞれ取得する)

start_date = (Date.today.beginning_of_month.to_time.to_i * 1000).to_s
end_date = (Date.today.end_of_month.to_time.to_i * 1000).to_s

statusが"未着手"のタスクは、稼働時間に含めないので除外します。

time_entries = response["data"]

filtered_entries = time_entries.reject do |entry|
  entry.dig("task", "status", "status") == "未着手"
end

Controllerの修正

コントローラーでインスタンス変数を定義します。

@total_duration_hours = Clickup.fetch_tracked_time

初期値にClickUpから取得した値を入れるために@hours_u1を修正します。
手動で値を修正した場合は、その値が反映されます。

@hours_u1 = params[:total_hours_user1].present? ? params[:total_hours_user1].to_f : Clickup.fetch_tracked_time

Viewの修正

User1の累計稼働時間入力欄に、ClickUpから取得した値を初期値として表示させるために修正します。

<div class="mb-4">
  <label for="total_hours_user1" class="block text-gray-700 font-bold mb-2">User1の累計稼働時間(h)</label>
  <input type="number" step="0.01" name="total_hours_user1" id="total_hours_user1" value="<%= params[:total_hours_user1] || @hours_u1 %>" class="w-full p-2 border rounded">
</div>

タスク一覧の取得

せっかくなので、指定のリストからタスク一覧の取得も試しました。

ClickUp API Referenceの[Tasks]セクションの[Get Tasks]を参考に作成します。
Cursor_と_Get_Tasks.png

clickup.rbにタスク一覧を取得するメソッドを追加します。

def self.fetch_tasks
  url = URI("#{base_url}/list/#{list_id}/task")
  response = perform_request(url)
  response ? response["tasks"] : []
end

Controllerの修正

コントローラーでインスタンス変数を定義します。

class AnalyticsController < ApplicationController
  def index
    @tasks = Clickup.fetch_tasks
  end
end

Viewの修正

取得したタスク情報から、Title, Assignees, Status, Created_atを表示します。

<div class="mt-10 p-8">
  <h2 class="text-xl font-bold text-center mb-4">タスク一覧</h2>
  <table class="table-auto w-full bg-white rounded-lg shadow-lg">
    <thead>
      <tr class="bg-gray-300">
        <th class="px-4 py-2">Name</th>
        <th class="px-4 py-2">Assignees</th>
        <th class="px-4 py-2">Status</th>
        <th class="px-4 py-2">Created_at</th>
      </tr>
    </thead>
    <tbody>
      <% if @tasks.present? %>
      <% @tasks.each do |task| %>
      <tr class="border-t">
        <td class="px-4 py-2"><%= task["name"] %></td>
        <td class="px-4 py-2">
          <% if task["assignees"].present? %>
          <% task["assignees"].each do |assignee| %>
          <div class="flex items-center space-x-2 mb-1">
            <img src="<%= assignee["profilePicture"] %>" alt="<%= assignee["username"] %>" class="w-8 h-8 rounded-full">
            <span><%= assignee["username"] %></span>
          </div>
          <% end %>
          <% else %>
          未設定
          <% end %>
        </td>
        <td class="px-4 py-2"><%= task["status"]["status"] %></td>
        <td class="px-4 py-2">
          <% if task["date_created"].present? %>
          <%= Time.at(task["date_created"].to_i / 1000).strftime('%Y-%m-%d') %>
          <% else %>
          --
          <% end %>
        </td>
      </tr>
      <% end %>
      <% else %>
      <tr>
        <td colspan="3" class="px-4 py-2 text-center">タスクが見つかりません。</td>
      </tr>
      <% end %>
    </tbody>
  </table>
</div>

タスク一覧が表示できました。
AssigneesはClickUpで設定しているプロフィール画像の取得もできました。
Science_App.png

まとめ

今回はClickUp APIを利用して、タスクや稼働時間を取得することができました。

API連携は初挑戦だったので、実装まで辿り着けて良かったです。
ClickUp APIの公式Referenceが、充実していたので助かりました。

今後はSalesforceと外部アプリケーションのAPI連携も試していきたいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?