Initial code for replicating hackclub/sailors-log

This commit is contained in:
Max Wofford 2025-02-22 00:15:16 -05:00
parent 7aa2068b1e
commit cfd38a87eb
21 changed files with 287 additions and 1 deletions

View file

@ -0,0 +1,14 @@
class Avo::Resources::SailorsLog < Avo::BaseResource
# self.includes = []
# self.attachments = []
# self.search = {
# query: -> { query.ransack(id_eq: params[:q], m: "or").result(distinct: false) }
# }
def fields
field :id, as: :id
field :slack_uid, as: :text
end
end

View file

@ -0,0 +1,16 @@
class Avo::Resources::SailorsLogNotificationPreference < Avo::BaseResource
# self.includes = []
# self.attachments = []
# self.search = {
# query: -> { query.ransack(id_eq: params[:q], m: "or").result(distinct: false) }
# }
def fields
field :id, as: :id
field :slack_uid, as: :text
field :enabled, as: :boolean
field :slack_channel_id, as: :text
end
end

View file

@ -0,0 +1,16 @@
class Avo::Resources::SailorsLogSlackNotification < Avo::BaseResource
# self.includes = []
# self.attachments = []
# self.search = {
# query: -> { query.ransack(id_eq: params[:q], m: "or").result(distinct: false) }
# }
def fields
field :id, as: :id
field :slack_uid, as: :text
field :slack_channel_id, as: :text
field :project_name, as: :text
end
end

View file

@ -0,0 +1,4 @@
# This controller has been generated to enable Rails' resource routes.
# More information on https://docs.avohq.io/3.0/controllers.html
class Avo::SailorsLogNotificationPreferencesController < Avo::ResourcesController
end

View file

@ -0,0 +1,4 @@
# This controller has been generated to enable Rails' resource routes.
# More information on https://docs.avohq.io/3.0/controllers.html
class Avo::SailorsLogSlackNotificationsController < Avo::ResourcesController
end

View file

@ -0,0 +1,4 @@
# This controller has been generated to enable Rails' resource routes.
# More information on https://docs.avohq.io/3.0/controllers.html
class Avo::SailorsLogsController < Avo::ResourcesController
end

View file

@ -0,0 +1,37 @@
class SailorsLogPollForChangesJob < ApplicationJob
queue_as :default
def perform
# Get all users with enabled preferences
slack_ids = SailorsLogNotificationPreference.where(enabled: true).distinct.pluck(:slack_uid)
# for each user, check if their logs have changed
logs = SailorsLog.where(slack_uid: slack_ids).includes(:notification_preferences)
logs.each do |log|
# get all projects for the user
projects = Heartbeat.today.where(user_id: log.slack_uid).distinct.pluck(:project)
new_notification = []
projects.each do |project|
new_project_time = Heartbeat.where(user_id: log.slack_uid, project: project).duration_seconds
if new_project_time > (log.projects_summary[project] || 0) + 1.hour
# create a new SailorsLogSlackNotification
log.notification_preferences.each do |preference|
new_notification << {
slack_uid: log.slack_uid,
slack_channel_id: preference.slack_channel_id,
project_name: project,
project_duration: new_project_time
}
end
log.projects_summary[project] = new_project_time
end
end
ActiveRecord::Base.transaction do
SailorsLogSlackNotification.insert_all(new_notification)
log.save! if log.changed?
end
end
end
end

View file

@ -0,0 +1,40 @@
class SailorsLogSlackNotificationJob < ApplicationJob
queue_as :default
def perform(sailors_log_slack_notification)
slack_uid = sailors_log_slack_notification.slack_uid
slack_channel_id = sailors_log_slack_notification.slack_channel_id
project_name = sailors_log_slack_notification.project_name
project_duration = sailors_log_slack_notification.project_duration
kudos_message = [
"Great work!",
"Nice job!",
"Amazing!",
"Fantastic!",
"Excellent!",
"Awesome!",
"Well done!",
"Wahoo!",
"Way to go!"
].sample
hours = project_duration / 3600
message = ":boat: <@#{slack_uid}> just coded 1 more hour on #{project_name} (total: #{hours}hrs). #{kudos_message}"
response = HTTP.auth("Bearer #{ENV['SLACK_BOT_TOKEN']}")
.post("https://slack.com/api/chat.postMessage",
json: {
channel: slack_channel_id,
text: message
})
response_data = JSON.parse(response.body)
if response_data["ok"]
sailors_log_slack_notification.update(sent: true)
else
Rails.logger.error("Failed to send Slack notification: #{response_data["error"]}")
end
end
end

24
app/models/sailors_log.rb Normal file
View file

@ -0,0 +1,24 @@
class SailorsLog < ApplicationRecord
validates :slack_uid, presence: true, uniqueness: true
after_create :initialize_projects_summary
has_many :notification_preferences,
class_name: "SailorsLogNotificationPreference",
foreign_key: :slack_uid,
primary_key: :slack_uid
has_many :notifications,
class_name: "SailorsLogSlackNotification",
foreign_key: :slack_uid,
primary_key: :slack_uid
private
def initialize_projects_summary
self.projects_summary = {}
Heartbeat.where(user_id: slack_uid).distinct.pluck(:project).each do |project|
self.projects_summary[project] = Heartbeat.where(user_id: slack_uid, project: project).duration_seconds
end
save! if changed?
end
end

View file

@ -0,0 +1,6 @@
class SailorsLogNotificationPreference < ApplicationRecord
belongs_to :sailors_log,
class_name: "SailorsLog",
foreign_key: :slack_uid,
primary_key: :slack_uid
end

View file

@ -0,0 +1,11 @@
class SailorsLogSlackNotification < ApplicationRecord
after_create :notify_user
private
def notify_user
return if sent?
SailorsLogSlackNotificationJob.perform_later(self)
end
end

View file

@ -0,0 +1,10 @@
class CreateSailorsLogs < ActiveRecord::Migration[8.0]
def change
create_table :sailors_logs do |t|
t.string :slack_uid, null: false
t.jsonb :projects_summary, null: false, default: {}
t.timestamps
end
end
end

View file

@ -0,0 +1,11 @@
class CreateSailorsLogNotificationPreferences < ActiveRecord::Migration[8.0]
def change
create_table :sailors_log_notification_preferences do |t|
t.string :slack_uid, null: false
t.string :slack_channel_id, null: false
t.boolean :enabled, null: false, default: true
t.timestamps
end
end
end

View file

@ -0,0 +1,14 @@
class CreateSailorsLogSlackNotifications < ActiveRecord::Migration[8.0]
def change
create_table :sailors_log_slack_notifications do |t|
t.string :slack_uid, null: false
t.string :slack_channel_id, null: false
t.string :project_name, null: false
t.integer :project_duration, null: false
t.boolean :sent, null: false, default: false
t.timestamps
end
end
end

27
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_02_22_005401) do
ActiveRecord::Schema[8.0].define(version: 2025_02_22_032930) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@ -122,6 +122,31 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_22_005401) do
t.datetime "deleted_at"
end
create_table "sailors_log_notification_preferences", force: :cascade do |t|
t.string "slack_uid", null: false
t.string "slack_channel_id", null: false
t.boolean "enabled", default: true, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "sailors_log_slack_notifications", force: :cascade do |t|
t.string "slack_uid", null: false
t.string "slack_channel_id", null: false
t.string "project_name", null: false
t.integer "project_duration", null: false
t.boolean "sent", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "sailors_logs", force: :cascade do |t|
t.string "slack_uid", null: false
t.jsonb "projects_summary", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "slack_uid", null: false
t.string "email", null: false

View file

@ -0,0 +1,11 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
slack_uid: MyString
enabled: false
slack_channel_id: MyString
two:
slack_uid: MyString
enabled: false
slack_channel_id: MyString

View file

@ -0,0 +1,11 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
slack_uid: MyString
slack_channel_id: MyString
project_name: MyString
two:
slack_uid: MyString
slack_channel_id: MyString
project_name: MyString

7
test/fixtures/sailors_logs.yml vendored Normal file
View file

@ -0,0 +1,7 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
slack_uid: MyString
two:
slack_uid: MyString

View file

@ -0,0 +1,7 @@
require "test_helper"
class SailorsLogNotificationPreferenceTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View file

@ -0,0 +1,7 @@
require "test_helper"
class SailorsLogSlackNotificationTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View file

@ -0,0 +1,7 @@
require "test_helper"
class SailorsLogTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end