diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 89f66a9..30b2c4a 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -137,3 +137,15 @@ color: #666; margin: 0 0 0.1rem; } + +.auto-scroll { + /* flash a little bit yellow & leave a little bit of a border */ + animation: flash 1s ease-in-out; + border: 2px solid var(--uchu-yellow); + border-radius: 5px; +} + +@keyframes flash { + 0% { background-color: var(--uchu-yellow); } + 100% { background-color: transparent; } +} diff --git a/app/controllers/api/hackatime/v1/hackatime_controller.rb b/app/controllers/api/hackatime/v1/hackatime_controller.rb index 9fe8880..5b43ffe 100644 --- a/app/controllers/api/hackatime/v1/hackatime_controller.rb +++ b/app/controllers/api/hackatime/v1/hackatime_controller.rb @@ -54,7 +54,14 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController def handle_heartbeat(heartbeat_array) results = [] heartbeat_array.each do |heartbeat| - attrs = heartbeat.merge({ user_id: @user.id, source_type: :direct_entry }) + source_type = :direct_entry + + # special case: if the entity is "test.txt", this is a test heartbeat + if heartbeat[:entity] == "test.txt" + source_type = :test_entry + end + + attrs = heartbeat.merge({ user_id: @user.id, source_type: source_type }) new_heartbeat = Heartbeat.find_or_create_by(attrs) results << [ new_heartbeat.attributes, 201 ] rescue => e diff --git a/app/controllers/api/v1/my/heartbeats_controller.rb b/app/controllers/api/v1/my/heartbeats_controller.rb index 836f56b..481275d 100644 --- a/app/controllers/api/v1/my/heartbeats_controller.rb +++ b/app/controllers/api/v1/my/heartbeats_controller.rb @@ -6,11 +6,10 @@ class Api::V1::My::HeartbeatsController < ApplicationController .order(time: :desc) .first - if heartbeat - render json: heartbeat - else - render json: { error: "No heartbeats found" }, status: :not_found - end + render json: { + has_heartbeat: heartbeat.present?, + heartbeat: heartbeat + } end def index diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb index 899bda0..134c341 100644 --- a/app/controllers/static_pages_controller.rb +++ b/app/controllers/static_pages_controller.rb @@ -7,6 +7,8 @@ class StaticPagesController < ApplicationController redirect_to FlavorText.random_time_video.sample, allow_other_host: allowed_hosts end + @show_wakatime_setup_notice = current_user.heartbeats.empty? + @project_names = current_user.project_names @projects = current_user.project_labels @current_project = current_user.active_project diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index aaab157..f4a94ce 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -34,6 +34,19 @@ class UsersController < ApplicationController notice: "Heartbeats & api keys migration started" end + def wakatime_setup + api_key = current_user&.api_keys&.last + api_key ||= current_user.api_keys.create!(name: "Wakatime API Key") + @current_user_api_key = api_key&.token + end + + def wakatime_setup_step_2 + end + + def wakatime_setup_step_3 + end + + private def require_admin diff --git a/app/jobs/wakatime_clear_test_heartbeats_job.rb b/app/jobs/wakatime_clear_test_heartbeats_job.rb new file mode 100644 index 0000000..4f908d3 --- /dev/null +++ b/app/jobs/wakatime_clear_test_heartbeats_job.rb @@ -0,0 +1,18 @@ +class WakatimeClearTestHeartbeatsJob < ApplicationJob + queue_as :default + + include GoodJob::ActiveJobExtensions::Concurrency + + # Limits concurrency to 1 job per date + good_job_control_concurrency_with( + key: -> { arguments.first || Date.current.to_s }, + total: 1, + drop: true + ) + + def perform + Heartbeat.where(source_type: "test_entry") + .where("created_at < ?", 7.days.ago) + .delete_all + end +end diff --git a/app/models/api_key.rb b/app/models/api_key.rb index ddbd150..aa11ab6 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -3,4 +3,17 @@ class ApiKey < ApplicationRecord validates :token, presence: true, uniqueness: true validates :name, presence: true, uniqueness: { scope: :user_id } + + before_validation :generate_token!, on: :create + + private + + def generate_token! + # we need to keep ourselves compatible with WakaTime: https://github.com/wakatime/vscode-wakatime/blob/241b60c8491c14e3c093b1ef2a0276c38586a172/src/utils.ts#L24 + # they use a UUID v4 + self.token ||= SecureRandom.uuid_v4 + + # Mark it as something not imported from WakaTime + self.name ||= "Hackatime key" + end end diff --git a/app/models/heartbeat.rb b/app/models/heartbeat.rb index c0c2df2..0e82617 100644 --- a/app/models/heartbeat.rb +++ b/app/models/heartbeat.rb @@ -7,7 +7,8 @@ class Heartbeat < ApplicationRecord enum :source_type, { direct_entry: 0, - wakapi_import: 1 + wakapi_import: 1, + test_entry: 2 } # This is to prevent Rails from trying to use STI even though we have a "type" column diff --git a/app/views/static_pages/index.html.erb b/app/views/static_pages/index.html.erb index 514eb18..164eae5 100644 --- a/app/views/static_pages/index.html.erb +++ b/app/views/static_pages/index.html.erb @@ -11,6 +11,12 @@ <% end %> <% if current_user %> + <% if @show_wakatime_setup_notice %> +
+ <%= link_to "Setup time tracking", my_wakatime_setup_path %> +
+ <% end %> + <% if @show_logged_time_sentence %> You've logged <%= short_time_detailed current_user.heartbeats.today.duration_seconds %> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 4acdc89..236c68f 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -4,6 +4,11 @@Step 1 of 4
+ +export HACKATIME_API_KEY="<%= @current_user_api_key %>" && curl -sSL <%= root_url %>hackatime/setup.sh | bash
+
+$env:HACKATIME_API_KEY="<%= @current_user_api_key %>"; iwr <%= root_url %>hackatime/setup.ps1 -UseBasicParsing | iex
+This will create your WakaTime config file and send a test heartbeat to verify the setup.
+ +Waiting for your first heartbeat...
+ ++ <%= link_to "Next Step", my_wakatime_setup_step_2_path, id: "next-step", style: "display: none;" %> +
+ + + + diff --git a/app/views/users/wakatime_setup_step_2.html.erb b/app/views/users/wakatime_setup_step_2.html.erb new file mode 100644 index 0000000..85a079f --- /dev/null +++ b/app/views/users/wakatime_setup_step_2.html.erb @@ -0,0 +1,23 @@ +Step 2 of 4
+ +Step 3 of 4
+ ++ Hackatime is WakaTime-compatible, so you can use it with any editor that WakaTime supports. For other editors, please refer to the WakaTime docs. + + ⚠️ Note: skip any steps that update your ~/.wakatime.cfg file or change the api_url inside it. +
++ Hackatime is WakaTime-compatible, so you can use it with any editor that WakaTime supports. For Kicad, + we recommend this Kicad plugin built by hack clubbers. + . +
+ Hackatime is WakaTime-compatible, so you can use it with any editor that WakaTime supports. For VS Code, install the WakaTime extension. +
+