Skip to content
This repository has been archived by the owner on Feb 20, 2020. It is now read-only.

Post major events within a group to slack #130

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ gem 'delayed_job_active_record'
gem 'daemons'
gem 'sinatra', :require => nil

#webhooks
gem 'httparty'
gem 'sequenced', '~> 2.0.0'

# error tracking
gem 'airbrake'

Expand Down
12 changes: 12 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ GEM
activesupport (>= 4.1.0)
hashie (3.4.2)
highline (1.7.2)
httparty (0.13.7)
json (~> 1.8)
multi_xml (>= 0.5.2)
i18n (0.7.0)
json (1.8.3)
loofah (2.0.2)
Expand All @@ -117,6 +120,7 @@ GEM
money (~> 6.5.0)
railties (>= 3.0)
multi_json (1.11.2)
multi_xml (0.5.5)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-sftp (2.1.2)
Expand Down Expand Up @@ -208,6 +212,9 @@ GEM
rspec-support (~> 3.3.0)
rspec-support (3.3.0)
ruby-graphviz (1.2.2)
sequenced (2.0.0)
activerecord (>= 3.0)
activesupport (>= 3.0)
simplecov (0.10.0)
docile (~> 1.1.0)
json (~> 1.8)
Expand Down Expand Up @@ -253,6 +260,7 @@ DEPENDENCIES
factory_girl_rails
faker
foreigner
httparty
money-rails
omniauth
pg
Expand All @@ -268,7 +276,11 @@ DEPENDENCIES
redcarpet
responders (~> 2.0)
rspec-rails (~> 3.0)
sequenced (~> 2.0.0)
simplecov
sinatra
spring
timecop

BUNDLED WITH
1.11.2
4 changes: 2 additions & 2 deletions app/controllers/buckets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def show
def create
bucket = Bucket.new(bucket_params_create)
if bucket.save
# BucketService.send_bucket_created_emails(bucket: bucket)
BucketService.bucket_created(bucket: bucket, current_user: current_user)
render json: [bucket]
else
render json: {
Expand All @@ -42,7 +42,7 @@ def update
def open_for_funding
bucket = Bucket.find(params[:id])
bucket.open_for_funding(target: params[:target], funding_closes_at: params[:funding_closes_at])
# BucketService.send_bucket_live_emails(bucket: bucket)
BucketService.bucket_moved_to_funding(bucket: bucket, current_user: current_user)
render json: [bucket]
end

Expand Down
1 change: 1 addition & 0 deletions app/controllers/contributions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def index
def create
contribution = Contribution.create(contribution_params)
if contribution.valid?
BucketService.bucket_received_contribution(bucket: contribution.bucket, current_user: current_user)
ContributionService.send_bucket_received_contribution_emails(contribution: contribution)
render json: [contribution]
else
Expand Down
27 changes: 27 additions & 0 deletions app/models/event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Event < ActiveRecord::Base
KINDS = %w[bucket_created bucket_moved_to_funding bucket_funded]

belongs_to :eventable, polymorphic: true
belongs_to :group
belongs_to :user

scope :sequenced, -> { where('sequence_id is not null').order('sequence_id asc') }
scope :chronologically, -> { order('created_at asc') }

after_create :notify_webhooks!, if: :group

validates_inclusion_of :kind, :in => KINDS
validates_presence_of :eventable

acts_as_sequenced scope: :group_id, column: :sequence_id, skip: lambda {|e| e.group.nil? || e.group_id.nil? }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

future self: look

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gdpelican

connor and eugene want to know what is this? and do we need it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Er, we use this to say events have a sequence_id, which is a better way to order things (in a discussion say) than by ID or created_at. You shouldn't need it for webhook stuff.


def belongs_to?(this_user)
self.user_id == this_user.id
end

def notify_webhooks!
self.group.webhooks.each { |webhook| WebhookService.publish! webhook: webhook, event: self }
end
handle_asynchronously :notify_webhooks!

end
10 changes: 10 additions & 0 deletions app/models/events/bucket_created.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Events::BucketCreated < Event

def self.publish!(bucket, user)
create!(kind: 'bucket_created',
eventable: bucket,
group: bucket.group,
user: user)
end

end
11 changes: 11 additions & 0 deletions app/models/events/bucket_funded.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Events::BucketFunded < Event

def self.publish!(bucket, user)
create!(kind: 'bucket_funded',
eventable: bucket,
group: bucket.group,
user: user)
end

end

11 changes: 11 additions & 0 deletions app/models/events/bucket_moved_to_funding.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Events::BucketMovedToFunding < Event

def self.publish!(bucket, user)
create!(kind: 'bucket_moved_to_funding',
eventable: bucket,
group: bucket.group,
user: user)
end

end

1 change: 1 addition & 0 deletions app/models/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class Group < ActiveRecord::Base
has_many :allocations, dependent: :destroy
has_many :memberships
has_many :members, through: :memberships, source: :member
has_many :webhooks, as: :hookable

validates_presence_of :name

Expand Down
13 changes: 13 additions & 0 deletions app/models/webhook.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Webhook < ActiveRecord::Base
belongs_to :hookable, polymorphic: true

validates :uri, presence: true
validates :hookable, presence: true
validates_inclusion_of :kind, in: %w[slack]
validates :event_types, length: { minimum: 1 }

def headers
{}
end

end
50 changes: 50 additions & 0 deletions app/models/webhooks/slack/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Webhooks::Slack::Base = Struct.new(:event) do

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sure looks familiar :P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally, I spun this off from loomio into metamaps land first, and then over to here. Absolutely worth noting that @gdpelican wrote a lot of this originally and should get a lot of the credit :)

def username
"Cobudget Bot"
end

def icon_url
"https://pbs.twimg.com/profile_images/535182261553876992/MV2TWTgd.png"
end

def text
""
end

def attachments
[{
title: attachment_title,
color: '#00B8D4',
text: attachment_text,
fields: attachment_fields,
fallback: attachment_fallback
}]
end

alias :read_attribute_for_serialization :send

private

def view_group_on_cobudget(text = nil)
"<#{url_root}groups/#{eventable.group.id}|#{text || eventable.group.name}>"
end

def bucket_link(text = nil)
"<#{url_root}buckets/#{eventable.id}|#{text || eventable.name}>"
end

def url_root
"http://localhost:9000/#/"
end

def eventable
@eventable ||= event.eventable
end

def author
@author ||= eventable.author
end

end

23 changes: 23 additions & 0 deletions app/models/webhooks/slack/bucket_created.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Webhooks::Slack::BucketCreated < Webhooks::Slack::Base

def text
"*#{eventable.user.name}* created a new idea in *#{view_group_on_cobudget}*"
end

def attachment_fallback
"*#{eventable.name}*\n#{eventable.description}\n"
end

def attachment_title
bucket_link
end

def attachment_text
"#{eventable.description}\n#{bucket_link('Discuss it on Cobudget')}"
end

def attachment_fields
[]
end

end
23 changes: 23 additions & 0 deletions app/models/webhooks/slack/bucket_funded.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Webhooks::Slack::BucketFunded < Webhooks::Slack::Base

def text
"#{eventable.group.currency_symbol}*#{eventable.target}* #{eventable.group.currency_code} bucket just got fully funded!"
end

def attachment_fallback
"*#{eventable.name}*\n#{eventable.description}\n"
end

def attachment_title
bucket_link
end

def attachment_text
"#{eventable.description}\n#{bucket_link('View it on Cobudget')}"
end

def attachment_fields
[]
end

end
23 changes: 23 additions & 0 deletions app/models/webhooks/slack/bucket_moved_to_funding.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Webhooks::Slack::BucketMovedToFunding < Webhooks::Slack::Base

def text
"Bucket with #{eventable.group.currency_symbol}#{eventable.target} #{eventable.group.currency_code} target is now open for funding"
end

def attachment_fallback
"*#{eventable.name}*\n#{eventable.description}\n"
end

def attachment_title
bucket_link
end

def attachment_text
"#{eventable.description}\n#{bucket_link('Comment or Fund it on Cobudget')}"
end

def attachment_fields
[]
end

end
3 changes: 3 additions & 0 deletions app/serializers/webhook_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class WebhookSerializer < ActiveModel::Serializer
attributes :text, :username, :icon_url, :attachments
end
14 changes: 14 additions & 0 deletions app/services/bucket_service.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
class BucketService
def self.bucket_created(bucket: ,current_user: )
Events::BucketCreated.publish!(bucket, current_user)
end

def self.bucket_moved_to_funding(bucket: , current_user: )
Events::BucketMovedToFunding.publish!(bucket, current_user)
end

def self.bucket_received_contribution(bucket: , current_user: )
if bucket.funded?
Events::BucketFunded.publish!(bucket, current_user)
end
end

# given the current email settings, its likely that this will never be called
def self.send_bucket_created_emails(bucket: )
memberships = bucket.group.memberships.active.where.not(member_id: bucket.user_id)
Expand Down
18 changes: 18 additions & 0 deletions app/services/webhook_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class WebhookService

def self.publish!(webhook:, event:)
return false unless webhook.event_types.include? event.kind
HTTParty.post webhook.uri, body: payload_for(webhook, event), headers: webhook.headers
end

private

def self.payload_for(webhook, event)
WebhookSerializer.new(webhook_object_for(webhook, event), root: false).to_json
end

def self.webhook_object_for(webhook, event)
"Webhooks::#{webhook.kind.classify}::#{event.kind.classify}".constantize.new(event)
end

end
10 changes: 10 additions & 0 deletions db/migrate/20160317235420_create_webhooks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateWebhooks < ActiveRecord::Migration
def change
create_table :webhooks do |t|
t.references :hookable, polymorphic: true, index: true
t.string :kind, null: false
t.string :uri, null: false
t.text :event_types, array: true, default: []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

future self look

end
end
end
13 changes: 13 additions & 0 deletions db/migrate/20160317235436_create_events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class CreateEvents < ActiveRecord::Migration
def change
create_table :events do |t|
t.string :kind, limit: 255
t.references :eventable, polymorphic: true, index: true
t.references :user, index: true
t.references :group, index: true
t.integer :sequence_id, index: true, default: nil, null: true
t.timestamps
end
add_index :events, [:group_id, :sequence_id], unique: true
end
end
Loading