chore: General fixes and clean up (#1169)

This commit is contained in:
Sojan Jose 2020-08-25 23:04:02 +05:30 committed by GitHub
parent 124e43b477
commit 2193de9853
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 111 additions and 121 deletions

View file

@ -50,6 +50,8 @@ class Messages::Facebook::MessageBuilder
def attach_file(attachment, file_url) def attach_file(attachment, file_url)
file_resource = LocalResource.new(file_url) file_resource = LocalResource.new(file_url)
attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding) attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding)
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
Rails.logger.info "invalid url #{file_url} : #{e.message}"
end end
def conversation def conversation

View file

@ -81,6 +81,8 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
avatar_resource = LocalResource.new(uri) avatar_resource = LocalResource.new(uri)
facebook_inbox.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding) facebook_inbox.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
Rails.logger.info "invalid url #{file_url} : #{e.message}"
end end
def get_avatar_url(page_id) def get_avatar_url(page_id)

View file

@ -1,5 +1,6 @@
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
include Events::Types include Events::Types
before_action :conversation, except: [:index] before_action :conversation, except: [:index]
before_action :contact_inbox, only: [:create] before_action :contact_inbox, only: [:create]

View file

@ -9,7 +9,11 @@ class AsyncDispatcher < BaseDispatcher
end end
def listeners def listeners
listeners = [EventListener.instance, WebhookListener.instance, HookListener.instance] listeners = [
EventListener.instance,
WebhookListener.instance,
InstallationWebhookListener.instance, HookListener.instance
]
listeners listeners
end end
end end

View file

@ -4,5 +4,7 @@ class ContactAvatarJob < ApplicationJob
def perform(contact, avatar_url) def perform(contact, avatar_url)
avatar_resource = LocalResource.new(avatar_url) avatar_resource = LocalResource.new(avatar_url)
contact.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding) contact.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
Rails.logger.info "invalid url #{file_url} : #{e.message}"
end end
end end

View file

@ -0,0 +1,19 @@
class InstallationWebhookListener < BaseListener
def account_created(event)
payload = event.data[:account].webhook_data.merge(event: __method__.to_s)
deliver_webhook_payloads(payload)
end
def account_destroyed(event)
payload = event.data[:account].webhook_data.merge(event: __method__.to_s)
deliver_webhook_payloads(payload)
end
private
def deliver_webhook_payloads(payload)
# Deliver the installation event
webhook_url = InstallationConfig.find_by(name: 'INSTALLATION_EVENTS_WEBHOOK_URL')&.value
WebhookJob.perform_later(webhook_url, payload) if webhook_url
end
end

View file

@ -16,8 +16,6 @@
class Account < ApplicationRecord class Account < ApplicationRecord
# used for single column multi flags # used for single column multi flags
include FlagShihTzu include FlagShihTzu
include Events::Types
include Reportable include Reportable
include Featurable include Featurable
@ -54,7 +52,7 @@ class Account < ApplicationRecord
enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h
after_create :notify_creation after_create_commit :notify_creation
after_destroy :notify_deletion after_destroy :notify_deletion
def agents def agents

View file

@ -24,8 +24,6 @@
# #
class AccountUser < ApplicationRecord class AccountUser < ApplicationRecord
include Events::Types
belongs_to :account belongs_to :account
belongs_to :user belongs_to :user
belongs_to :inviter, class_name: 'User', optional: true belongs_to :inviter, class_name: 'User', optional: true
@ -33,7 +31,7 @@ class AccountUser < ApplicationRecord
enum role: { agent: 0, administrator: 1 } enum role: { agent: 0, administrator: 1 }
accepts_nested_attributes_for :account accepts_nested_attributes_for :account
after_create :notify_creation, :create_notification_setting after_create_commit :notify_creation, :create_notification_setting
after_destroy :notify_deletion, :destroy_notification_setting after_destroy :notify_deletion, :destroy_notification_setting
validates :user_id, uniqueness: { scope: :account_id } validates :user_id, uniqueness: { scope: :account_id }

View file

@ -1,7 +1,11 @@
class ApplicationRecord < ActiveRecord::Base class ApplicationRecord < ActiveRecord::Base
include Events::Types
self.abstract_class = true self.abstract_class = true
# the models that exposed in email templates through liquid
DROPPABLES = %w[Account Channel Conversation Inbox User].freeze DROPPABLES = %w[Account Channel Conversation Inbox User].freeze
# ModelDrop class should exist in app/drops
def to_drop def to_drop
return unless DROPPABLES.include?(self.class.name) return unless DROPPABLES.include?(self.class.name)

View file

@ -26,7 +26,6 @@ class Contact < ApplicationRecord
include Pubsubable include Pubsubable
include Avatarable include Avatarable
include AvailabilityStatusable include AvailabilityStatusable
include Events::Types
validates :account_id, presence: true validates :account_id, presence: true
validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false } validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false }

View file

@ -31,8 +31,6 @@
# #
class Conversation < ApplicationRecord class Conversation < ApplicationRecord
include Events::Types
validates :account_id, presence: true validates :account_id, presence: true
validates :inbox_id, presence: true validates :inbox_id, presence: true

View file

@ -28,8 +28,6 @@
# #
class Message < ApplicationRecord class Message < ApplicationRecord
include Events::Types
NUMBER_OF_PERMITTED_ATTACHMENTS = 15 NUMBER_OF_PERMITTED_ATTACHMENTS = 15
validates :account_id, presence: true validates :account_id, presence: true
@ -105,6 +103,7 @@ class Message < ApplicationRecord
created_at: created_at, created_at: created_at,
message_type: message_type, message_type: message_type,
content_type: content_type, content_type: content_type,
private: private,
content_attributes: content_attributes, content_attributes: content_attributes,
source_id: source_id, source_id: source_id,
sender: sender.try(:webhook_data), sender: sender.try(:webhook_data),

View file

@ -1,97 +0,0 @@
class Plan
attr_accessor :key, :attributes
def initialize(key, attributes = {})
@key = key.to_sym
@attributes = attributes
end
def name
attributes[:name]
end
def id
attributes[:id]
end
def price
attributes[:price]
end
def active
attributes[:active]
end
def version
attributes[:version]
end
class << self
def config
Hashie::Mash.new(PLAN_CONFIG)
end
def default_trial_period
(config['trial_period'] || 14).days
end
def default_pricing_version
config['default_pricing_version']
end
def default_plans
load_active_plans + load_inactive_plans
end
def all_plans
default_plans
end
def active_plans
all_plans.select(&:active)
end
def paid_plan
active_plans.first
end
def inactive_plans
all_plans.reject(&:active)
end
def trial_plan
all_plans.detect { |plan| plan.key == :trial }
end
def plans_of_version(version)
all_plans.select { |plan| plan.version == version }
end
def find_by_key(key)
key = key.to_sym
all_plans.detect { |plan| plan.key == key }.dup
end
# #helpers
def load_active_plans
result = []
Plan.config.active.each_pair do |version, plans|
plans.each_pair do |key, attributes|
result << Plan.new(key, attributes.merge(active: true, version: version))
end
end
result
end
def load_inactive_plans
result = []
Plan.config.inactive.each_pair do |version, plans|
plans.each_pair do |key, attributes|
result << Plan.new(key, attributes.merge(active: false, version: version))
end
end
result
end
end
end

View file

@ -41,7 +41,6 @@ class User < ApplicationRecord
include Avatarable include Avatarable
# Include default devise modules. # Include default devise modules.
include DeviseTokenAuth::Concerns::User include DeviseTokenAuth::Concerns::User
include Events::Types
include Pubsubable include Pubsubable
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
include Reportable include Reportable
@ -78,7 +77,7 @@ class User < ApplicationRecord
before_validation :set_password_and_uid, on: :create before_validation :set_password_and_uid, on: :create
after_create :create_access_token after_create_commit :create_access_token
after_save :update_presence_in_redis, if: :saved_change_to_availability? after_save :update_presence_in_redis, if: :saved_change_to_availability?
scope :order_by_full_name, -> { order('lower(name) ASC') } scope :order_by_full_name, -> { order('lower(name) ASC') }

View file

@ -107,5 +107,7 @@ class Twilio::IncomingMessageService
) )
@message.save! @message.save!
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
Rails.logger.info "invalid url #{file_url} : #{e.message}"
end end
end end

View file

@ -22,3 +22,5 @@
value: false value: false
- name: BRAND_NAME - name: BRAND_NAME
value: 'Chatwoot' value: 'Chatwoot'
- name: 'INSTALLATION_EVENTS_WEBHOOK_URL'
value:

View file

@ -1,10 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
module Events::Types module Events::Types
### Installation Events ###
# account events # account events
ACCOUNT_CREATED = 'account.created' ACCOUNT_CREATED = 'account.created'
ACCOUNT_DESTROYED = 'account.destroyed' ACCOUNT_DESTROYED = 'account.destroyed'
#### Account Events ###
# channel events # channel events
WEBWIDGET_TRIGGERED = 'webwidget.triggered' WEBWIDGET_TRIGGERED = 'webwidget.triggered'
@ -28,10 +30,7 @@ module Events::Types
CONTACT_CREATED = 'contact.created' CONTACT_CREATED = 'contact.created'
CONTACT_UPDATED = 'contact.updated' CONTACT_UPDATED = 'contact.updated'
# subscription events # agent events
AGENT_ADDED = 'agent.added' AGENT_ADDED = 'agent.added'
AGENT_REMOVED = 'agent.removed' AGENT_REMOVED = 'agent.removed'
SUBSCRIPTION_CREATED = 'subscription.created'
SUBSCRIPTION_REACTIVATED = 'subscription.reactivated'
SUBSCRIPTION_DEACTIVATED = 'subscription.deactivated'
end end

View file

@ -1,6 +1,8 @@
class Webhooks::Trigger class Webhooks::Trigger
def self.execute(url, payload) def self.execute(url, payload)
RestClient.post(url, payload.to_json, { content_type: :json, accept: :json }) RestClient.post(url, payload.to_json, { content_type: :json, accept: :json })
rescue RestClient::NotFound => e
Rails.logger.info "invalid url #{url} : #{e.message}"
rescue StandardError => e rescue StandardError => e
Raven.capture_exception(e) Raven.capture_exception(e)
end end

View file

@ -0,0 +1,6 @@
FactoryBot.define do
factory :installation_config do
name { 'xyc' }
value { 1.5 }
end
end

View file

@ -0,0 +1,44 @@
require 'rails_helper'
describe InstallationWebhookListener do
let(:listener) { described_class.instance }
let!(:account) { create(:account) }
let!(:event) { Events::Base.new(event_name, Time.zone.now, account: account) }
describe '#account_created' do
let(:event_name) { :'account.created' }
context 'when installation config is not configured' do
it 'does not trigger webhook' do
expect(WebhookJob).to receive(:perform_later).exactly(0).times
listener.account_created(event)
end
end
context 'when installation config is configured' do
it 'triggers webhook' do
create(:installation_config, name: 'INSTALLATION_EVENTS_WEBHOOK_URL', value: 'https://test.com')
expect(WebhookJob).to receive(:perform_later).with('https://test.com', account.webhook_data.merge(event: 'account_created')).once
listener.account_created(event)
end
end
end
describe '#account_destroyed' do
let(:event_name) { :'account.destroyed' }
context 'when installation config is not configured' do
it 'does not trigger webhook' do
expect(WebhookJob).to receive(:perform_later).exactly(0).times
listener.account_destroyed(event)
end
end
context 'when installation config is configured' do
it 'triggers webhook' do
create(:installation_config, name: 'INSTALLATION_EVENTS_WEBHOOK_URL', value: 'https://test.com')
expect(WebhookJob).to receive(:perform_later).with('https://test.com', account.webhook_data.merge(event: 'account_destroyed')).once
listener.account_destroyed(event)
end
end
end
end

View file

@ -17,7 +17,7 @@ describe WebhookListener do
context 'when webhook is not configured' do context 'when webhook is not configured' do
it 'does not trigger webhook' do it 'does not trigger webhook' do
expect(RestClient).to receive(:post).exactly(0).times expect(WebhookJob).to receive(:perform_later).exactly(0).times
listener.message_created(event) listener.message_created(event)
end end
end end

View file

@ -12,7 +12,10 @@ properties:
description: Flag to identify if it is a private note description: Flag to identify if it is a private note
content_type: content_type:
type: string type: string
enum: ['input_select', 'form', 'cards'] enum: ['input_email', 'cards', 'input_select', 'form' , 'article']
example: 'cards'
description: 'if you want to create custom message types'
content_attributes: content_attributes:
type: object type: object
description: options/form object description: attributes based on your content type

View file

@ -1641,14 +1641,18 @@
"content_type": { "content_type": {
"type": "string", "type": "string",
"enum": [ "enum": [
"input_email",
"cards",
"input_select", "input_select",
"form", "form",
"cards" "article"
] ],
"example": "cards",
"description": "if you want to create custom message types"
}, },
"content_attributes": { "content_attributes": {
"type": "object", "type": "object",
"description": "options/form object" "description": "attributes based on your content type"
} }
} }
} }