Skip to content

Commit

Permalink
Feature: Slack integration (chatwoot#783)
Browse files Browse the repository at this point in the history
- Integrations architecture
- Slack integration
  • Loading branch information
subintp authored Jun 12, 2020
1 parent 4f3b483 commit ed1c871
Show file tree
Hide file tree
Showing 44 changed files with 867 additions and 7 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ENVIRONMENT=

#slack
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET

### Change this env variable only if you are using a custom build mobile app
## Mobile app env variables
IOS_APP_ID=6C953F3RX2.com.chatwoot.app
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ gem 'twilio-ruby', '~> 5.32.0'
gem 'twitty'
# facebook client
gem 'koala'
# slack client
gem 'slack-ruby-client'
# Random name generator
gem 'haikunator'

Expand Down Expand Up @@ -115,4 +117,5 @@ group :development, :test do
gem 'simplecov', '0.17.1', require: false
gem 'spring'
gem 'spring-watcher-listen'
gem 'webmock'
end
18 changes: 18 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ GEM
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.1.6)
connection_pool (2.2.2)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.6)
datetime_picker_rails (0.0.7)
momentjs-rails (>= 2.8.1)
Expand Down Expand Up @@ -191,6 +193,7 @@ GEM
ffi (1.12.2)
flag_shih_tzu (0.3.23)
foreman (0.87.1)
gli (2.19.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
google-api-client (0.39.4)
Expand Down Expand Up @@ -225,6 +228,7 @@ GEM
activesupport (>= 5)
haikunator (1.1.0)
hana (1.3.6)
hashdiff (1.0.1)
hashie (4.1.0)
hkdf (0.3.0)
http-accept (1.7.0)
Expand Down Expand Up @@ -411,6 +415,7 @@ GEM
rubocop-rspec (1.39.0)
rubocop (>= 0.68.1)
ruby-progressbar (1.10.1)
safe_yaml (1.0.5)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
Expand Down Expand Up @@ -452,6 +457,13 @@ GEM
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slack-ruby-client (0.14.6)
activesupport
faraday (>= 0.9)
faraday_middleware
gli
hashie
websocket-driver
spring (2.1.0)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
Expand Down Expand Up @@ -507,6 +519,10 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webmock (3.8.3)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webpacker (5.1.1)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
Expand Down Expand Up @@ -583,6 +599,7 @@ DEPENDENCIES
shoulda-matchers
sidekiq
simplecov (= 0.17.1)
slack-ruby-client
spring
spring-watcher-listen
telegram-bot-ruby
Expand All @@ -594,6 +611,7 @@ DEPENDENCIES
uglifier
valid_email2
web-console
webmock
webpacker
webpush
wisper (= 2.0.0)
Expand Down
18 changes: 18 additions & 0 deletions app/controllers/api/v1/accounts/integrations/apps_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class Api::V1::Accounts::Integrations::AppsController < Api::V1::Accounts::BaseController
before_action :fetch_apps, only: [:index]
before_action :fetch_app, only: [:show]

def index; end

def show; end

private

def fetch_apps
@apps = Integrations::App.all
end

def fetch_app
@app = Integrations::App.find(id: params[:id])
end
end
36 changes: 36 additions & 0 deletions app/controllers/api/v1/accounts/integrations/slack_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::BaseController
before_action :fetch_hook, only: [:update, :destroy]

def create
builder = Integrations::Slack::HookBuilder.new(
account: current_account,
code: params[:code],
inbox_id: params[:inbox_id]
)

@hook = builder.perform

render json: @hook
end

def update
builder = Integrations::Slack::ChannelBuilder.new(
hook: @hook, channel: params[:channel]
)
builder.perform

render json: @hook
end

def destroy
@hook.destroy

head :ok
end

private

def fetch_hook
@hook = Integrations::Hook.find(params[:id])
end
end
7 changes: 7 additions & 0 deletions app/controllers/api/v1/integrations/webhooks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Api::V1::Integrations::WebhooksController < ApplicationController
def create
builder = Integrations::Slack::IncomingMessageBuilder.new(params)
response = builder.perform
render json: response
end
end
2 changes: 1 addition & 1 deletion app/dispatchers/async_dispatcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def publish_event(event_name, timestamp, data)
end

def listeners
listeners = [EventListener.instance, WebhookListener.instance]
listeners = [EventListener.instance, WebhookListener.instance, HookListener.instance]
listeners
end
end
11 changes: 11 additions & 0 deletions app/jobs/hook_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class HookJob < ApplicationJob
queue_as :integrations

def perform(hook, message)
return unless hook.slack?

Integrations::Slack::OutgoingMessageBuilder.perform(hook, message)
rescue StandardError => e
Raven.capture_exception(e)
end
end
10 changes: 10 additions & 0 deletions app/listeners/hook_listener.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class HookListener < BaseListener
def message_created(event)
message = extract_message_and_account(event)[0]
return unless message.reportable?

message.account.hooks.each do |hook|
HookJob.perform_later(hook, message)
end
end
end
1 change: 1 addition & 0 deletions app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Account < ApplicationRecord
has_many :webhooks, dependent: :destroy
has_many :labels, dependent: :destroy
has_many :notification_settings, dependent: :destroy
has_many :hooks, dependent: :destroy, class_name: 'Integrations::Hook'
has_flags ACCOUNT_SETTINGS_FLAGS.merge(column: 'settings_flags').merge(DEFAULT_QUERY_SETTING)

enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h
Expand Down
1 change: 1 addition & 0 deletions app/models/conversation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# id :integer not null, primary key
# additional_attributes :jsonb
# agent_last_seen_at :datetime
# identifier :string
# locked :boolean default(FALSE)
# status :integer default("open"), not null
# user_last_seen_at :datetime
Expand Down
1 change: 1 addition & 0 deletions app/models/inbox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Inbox < ApplicationRecord
has_one :agent_bot_inbox, dependent: :destroy
has_one :agent_bot, through: :agent_bot_inbox
has_many :webhooks, dependent: :destroy
has_many :hooks, dependent: :destroy, class_name: 'Integrations::Hook'

after_destroy :delete_round_robin_agents

Expand Down
5 changes: 5 additions & 0 deletions app/models/integrations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Integrations
def self.table_name_prefix
'integrations_'
end
end
51 changes: 51 additions & 0 deletions app/models/integrations/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
class Integrations::App
attr_accessor :params

def initialize(params)
@params = params
end

def id
params[:id]
end

def name
params[:name]
end

def description
params[:description]
end

def logo
params[:logo]
end

def fields
params[:fields]
end

def button
params[:button]
end

def enabled?(account)
account.hooks.where(app_id: id).exists?
end

class << self
def apps
Hashie::Mash.new(APPS_CONFIG)
end

def all
apps.values.each_with_object([]) do |app, result|
result << new(app)
end
end

def find(params)
all.detect { |app| app.id == params[:id] }
end
end
end
36 changes: 36 additions & 0 deletions app/models/integrations/hook.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# == Schema Information
#
# Table name: integrations_hooks
#
# id :bigint not null, primary key
# access_token :string
# hook_type :integer default("account")
# settings :text
# status :integer default("disabled")
# created_at :datetime not null
# updated_at :datetime not null
# account_id :integer
# app_id :string
# inbox_id :integer
# reference_id :string
#
class Integrations::Hook < ApplicationRecord
validates :account_id, presence: true
validates :app_id, presence: true

enum status: { disabled: 0, enabled: 1 }

belongs_to :account
belongs_to :inbox, optional: true
has_secure_token :access_token

enum hook_type: { account: 0, inbox: 1 }

def app
@app ||= Integrations::App.find(id: app_id)
end

def slack?
app_id == 'cw_slack'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
json.array! @apps do |app|
json.id app.id
json.name app.name
json.logo app.logo
json.enabled app.enabled?(@current_account)
json.button app.button
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
json.id @app.id
json.name @app.name
json.logo @app.logo
json.description @app.description
json.fields @app.fields
json.enabled @app.enabled?(@current_account)
json.button @app.button
1 change: 1 addition & 0 deletions config/initializers/00_init.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
APPS_CONFIG = YAML.load_file(File.join(Rails.root, 'config/integration', 'apps.yml'))
6 changes: 6 additions & 0 deletions config/integration/apps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
slack:
id: cw_slack
name: Slack
logo: https://a.slack-edge.com/80588/marketing/img/media-kit/img-logos@2x.png
description: "'Be less busy' - Slack is the chat tool that brings all your communication together in one place. By integrating Slack with SupportBee, you can get notified in your Slack channels for important events in your support desk"
button: <a href="https://slack.com/oauth/v2/authorize?scope=incoming-webhook,commands,chat:write&client_id=706921004289.1094198503990"><img alt=""Add to Slack"" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
12 changes: 10 additions & 2 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,26 @@
end
resource :notification_settings, only: [:show, :update]

resources :webhooks, except: [:show]
resources :webhooks, except: [:show]
namespace :integrations do
resources :apps, only: [:index, :show]
resources :slack, only: [:create, :update, :destroy]
end
end
end

# end of account scoped api routes
# ----------------------------------

namespace :integrations do
resources :webhooks, only: [:create]
end

resource :profile, only: [:show, :update]
resource :notification_subscriptions, only: [:create]

resources :agent_bots, only: [:index]


namespace :widget do
resources :events, only: [:create]
resources :messages, only: [:index, :create, :update]
Expand Down
1 change: 1 addition & 0 deletions config/sidekiq.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
:queues:
- [low, 1]
- [webhooks, 1]
- [integrations, 2]
- [bots, 1]
- [active_storage_analysis, 1]
- [action_mailbox_incineration, 1]
Expand Down
15 changes: 15 additions & 0 deletions db/migrate/20200430163438_create_integrations_hooks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class CreateIntegrationsHooks < ActiveRecord::Migration[6.0]
def change
create_table :integrations_hooks do |t|
t.integer :status, default: 0
t.integer :inbox_id
t.integer :account_id
t.string :app_id
t.text :settings
t.integer :hook_type, default: 0
t.string :reference_id
t.string :access_token
t.timestamps
end
end
end
Loading

0 comments on commit ed1c871

Please sign in to comment.