From b2d5cc7b058428ab311edc188d31a220d1da0ead Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Fri, 6 Mar 2020 01:43:12 +0530 Subject: [PATCH 01/29] Feature: Introduce bots (#545) Co-authored-by: Pranav Raj S Co-authored-by: Sojan Jose --- .../messages/outgoing/normal_builder.rb | 2 +- app/controllers/api/v1/webhooks_controller.rb | 1 + app/dispatchers/async_dispatcher.rb | 2 +- app/jobs/agent_bot_job.rb | 3 ++ app/listeners/agent_bot_listener.rb | 13 ++++++++ app/listeners/email_notification_listener.rb | 2 ++ app/models/agent_bot.rb | 19 +++++++++++ app/models/agent_bot_inbox.rb | 20 +++++++++++ app/models/conversation.rb | 22 ++++++++++--- app/models/inbox.rb | 3 ++ .../hook_execution_service.rb | 2 ++ config/sidekiq.yml | 1 + .../20200222143100_create_agent_bots.rb | 12 +++++++ ...20200222143259_create_agent_bot_inboxes.rb | 11 +++++++ db/schema.rb | 17 ++++++++++ spec/factories/agent_bot_inboxes.rb | 7 ++++ spec/factories/agent_bots.rb | 7 ++++ spec/jobs/agent_bot_job_spec.rb | 14 ++++++++ spec/listeners/agent_bot_listener_spec.rb | 33 +++++++++++++++++++ spec/models/agent_bot_inbox_spec.rb | 13 ++++++++ spec/models/agent_bot_spec.rb | 8 +++++ spec/models/conversation_spec.rb | 9 +++++ spec/models/inbox_spec.rb | 3 ++ 23 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 app/jobs/agent_bot_job.rb create mode 100644 app/listeners/agent_bot_listener.rb create mode 100644 app/models/agent_bot.rb create mode 100644 app/models/agent_bot_inbox.rb create mode 100644 db/migrate/20200222143100_create_agent_bots.rb create mode 100644 db/migrate/20200222143259_create_agent_bot_inboxes.rb create mode 100644 spec/factories/agent_bot_inboxes.rb create mode 100644 spec/factories/agent_bots.rb create mode 100644 spec/jobs/agent_bot_job_spec.rb create mode 100644 spec/listeners/agent_bot_listener_spec.rb create mode 100644 spec/models/agent_bot_inbox_spec.rb create mode 100644 spec/models/agent_bot_spec.rb diff --git a/app/builders/messages/outgoing/normal_builder.rb b/app/builders/messages/outgoing/normal_builder.rb index b75e8f84b..5969a97c5 100644 --- a/app/builders/messages/outgoing/normal_builder.rb +++ b/app/builders/messages/outgoing/normal_builder.rb @@ -3,7 +3,7 @@ class Messages::Outgoing::NormalBuilder def initialize(user, conversation, params) @content = params[:message] - @private = ['1', 'true', 1, true].include? params[:private] + @private = params[:private] || false @conversation = conversation @user = user @fb_id = params[:fb_id] diff --git a/app/controllers/api/v1/webhooks_controller.rb b/app/controllers/api/v1/webhooks_controller.rb index d15b414c1..5db576000 100644 --- a/app/controllers/api/v1/webhooks_controller.rb +++ b/app/controllers/api/v1/webhooks_controller.rb @@ -5,6 +5,7 @@ class Api::V1::WebhooksController < ApplicationController before_action :login_from_basic_auth, only: [:chargebee] before_action :check_billing_enabled, only: [:chargebee] + def chargebee chargebee_consumer.consume head :ok diff --git a/app/dispatchers/async_dispatcher.rb b/app/dispatchers/async_dispatcher.rb index dff0c9d92..75b853cd0 100644 --- a/app/dispatchers/async_dispatcher.rb +++ b/app/dispatchers/async_dispatcher.rb @@ -5,7 +5,7 @@ class AsyncDispatcher < BaseDispatcher end def listeners - listeners = [EmailNotificationListener.instance, ReportingListener.instance, WebhookListener.instance] + listeners = [AgentBotListener.instance, EmailNotificationListener.instance, ReportingListener.instance, WebhookListener.instance] listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED'] listeners end diff --git a/app/jobs/agent_bot_job.rb b/app/jobs/agent_bot_job.rb new file mode 100644 index 000000000..e988701c4 --- /dev/null +++ b/app/jobs/agent_bot_job.rb @@ -0,0 +1,3 @@ +class AgentBotJob < WebhookJob + queue_as :bots +end diff --git a/app/listeners/agent_bot_listener.rb b/app/listeners/agent_bot_listener.rb new file mode 100644 index 000000000..848c09cb7 --- /dev/null +++ b/app/listeners/agent_bot_listener.rb @@ -0,0 +1,13 @@ +class AgentBotListener < BaseListener + def message_created(event) + message = extract_message_and_account(event)[0] + inbox = message.inbox + return unless message.reportable? && inbox.agent_bot_inbox.present? + return unless inbox.agent_bot_inbox.active? + + agent_bot = inbox.agent_bot_inbox.agent_bot + + payload = message.webhook_data.merge(event: __method__.to_s) + AgentBotJob.perform_later(agent_bot.outgoing_url, payload) + end +end diff --git a/app/listeners/email_notification_listener.rb b/app/listeners/email_notification_listener.rb index c9dfcfb09..a928960b2 100644 --- a/app/listeners/email_notification_listener.rb +++ b/app/listeners/email_notification_listener.rb @@ -1,6 +1,8 @@ class EmailNotificationListener < BaseListener def conversation_created(event) conversation, _account, _timestamp = extract_conversation_and_account(event) + return if conversation.bot? + conversation.inbox.members.each do |agent| next unless agent.notification_settings.find_by(account_id: conversation.account_id).conversation_creation? diff --git a/app/models/agent_bot.rb b/app/models/agent_bot.rb new file mode 100644 index 000000000..a1ff79768 --- /dev/null +++ b/app/models/agent_bot.rb @@ -0,0 +1,19 @@ +# == Schema Information +# +# Table name: agent_bots +# +# id :bigint not null, primary key +# auth_token :string +# description :string +# name :string +# outgoing_url :string +# created_at :datetime not null +# updated_at :datetime not null +# + +class AgentBot < ApplicationRecord + include Avatarable + has_many :agent_bot_inboxes, dependent: :destroy + has_many :inboxes, through: :agent_bot_inboxes + has_secure_token :auth_token +end diff --git a/app/models/agent_bot_inbox.rb b/app/models/agent_bot_inbox.rb new file mode 100644 index 000000000..aa845a578 --- /dev/null +++ b/app/models/agent_bot_inbox.rb @@ -0,0 +1,20 @@ +# == Schema Information +# +# Table name: agent_bot_inboxes +# +# id :bigint not null, primary key +# status :integer default("active") +# created_at :datetime not null +# updated_at :datetime not null +# agent_bot_id :integer +# inbox_id :integer +# + +class AgentBotInbox < ApplicationRecord + validates :inbox_id, presence: true + validates :agent_bot_id, presence: true + + belongs_to :inbox + belongs_to :agent_bot + enum status: { active: 0, inactive: 1 } +end diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 51f53039a..b868153c2 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -34,7 +34,7 @@ class Conversation < ApplicationRecord validates :account_id, presence: true validates :inbox_id, presence: true - enum status: [:open, :resolved] + enum status: { open: 0, resolved: 1, bot: 2 } scope :latest, -> { order(created_at: :desc) } scope :unassigned, -> { where(assignee_id: nil) } @@ -50,6 +50,8 @@ class Conversation < ApplicationRecord before_create :set_display_id, unless: :display_id? + before_create :set_bot_conversation + after_update :notify_status_change, :create_activity, :send_email_notification_to_assignee after_create :send_events, :run_round_robin @@ -102,6 +104,10 @@ class Conversation < ApplicationRecord private + def set_bot_conversation + self.status = :bot if inbox.agent_bot_inbox&.active? + end + def dispatch_events dispatcher_dispatch(CONVERSATION_RESOLVED) end @@ -110,11 +116,18 @@ class Conversation < ApplicationRecord dispatcher_dispatch(CONVERSATION_CREATED) end + def notifiable_assignee_change? + return false if self_assign?(assignee_id) + return false unless saved_change_to_assignee_id? + return false if assignee_id.blank? + + true + end + def send_email_notification_to_assignee - return if self_assign?(assignee_id) - return unless saved_change_to_assignee_id? - return if assignee_id.blank? + return unless notifiable_assignee_change? return if assignee.notification_settings.find_by(account_id: account_id).not_conversation_assignment? + return if bot? AgentNotifications::ConversationNotificationsMailer.conversation_assigned(self, assignee).deliver_later end @@ -161,6 +174,7 @@ class Conversation < ApplicationRecord def run_round_robin return unless inbox.enable_auto_assignment return if assignee + return if bot? inbox.next_available_agent.then { |new_assignee| update_assignee(new_assignee) } end diff --git a/app/models/inbox.rb b/app/models/inbox.rb index 3420cf3f0..bda28b160 100644 --- a/app/models/inbox.rb +++ b/app/models/inbox.rb @@ -33,7 +33,10 @@ class Inbox < ApplicationRecord has_many :members, through: :inbox_members, source: :user has_many :conversations, dependent: :destroy has_many :messages, through: :conversations + + has_one :agent_bot_inbox, dependent: :destroy has_many :webhooks, dependent: :destroy + after_create :subscribe_webhook, if: :facebook? after_destroy :delete_round_robin_agents diff --git a/app/services/message_templates/hook_execution_service.rb b/app/services/message_templates/hook_execution_service.rb index 071e12a64..5117529c0 100644 --- a/app/services/message_templates/hook_execution_service.rb +++ b/app/services/message_templates/hook_execution_service.rb @@ -2,6 +2,8 @@ class MessageTemplates::HookExecutionService pattr_initialize [:message!] def perform + return if inbox.agent_bot_inbox&.active? + ::MessageTemplates::Template::EmailCollect.new(conversation: conversation).perform if should_send_email_collect? end diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 1ffb5fa51..8dc7f5ede 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -16,6 +16,7 @@ - [low, 1] - [mailers, 2] - [webhooks, 1] + - [bots, 1] # you can override concurrency based on environment production: diff --git a/db/migrate/20200222143100_create_agent_bots.rb b/db/migrate/20200222143100_create_agent_bots.rb new file mode 100644 index 000000000..896ec8a0b --- /dev/null +++ b/db/migrate/20200222143100_create_agent_bots.rb @@ -0,0 +1,12 @@ +class CreateAgentBots < ActiveRecord::Migration[6.0] + def change + create_table :agent_bots do |t| + t.string :name + t.string :description + t.string :outgoing_url + t.string :auth_token, unique: true + + t.timestamps + end + end +end diff --git a/db/migrate/20200222143259_create_agent_bot_inboxes.rb b/db/migrate/20200222143259_create_agent_bot_inboxes.rb new file mode 100644 index 000000000..b2d50165e --- /dev/null +++ b/db/migrate/20200222143259_create_agent_bot_inboxes.rb @@ -0,0 +1,11 @@ +class CreateAgentBotInboxes < ActiveRecord::Migration[6.0] + def change + create_table :agent_bot_inboxes do |t| + t.integer :inbox_id + t.integer :agent_bot_id + t.integer :status, default: 0 + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 563501515..1927983cf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -41,6 +41,23 @@ ActiveRecord::Schema.define(version: 20_200_226_194_012) do t.index ['key'], name: 'index_active_storage_blobs_on_key', unique: true end + create_table 'agent_bot_inboxes', force: :cascade do |t| + t.integer 'inbox_id' + t.integer 'agent_bot_id' + t.integer 'status', default: 0 + t.datetime 'created_at', precision: 6, null: false + t.datetime 'updated_at', precision: 6, null: false + end + + create_table 'agent_bots', force: :cascade do |t| + t.string 'name' + t.string 'description' + t.string 'outgoing_url' + t.string 'auth_token' + t.datetime 'created_at', precision: 6, null: false + t.datetime 'updated_at', precision: 6, null: false + end + create_table 'attachments', id: :serial, force: :cascade do |t| t.integer 'file_type', default: 0 t.string 'external_url' diff --git a/spec/factories/agent_bot_inboxes.rb b/spec/factories/agent_bot_inboxes.rb new file mode 100644 index 000000000..df687d78d --- /dev/null +++ b/spec/factories/agent_bot_inboxes.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :agent_bot_inbox do + inbox + agent_bot + status { 'active' } + end +end diff --git a/spec/factories/agent_bots.rb b/spec/factories/agent_bots.rb new file mode 100644 index 000000000..227fd86a2 --- /dev/null +++ b/spec/factories/agent_bots.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :agent_bot do + name { 'MyString' } + description { 'MyString' } + outgoing_url { 'MyString' } + end +end diff --git a/spec/jobs/agent_bot_job_spec.rb b/spec/jobs/agent_bot_job_spec.rb new file mode 100644 index 000000000..af73e4933 --- /dev/null +++ b/spec/jobs/agent_bot_job_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe AgentBotJob, type: :job do + subject(:job) { described_class.perform_later(url, payload) } + + let(:url) { 'https://test.com' } + let(:payload) { { name: 'test' } } + + it 'queues the job' do + expect { job }.to have_enqueued_job(described_class) + .with(url, payload) + .on_queue('bots') + end +end diff --git a/spec/listeners/agent_bot_listener_spec.rb b/spec/listeners/agent_bot_listener_spec.rb new file mode 100644 index 000000000..3425ef3d3 --- /dev/null +++ b/spec/listeners/agent_bot_listener_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' +describe AgentBotListener do + let(:listener) { described_class.instance } + let!(:account) { create(:account) } + let!(:user) { create(:user, account: account) } + let!(:inbox) { create(:inbox, account: account) } + let!(:agent_bot) { create(:agent_bot) } + let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user) } + let!(:message) do + create(:message, message_type: 'outgoing', + account: account, inbox: inbox, conversation: conversation) + end + let!(:event) { Events::Base.new(event_name, Time.zone.now, message: message) } + + describe '#message_created' do + let(:event_name) { :'conversation.created' } + + context 'when agent bot is not configured' do + it 'does not send message to agent bot' do + expect(AgentBotJob).to receive(:perform_later).exactly(0).times + listener.message_created(event) + end + end + + context 'when agent bot is configured' do + it 'sends message to agent bot' do + create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot) + expect(AgentBotJob).to receive(:perform_later).with(agent_bot.outgoing_url, message.webhook_data.merge(event: 'message_created')).once + listener.message_created(event) + end + end + end +end diff --git a/spec/models/agent_bot_inbox_spec.rb b/spec/models/agent_bot_inbox_spec.rb new file mode 100644 index 000000000..2c0ef84e7 --- /dev/null +++ b/spec/models/agent_bot_inbox_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +RSpec.describe AgentBotInbox, type: :model do + describe 'validations' do + it { is_expected.to validate_presence_of(:inbox_id) } + it { is_expected.to validate_presence_of(:agent_bot_id) } + end + + describe 'associations' do + it { is_expected.to belong_to(:agent_bot) } + it { is_expected.to belong_to(:inbox) } + end +end diff --git a/spec/models/agent_bot_spec.rb b/spec/models/agent_bot_spec.rb new file mode 100644 index 000000000..d6f1500c4 --- /dev/null +++ b/spec/models/agent_bot_spec.rb @@ -0,0 +1,8 @@ +require 'rails_helper' + +RSpec.describe AgentBot, type: :model do + describe 'associations' do + it { is_expected.to have_many(:agent_bot_inboxes) } + it { is_expected.to have_many(:inboxes) } + end +end diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index baeb04748..68bdefc53 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -257,4 +257,13 @@ RSpec.describe Conversation, type: :model do expect(lock_event_data).to eq(id: 505, locked: false) end end + + describe '#botinbox: when conversation created inside inbox with agent bot' do + let!(:bot_inbox) { create(:agent_bot_inbox) } + let(:conversation) { create(:conversation, inbox: bot_inbox.inbox) } + + it 'returns conversation status as bot' do + expect(conversation.status).to eq('bot') + end + end end diff --git a/spec/models/inbox_spec.rb b/spec/models/inbox_spec.rb index abe14211d..db189b1d3 100644 --- a/spec/models/inbox_spec.rb +++ b/spec/models/inbox_spec.rb @@ -23,6 +23,9 @@ RSpec.describe Inbox do it { is_expected.to have_many(:conversations).dependent(:destroy) } it { is_expected.to have_many(:messages).through(:conversations) } + + it { is_expected.to have_one(:agent_bot_inbox) } + it { is_expected.to have_many(:webhooks).dependent(:destroy) } end From 8b4df986bfa431275d290f50d75a3bfb33e28f47 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Sat, 7 Mar 2020 12:18:16 +0530 Subject: [PATCH 02/29] Chore: Enable Users to create multiple accounts (#440) Addresses: #402 - migrations to split roles and other attributes from users table - make changes in code to accommodate this change Co-authored-by: Sojan Jose Co-authored-by: Pranav Raj Sreepuram --- .gitignore | 4 +- .rubocop.yml | 3 ++ app/builders/account_builder.rb | 18 +++++-- app/controllers/api/v1/agents_controller.rb | 33 +++++++++--- app/models/account.rb | 3 +- app/models/account_user.rb | 48 ++++++++++++++++++ app/models/user.rb | 50 ++++++++++++------- app/views/api/v1/agents/index.json.jbuilder | 2 +- app/views/api/v1/models/user.json.jbuilder | 12 +++++ .../api/v1/profiles/update.json.jbuilder | 2 +- app/views/devise/auth.json.jbuilder | 6 +-- app/views/devise/token.json.jbuilder | 6 +-- .../20200121190901_create_account_users.rb | 42 ++++++++++++++++ ..._uniqueness_constraint_to_account_users.rb | 5 ++ db/schema.rb | 21 +++++--- db/seeds.rb | 8 ++- spec/factories/account_users.rb | 9 ++++ spec/factories/users.rb | 6 ++- .../mailers/confirmation_instructions_spec.rb | 7 ++- spec/models/account_spec.rb | 3 +- spec/models/account_user_spec.rb | 16 ++++++ spec/models/user_spec.rb | 14 +----- spec/policies/contact_policy_spec.rb | 6 ++- spec/policies/inbox_policy_spec.rb | 6 ++- spec/policies/user_policy_spec.rb | 8 +-- 25 files changed, 264 insertions(+), 74 deletions(-) create mode 100644 app/models/account_user.rb create mode 100644 app/views/api/v1/models/user.json.jbuilder create mode 100644 db/migrate/20200121190901_create_account_users.rb create mode 100644 db/migrate/20200121220431_add_uniqueness_constraint_to_account_users.rb create mode 100644 spec/factories/account_users.rb create mode 100644 spec/models/account_user_spec.rb diff --git a/.gitignore b/.gitignore index 629e1676e..d058d20d1 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,6 @@ coverage # ignore packages node_modules -package-lock.json \ No newline at end of file +package-lock.json + +*.dump diff --git a/.rubocop.yml b/.rubocop.yml index 7c73f0191..6d6ec1f13 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -41,6 +41,9 @@ RSpec/NestedGroups: Max: 4 RSpec/MessageSpies: Enabled: false +Rails/BulkChangeTable: + Exclude: + - 'db/migrate/20200121190901_create_account_users.rb' AllCops: Exclude: - db/* diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb index c02bea13f..126eedce0 100644 --- a/app/builders/account_builder.rb +++ b/app/builders/account_builder.rb @@ -42,18 +42,26 @@ class AccountBuilder def create_and_link_user password = Time.now.to_i - @user = @account.users.new(email: @email, - password: password, - password_confirmation: password, - role: User.roles['administrator'], - name: email_to_name(@email)) + @user = User.new(email: @email, + password: password, + password_confirmation: password, + name: email_to_name(@email)) if @user.save! + link_user_to_account(@user, @account) @user else raise UserErrors.new(errors: @user.errors) end end + def link_user_to_account(user, account) + AccountUser.create!( + account_id: account.id, + user_id: user.id, + role: AccountUser.roles['administrator'] + ) + end + def email_to_name(email) name = email[/[^@]+/] name.split('.').map(&:capitalize).join(' ') diff --git a/app/controllers/api/v1/agents_controller.rb b/app/controllers/api/v1/agents_controller.rb index a1758b40b..aa0c665cd 100644 --- a/app/controllers/api/v1/agents_controller.rb +++ b/app/controllers/api/v1/agents_controller.rb @@ -1,25 +1,27 @@ class Api::V1::AgentsController < Api::BaseController before_action :fetch_agent, except: [:create, :index] before_action :check_authorization - before_action :build_agent, only: [:create] + before_action :find_user, only: [:create] + before_action :create_user, only: [:create] + before_action :save_account_user, only: [:create] def index @agents = agents end def destroy - @agent.destroy + @agent.account_user.destroy head :ok end def update - @agent.update!(agent_params) - render json: @agent + @agent.update!(agent_params.except(:role)) + @agent.account_user.update!(role: agent_params[:role]) if agent_params[:role] + render 'api/v1/models/user.json', locals: { resource: @agent } end def create - @agent.save! - render json: @agent + render 'api/v1/models/user.json', locals: { resource: @user } end private @@ -32,8 +34,23 @@ class Api::V1::AgentsController < Api::BaseController @agent = agents.find(params[:id]) end - def build_agent - @agent = agents.new(new_agent_params) + def find_user + @user = User.find_by(email: new_agent_params[:email]) + end + + def create_user + return if @user + + @user = User.create!(new_agent_params.slice(:email, :name, :password, :password_confirmation)) + end + + def save_account_user + AccountUser.create!( + account_id: current_account.id, + user_id: @user.id, + role: new_agent_params[:role], + inviter_id: current_user.id + ) end def agent_params diff --git a/app/models/account.rb b/app/models/account.rb index 77b102847..6da157ac4 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -13,7 +13,8 @@ class Account < ApplicationRecord validates :name, presence: true - has_many :users, dependent: :destroy + has_many :account_users, dependent: :destroy + has_many :users, through: :account_users has_many :inboxes, dependent: :destroy has_many :conversations, dependent: :destroy has_many :contacts, dependent: :destroy diff --git a/app/models/account_user.rb b/app/models/account_user.rb new file mode 100644 index 000000000..e86f1d3e2 --- /dev/null +++ b/app/models/account_user.rb @@ -0,0 +1,48 @@ +# == Schema Information +# +# Table name: account_users +# +# id :bigint not null, primary key +# role :integer default("agent") +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint +# inviter_id :bigint +# user_id :bigint +# +# Indexes +# +# index_account_users_on_account_id (account_id) +# index_account_users_on_user_id (user_id) +# uniq_user_id_per_account_id (account_id,user_id) UNIQUE +# +# Foreign Keys +# +# fk_rails_... (account_id => accounts.id) +# fk_rails_... (user_id => users.id) +# + +class AccountUser < ApplicationRecord + belongs_to :account + belongs_to :user + belongs_to :inviter, class_name: 'User', optional: true + + enum role: { agent: 0, administrator: 1 } + accepts_nested_attributes_for :account + + after_create :create_notification_setting + after_destroy :destroy_notification_setting + + validates :user_id, uniqueness: { scope: :account_id } + + def create_notification_setting + setting = user.notification_settings.new(account_id: account.id) + setting.selected_email_flags = [:conversation_assignment] + setting.save! + end + + def destroy_notification_setting + setting = user.notification_settings.new(account_id: account.id) + setting.destroy! + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 2aa47a011..6be83c578 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,28 +19,20 @@ # remember_created_at :datetime # reset_password_sent_at :datetime # reset_password_token :string -# role :integer default("agent") # sign_in_count :integer default(0), not null # tokens :json # uid :string default(""), not null # unconfirmed_email :string # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# inviter_id :bigint # # Indexes # # index_users_on_email (email) -# index_users_on_inviter_id (inviter_id) # index_users_on_pubsub_token (pubsub_token) UNIQUE # index_users_on_reset_password_token (reset_password_token) UNIQUE # index_users_on_uid_and_provider (uid,provider) UNIQUE # -# Foreign Keys -# -# fk_rails_... (inviter_id => users.id) ON DELETE => nullify -# class User < ApplicationRecord # Include default devise modules. @@ -62,25 +54,23 @@ class User < ApplicationRecord # The validation below has been commented out as it does not # work because :validatable in devise overrides this. # validates_uniqueness_of :email, scope: :account_id - validates :email, :name, :account_id, presence: true + validates :email, :name, presence: true - enum role: [:agent, :administrator] - - belongs_to :account - belongs_to :inviter, class_name: 'User', required: false - has_many :invitees, class_name: 'User', foreign_key: 'inviter_id', dependent: :nullify + has_many :account_users, dependent: :destroy + has_many :accounts, through: :account_users + accepts_nested_attributes_for :account_users has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify has_many :inbox_members, dependent: :destroy has_many :assigned_inboxes, through: :inbox_members, source: :inbox has_many :messages + has_many :invitees, through: :account_users, class_name: 'User', foreign_key: 'inviter_id', dependent: :nullify has_many :notification_settings, dependent: :destroy before_validation :set_password_and_uid, on: :create - accepts_nested_attributes_for :account + after_create :notify_creation - after_create :notify_creation, :create_notification_setting after_destroy :notify_deletion def send_devise_notification(notification, *args) @@ -91,6 +81,32 @@ class User < ApplicationRecord self.uid = email end + def account_user + # FIXME : temporary hack to transition over to multiple accounts per user + # We should be fetching the current account user relationship here. + account_users&.first + end + + def account + account_user&.account + end + + def administrator? + account_user&.administrator? + end + + def agent? + account_user&.agent? + end + + def role + account_user&.role + end + + def inviter + account_user&.inviter + end + def serializable_hash(options = nil) serialized_user = super(options).merge(confirmed: confirmed?) serialized_user.merge(subscription: account.try(:subscription).try(:summary)) if ENV['BILLING_ENABLED'] @@ -102,7 +118,7 @@ class User < ApplicationRecord end def create_notification_setting - setting = notification_settings.new(account_id: account_id) + setting = notification_settings.new(account_id: account.id) setting.selected_email_flags = [:conversation_assignment] setting.save! end diff --git a/app/views/api/v1/agents/index.json.jbuilder b/app/views/api/v1/agents/index.json.jbuilder index b2883ad2e..0b6a01f1c 100644 --- a/app/views/api/v1/agents/index.json.jbuilder +++ b/app/views/api/v1/agents/index.json.jbuilder @@ -1,5 +1,5 @@ json.array! @agents do |agent| - json.account_id agent.account_id + json.account_id agent.account.id json.availability_status agent.availability_status json.confirmed agent.confirmed? json.email agent.email diff --git a/app/views/api/v1/models/user.json.jbuilder b/app/views/api/v1/models/user.json.jbuilder new file mode 100644 index 000000000..2d99937d0 --- /dev/null +++ b/app/views/api/v1/models/user.json.jbuilder @@ -0,0 +1,12 @@ +json.id resource.id +json.provider resource.provider +json.uid resource.uid +json.name resource.name +json.nickname resource.nickname +json.email resource.email +json.account_id resource.account.id +json.pubsub_token resource.pubsub_token +json.role resource.role +json.inviter_id resource.account_user.inviter_id +json.confirmed resource.confirmed? +json.avatar_url resource.avatar_url diff --git a/app/views/api/v1/profiles/update.json.jbuilder b/app/views/api/v1/profiles/update.json.jbuilder index b55f96967..a1d99525a 100644 --- a/app/views/api/v1/profiles/update.json.jbuilder +++ b/app/views/api/v1/profiles/update.json.jbuilder @@ -4,7 +4,7 @@ json.uid @user.uid json.name @user.name json.nickname @user.nickname json.email @user.email -json.account_id @user.account_id +json.account_id @user.account.id json.pubsub_token @user.pubsub_token json.role @user.role json.confirmed @user.confirmed? diff --git a/app/views/devise/auth.json.jbuilder b/app/views/devise/auth.json.jbuilder index d2caa20ec..2f45362a1 100644 --- a/app/views/devise/auth.json.jbuilder +++ b/app/views/devise/auth.json.jbuilder @@ -5,10 +5,10 @@ json.data do json.name @resource.name json.nickname @resource.nickname json.email @resource.email - json.account_id @resource.account_id + json.account_id @resource.account.id json.pubsub_token @resource.pubsub_token - json.role @resource.role - json.inviter_id @resource.inviter_id + json.role @resource.account_user.role + json.inviter_id @resource.account_user.inviter_id json.confirmed @resource.confirmed? json.avatar_url @resource.avatar_url end diff --git a/app/views/devise/token.json.jbuilder b/app/views/devise/token.json.jbuilder index 78cd44235..770e4adc5 100644 --- a/app/views/devise/token.json.jbuilder +++ b/app/views/devise/token.json.jbuilder @@ -7,10 +7,10 @@ json.payload do json.name @resource.name json.nickname @resource.nickname json.email @resource.email - json.account_id @resource.account_id + json.account_id @resource.account.id json.pubsub_token @resource.pubsub_token - json.role @resource.role - json.inviter_id @resource.inviter_id + json.role @resource.account_user.role + json.inviter_id @resource.account_user.inviter_id json.confirmed @resource.confirmed? json.avatar_url @resource.avatar_url end diff --git a/db/migrate/20200121190901_create_account_users.rb b/db/migrate/20200121190901_create_account_users.rb new file mode 100644 index 000000000..555c7cf3f --- /dev/null +++ b/db/migrate/20200121190901_create_account_users.rb @@ -0,0 +1,42 @@ +class CreateAccountUsers < ActiveRecord::Migration[6.0] + def change + create_table :account_users do |t| + t.references :account, foreign_key: true, index: true + t.references :user, foreign_key: true, index: true + t.integer :role, default: 0 + t.bigint :inviter_id + t.timestamps + end + + migrate_to_account_users + + remove_column :users, :account_id, :bigint + remove_column :users, :role, :integer + remove_column :users, :inviter_id, :bigint + end + + def migrate_to_account_users + ::User.find_in_batches.each do |users| + users.each do |user| + account_user = ::AccountUser.find_by(account_id: user.account_id, user_id: user.id, role: user.role) + + notification_setting = ::NotificationSetting.find_by(account_id: user.account_id, user_id: user.id) + selected_email_flags = notification_setting.selected_email_flags + notification_setting.destroy! + + next if account_user.present? + + ::AccountUser.create!( + account_id: user.account_id, + user_id: user.id, + role: user[:role], # since we are overriding role method, lets fetch value from attribute + inviter_id: user.inviter_id + ) + + updated_notification_setting = ::NotificationSetting.find_by(account_id: user.account_id, user_id: user.id) + updated_notification_setting.selected_email_flags = selected_email_flags + updated_notification_setting.save! + end + end + end +end diff --git a/db/migrate/20200121220431_add_uniqueness_constraint_to_account_users.rb b/db/migrate/20200121220431_add_uniqueness_constraint_to_account_users.rb new file mode 100644 index 000000000..2d49c3feb --- /dev/null +++ b/db/migrate/20200121220431_add_uniqueness_constraint_to_account_users.rb @@ -0,0 +1,5 @@ +class AddUniquenessConstraintToAccountUsers < ActiveRecord::Migration[6.0] + def change + add_index :account_users, [:account_id, :user_id], unique: true, name: 'uniq_user_id_per_account_id' + end +end diff --git a/db/schema.rb b/db/schema.rb index 1927983cf..b72496fe6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -14,6 +14,18 @@ ActiveRecord::Schema.define(version: 20_200_226_194_012) do # These are extensions that must be enabled in order to support this database enable_extension 'plpgsql' + create_table 'account_users', force: :cascade do |t| + t.bigint 'account_id' + t.bigint 'user_id' + t.integer 'role', default: 0 + t.bigint 'inviter_id' + t.datetime 'created_at', precision: 6, null: false + t.datetime 'updated_at', precision: 6, null: false + t.index %w[account_id user_id], name: 'uniq_user_id_per_account_id', unique: true + t.index ['account_id'], name: 'index_account_users_on_account_id' + t.index ['user_id'], name: 'index_account_users_on_user_id' + end + create_table 'accounts', id: :serial, force: :cascade do |t| t.string 'name', null: false t.datetime 'created_at', null: false @@ -230,11 +242,9 @@ ActiveRecord::Schema.define(version: 20_200_226_194_012) do t.index %w[taggable_id taggable_type context], name: 'index_taggings_on_taggable_id_and_taggable_type_and_context' t.index %w[taggable_id taggable_type tagger_id context], name: 'taggings_idy' t.index ['taggable_id'], name: 'index_taggings_on_taggable_id' - t.index %w[taggable_type taggable_id], name: 'index_taggings_on_taggable_type_and_taggable_id' t.index ['taggable_type'], name: 'index_taggings_on_taggable_type' t.index %w[tagger_id tagger_type], name: 'index_taggings_on_tagger_id_and_tagger_type' t.index ['tagger_id'], name: 'index_taggings_on_tagger_id' - t.index %w[tagger_type tagger_id], name: 'index_taggings_on_tagger_type_and_tagger_id' end create_table 'tags', id: :serial, force: :cascade do |t| @@ -271,14 +281,10 @@ ActiveRecord::Schema.define(version: 20_200_226_194_012) do t.string 'nickname' t.string 'email' t.json 'tokens' - t.integer 'account_id', null: false t.datetime 'created_at', null: false t.datetime 'updated_at', null: false t.string 'pubsub_token' - t.integer 'role', default: 0 - t.bigint 'inviter_id' t.index ['email'], name: 'index_users_on_email' - t.index ['inviter_id'], name: 'index_users_on_inviter_id' t.index ['pubsub_token'], name: 'index_users_on_pubsub_token', unique: true t.index ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true t.index %w[uid provider], name: 'index_users_on_uid_and_provider', unique: true @@ -293,10 +299,11 @@ ActiveRecord::Schema.define(version: 20_200_226_194_012) do t.integer 'webhook_type', default: 0 end + add_foreign_key 'account_users', 'accounts' + add_foreign_key 'account_users', 'users' add_foreign_key 'active_storage_attachments', 'active_storage_blobs', column: 'blob_id' add_foreign_key 'contact_inboxes', 'contacts' add_foreign_key 'contact_inboxes', 'inboxes' add_foreign_key 'conversations', 'contact_inboxes' add_foreign_key 'messages', 'contacts' - add_foreign_key 'users', 'users', column: 'inviter_id', on_delete: :nullify end diff --git a/db/seeds.rb b/db/seeds.rb index aed828286..4ebaf08f0 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,9 +1,15 @@ account = Account.create!(name: 'Acme Inc') -user = User.new(name: 'John', email: 'john@acme.inc', password: '123456', account: account, role: :administrator) +user = User.new(name: 'John', email: 'john@acme.inc', password: '123456') user.skip_confirmation! user.save! +AccountUser.create!( + account_id: account.id, + user_id: user.id, + role: :administrator +) + web_widget = Channel::WebWidget.create!(account: account, website_name: 'Acme', website_url: 'https://acme.inc') inbox = Inbox.create!(channel: web_widget, account: account, name: 'Acme Support') diff --git a/spec/factories/account_users.rb b/spec/factories/account_users.rb new file mode 100644 index 000000000..e2bc52e64 --- /dev/null +++ b/spec/factories/account_users.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :account_user do + account + user + role { 'agent' } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index ef2ae752c..a268c1a12 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -4,6 +4,9 @@ FactoryBot.define do factory :user do transient do skip_confirmation { true } + role { 'agent' } + account { nil } + inviter { nil } end provider { 'email' } @@ -11,12 +14,11 @@ FactoryBot.define do name { Faker::Name.name } nickname { Faker::Name.first_name } email { nickname + '@example.com' } - role { 'agent' } password { 'password' } - account after(:build) do |user, evaluator| user.skip_confirmation! if evaluator.skip_confirmation + create(:account_user, user: user, account: evaluator.account, role: evaluator.role, inviter: evaluator.inviter) if evaluator.account end trait :with_avatar do diff --git a/spec/mailers/confirmation_instructions_spec.rb b/spec/mailers/confirmation_instructions_spec.rb index 2c217f911..0b830a333 100644 --- a/spec/mailers/confirmation_instructions_spec.rb +++ b/spec/mailers/confirmation_instructions_spec.rb @@ -4,7 +4,8 @@ require 'rails_helper' RSpec.describe 'Confirmation Instructions', type: :mailer do describe :notify do - let(:confirmable_user) { FactoryBot.build(:user, inviter: inviter_val) } + let(:account) { create(:account) } + let(:confirmable_user) { build(:user, inviter: inviter_val, account: account) } let(:inviter_val) { nil } let(:mail) { Devise::Mailer.confirmation_instructions(confirmable_user, nil, {}) } @@ -23,9 +24,7 @@ RSpec.describe 'Confirmation Instructions', type: :mailer do end context 'when there is an inviter' do - let(:inviter_val) do - FactoryBot.create(:user, role: :administrator, skip_confirmation: true) - end + let(:inviter_val) { create(:user, :administrator, skip_confirmation: true, account: account) } it 'refers to the inviter and their account' do expect(mail.body).to match( diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 10d2b4651..8dc7cfdcb 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -5,7 +5,8 @@ require 'rails_helper' RSpec.describe Account do it { is_expected.to validate_presence_of(:name) } - it { is_expected.to have_many(:users).dependent(:destroy) } + it { is_expected.to have_many(:users).through(:account_users) } + it { is_expected.to have_many(:account_users) } it { is_expected.to have_many(:inboxes).dependent(:destroy) } it { is_expected.to have_many(:conversations).dependent(:destroy) } it { is_expected.to have_many(:contacts).dependent(:destroy) } diff --git a/spec/models/account_user_spec.rb b/spec/models/account_user_spec.rb new file mode 100644 index 000000000..54d16672b --- /dev/null +++ b/spec/models/account_user_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe User do + let!(:account_user) { create(:account_user) } + + describe 'notification_settings' do + it 'gets created with the right default settings' do + expect(account_user.user.notification_settings).not_to eq(nil) + + expect(account_user.user.notification_settings.first.conversation_creation?).to eq(false) + expect(account_user.user.notification_settings.first.conversation_assignment?).to eq(true) + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 58858850e..1f6010b5b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -8,12 +8,11 @@ RSpec.describe User do context 'validations' do it { is_expected.to validate_presence_of(:email) } it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_presence_of(:account_id) } end context 'associations' do - it { is_expected.to belong_to(:account) } - it { is_expected.to belong_to(:inviter).class_name('User').required(false) } + it { is_expected.to have_many(:accounts).through(:account_users) } + it { is_expected.to have_many(:account_users) } it { is_expected.to have_many(:assigned_conversations).class_name('Conversation').dependent(:nullify) } it { is_expected.to have_many(:inbox_members).dependent(:destroy) } it { is_expected.to have_many(:notification_settings).dependent(:destroy) } @@ -27,13 +26,4 @@ RSpec.describe User do it { expect(user.pubsub_token).not_to eq(nil) } it { expect(user.saved_changes.keys).not_to eq('pubsub_token') } end - - describe 'notification_settings' do - it 'gets created with the right default settings' do - expect(user.notification_settings).not_to eq(nil) - - expect(user.notification_settings.first.conversation_creation?).to eq(false) - expect(user.notification_settings.first.conversation_assignment?).to eq(true) - end - end end diff --git a/spec/policies/contact_policy_spec.rb b/spec/policies/contact_policy_spec.rb index f6cad9718..01402ca59 100644 --- a/spec/policies/contact_policy_spec.rb +++ b/spec/policies/contact_policy_spec.rb @@ -5,8 +5,10 @@ require 'rails_helper' RSpec.describe ContactPolicy, type: :policy do subject(:contact_policy) { described_class } - let(:administrator) { create(:user, :administrator) } - let(:agent) { create(:user) } + let(:account) { create(:account) } + + let(:administrator) { create(:user, :administrator, account: account) } + let(:agent) { create(:user, account: account) } let(:contact) { create(:contact) } permissions :index?, :show?, :update? do diff --git a/spec/policies/inbox_policy_spec.rb b/spec/policies/inbox_policy_spec.rb index a963f4aec..5e4de0538 100644 --- a/spec/policies/inbox_policy_spec.rb +++ b/spec/policies/inbox_policy_spec.rb @@ -5,8 +5,10 @@ require 'rails_helper' RSpec.describe InboxPolicy, type: :policy do subject(:inbox_policy) { described_class } - let(:administrator) { create(:user, :administrator) } - let(:agent) { create(:user) } + let(:account) { create(:account) } + + let(:administrator) { create(:user, :administrator, account: account) } + let(:agent) { create(:user, account: account) } let(:inbox) { create(:inbox) } permissions :create?, :destroy? do diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb index 95634a0a7..05e8f3dc0 100644 --- a/spec/policies/user_policy_spec.rb +++ b/spec/policies/user_policy_spec.rb @@ -5,9 +5,11 @@ require 'rails_helper' RSpec.describe UserPolicy, type: :policy do subject(:user_policy) { described_class } - let(:administrator) { create(:user, :administrator) } - let(:agent) { create(:user) } - let(:user) { create(:user) } + let(:account) { create(:account) } + + let(:administrator) { create(:user, :administrator, account: account) } + let(:agent) { create(:user, account: account) } + let(:user) { create(:user, account: account) } permissions :create?, :update?, :destroy? do context 'when administrator' do From 60dc564f373d4309bc74b9fa2f5072f1e235f1b9 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas Date: Sat, 7 Mar 2020 23:39:41 +0530 Subject: [PATCH 03/29] Bug: Fix missing close button on mobile chat window (#600) Co-authored-by: Pranav Raj Sreepuram --- .scss-lint.yml | 2 +- app/javascript/packs/sdk.js | 15 ++++++++ app/javascript/widget/App.vue | 9 ++++- app/javascript/widget/assets/scss/woot.scss | 33 +++++++++++++++++ .../widget/components/AgentMessage.vue | 1 + .../widget/components/ChatHeader.vue | 25 ++----------- .../widget/components/ChatHeaderExpanded.vue | 36 +++++++++++++------ app/javascript/widget/views/Home.vue | 3 +- 8 files changed, 89 insertions(+), 35 deletions(-) diff --git a/.scss-lint.yml b/.scss-lint.yml index dadb2c2cd..eeaac4c52 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -82,7 +82,7 @@ linters: enabled: true ImportantRule: - enabled: true + enabled: false ImportPath: enabled: true diff --git a/app/javascript/packs/sdk.js b/app/javascript/packs/sdk.js index 8f2295ff2..11f485b7a 100755 --- a/app/javascript/packs/sdk.js +++ b/app/javascript/packs/sdk.js @@ -131,6 +131,7 @@ const IFrameHelper = { body.appendChild(holder); IFrameHelper.initPostMessageCommunication(); IFrameHelper.initLocationListener(); + IFrameHelper.initWindowSizeListener(); }, getAppFrame: () => document.getElementById('chatwoot_live_chat_widget'), sendMessage: (key, value) => { @@ -146,6 +147,7 @@ const IFrameHelper = { IFrameHelper.sendMessage('config-set', {}); IFrameHelper.onLoad(message.config.channelConfig); IFrameHelper.setCurrentUrl(); + IFrameHelper.toggleCloseButton(); }, set_auth_token: message => { Cookies.set('cw_conversation', message.authToken); @@ -173,6 +175,11 @@ const IFrameHelper = { IFrameHelper.setCurrentUrl(); }; }, + initWindowSizeListener: () => { + wootOn(window, 'resize', () => { + IFrameHelper.toggleCloseButton(); + }); + }, onLoad: ({ widget_color: widgetColor }) => { const iframe = IFrameHelper.getAppFrame(); iframe.style.visibility = ''; @@ -205,6 +212,14 @@ const IFrameHelper = { refererURL: window.location.href, }); }, + toggleCloseButton: () => { + console.log(window.matchMedia('(max-width: 668px)')); + if (window.matchMedia('(max-width: 668px)').matches) { + IFrameHelper.sendMessage('toggle-close-button', { showClose: true }); + } else { + IFrameHelper.sendMessage('toggle-close-button', { showClose: false }); + } + }, }; function loadIframe({ baseUrl, websiteToken }) { diff --git a/app/javascript/widget/App.vue b/app/javascript/widget/App.vue index 050c5b7c1..e08dcb341 100755 --- a/app/javascript/widget/App.vue +++ b/app/javascript/widget/App.vue @@ -1,5 +1,5 @@ @@ -11,6 +11,11 @@ import { IFrameHelper } from 'widget/helpers/utils'; export default { name: 'App', + data() { + return { + isMobile: false, + }; + }, mounted() { const { website_token: websiteToken = '' } = window.chatwootWebChannel; if (IFrameHelper.isIFrame()) { @@ -40,6 +45,8 @@ export default { this.scrollConversationToBottom(); } else if (message.event === 'set-current-url') { window.refererURL = message.refererURL; + } else if (message.event === 'toggle-close-button') { + this.isMobile = message.showClose; } }); }, diff --git a/app/javascript/widget/assets/scss/woot.scss b/app/javascript/widget/assets/scss/woot.scss index 40304fbfe..222275592 100755 --- a/app/javascript/widget/assets/scss/woot.scss +++ b/app/javascript/widget/assets/scss/woot.scss @@ -17,3 +17,36 @@ body { .woot-widget-wrap { height: 100%; } + +.close-button { + cursor: pointer; + position: relative; + width: $space-two; + + &::before, + &::after { + background-color: $color-heading; + content: ' '; + height: $space-normal; + left: $space-small; + position: absolute; + top: $space-micro; + width: 2px; + } + + &::before { + transform: rotate(45deg); + } + + &::after { + transform: rotate(-45deg); + } +} + +.is-mobile { + .header-wrap { + .close-button { + display: block !important; + } + } +} diff --git a/app/javascript/widget/components/AgentMessage.vue b/app/javascript/widget/components/AgentMessage.vue index 62e478f00..aff9f67d7 100755 --- a/app/javascript/widget/components/AgentMessage.vue +++ b/app/javascript/widget/components/AgentMessage.vue @@ -85,6 +85,7 @@ export default { .avatar-wrap { height: $space-medium; width: $space-medium; + flex-shrink: 0; .user-thumbnail-box { margin-top: -$space-large; diff --git a/app/javascript/widget/components/ChatHeader.vue b/app/javascript/widget/components/ChatHeader.vue index 567f6d69a..07f908455 100644 --- a/app/javascript/widget/components/ChatHeader.vue +++ b/app/javascript/widget/components/ChatHeader.vue @@ -3,7 +3,7 @@

{{ title }}

- + @@ -54,27 +54,8 @@ export default { color: $color-heading; } - .close { - cursor: pointer; - position: relative; - width: $space-two; - - &:before, - &:after { - position: absolute; - left: $space-small; - top: $space-smaller; - content: ' '; - height: $space-normal; - width: 2px; - background-color: $color-heading; - } - &:before { - transform: rotate(45deg); - } - &:after { - transform: rotate(-45deg); - } + .close-button { + display: none; } } diff --git a/app/javascript/widget/components/ChatHeaderExpanded.vue b/app/javascript/widget/components/ChatHeaderExpanded.vue index f7ff7034c..68e023a95 100755 --- a/app/javascript/widget/components/ChatHeaderExpanded.vue +++ b/app/javascript/widget/components/ChatHeaderExpanded.vue @@ -1,22 +1,22 @@ @@ -47,12 +56,19 @@ export default { padding: $space-larger $space-medium $space-large; width: 100%; box-sizing: border-box; + position: relative; .logo { width: 64px; height: 64px; } + .close { + position: absolute; + right: $space-medium; + top: $space-medium; + display: none; + } .title { color: $color-heading; font-size: $font-size-mega; diff --git a/app/javascript/widget/views/Home.vue b/app/javascript/widget/views/Home.vue index a4d9209a8..20d86fdd7 100755 --- a/app/javascript/widget/views/Home.vue +++ b/app/javascript/widget/views/Home.vue @@ -79,9 +79,10 @@ export default { flex-shrink: 0; border-radius: $space-normal; background: white; + z-index: 99; @include shadow-large; - @media only screen and (min-device-width: 320px) and (max-device-width: 480px) { + @media only screen and (min-device-width: 320px) and (max-device-width: 667px) { border-radius: 0; } } From 19ab0fe108f913e597e49a4895b95b4e606df099 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Mon, 9 Mar 2020 23:27:10 +0530 Subject: [PATCH 04/29] Chore: Scope URLs with account_id (#601) * Chore: Enable Users to create multiple accounts Addresses: #402 - migrations to split roles and other attributes from users table - make changes in code to accommodate this change Co-authored-by: Pranav Raj Sreepuram --- .rubocop.yml | 55 +++++- .rubocop_todo.yml | 9 - .../v1/{ => accounts}/accounts_controller.rb | 6 +- .../actions/contact_merges_controller.rb | 2 +- .../v1/{ => accounts}/agents_controller.rb | 2 +- .../v1/{ => accounts}/callbacks_controller.rb | 11 +- .../canned_responses_controller.rb | 2 +- .../contacts/conversations_controller.rb | 2 +- .../v1/{ => accounts}/contacts_controller.rb | 2 +- .../conversations/assignments_controller.rb | 5 +- .../conversations/labels_controller.rb | 5 +- .../conversations/messages_controller.rb | 2 +- .../conversations_controller.rb | 6 +- .../facebook_indicators_controller.rb | 3 +- .../inbox_members_controller.rb | 2 +- .../v1/{ => accounts}/inboxes_controller.rb | 2 +- .../v1/{ => accounts}/labels_controller.rb | 5 +- .../notification_settings_controller.rb | 2 +- .../v1/{ => accounts}/reports_controller.rb | 24 ++- .../subscriptions_controller.rb | 2 +- .../webhooks_controller.rb | 2 +- .../devise_overrides/passwords_controller.rb | 4 +- .../devise_overrides/sessions_controller.rb | 2 +- app/javascript/dashboard/api/ApiClient.js | 20 ++- app/javascript/dashboard/api/agents.js | 2 +- .../dashboard/api/cannedResponse.js | 2 +- app/javascript/dashboard/api/contacts.js | 2 +- app/javascript/dashboard/api/conversations.js | 2 +- app/javascript/dashboard/api/endPoints.js | 15 +- .../dashboard/api/inbox/conversation.js | 2 +- app/javascript/dashboard/api/inbox/message.js | 2 +- app/javascript/dashboard/api/inboxMembers.js | 2 +- app/javascript/dashboard/api/inboxes.js | 2 +- app/javascript/dashboard/api/reports.js | 30 ++-- .../dashboard/api/userNotificationSettings.js | 2 +- app/javascript/dashboard/api/webhooks.js | 2 +- .../dashboard/components/layout/Sidebar.vue | 11 +- .../widgets/conversation/ConversationCard.vue | 7 +- app/javascript/dashboard/helper/URLHelper.js | 6 +- .../dashboard/helper/spec/URLHelper.spec.js | 8 +- .../dashboard/i18n/default-sidebar.js | 24 ++- .../dashboard/routes/auth/Confirmation.vue | 4 +- .../dashboard/routes/auth/PasswordEdit.vue | 2 +- .../dashboard/routes/auth/Signup.vue | 5 +- .../conversation/conversation.routes.js | 10 +- .../routes/dashboard/dashboard.routes.js | 2 +- .../dashboard/settings/agents/agent.routes.js | 2 +- .../settings/billing/billing.routes.js | 2 +- .../settings/canned/canned.routes.js | 2 +- .../routes/dashboard/settings/inbox/Index.vue | 10 +- .../dashboard/settings/inbox/inbox.routes.js | 2 +- .../dashboard/settings/integrations/Index.vue | 17 +- .../integrations/integrations.routes.js | 2 +- .../settings/profile/profile.routes.js | 2 +- .../settings/reports/reports.routes.js | 2 +- .../dashboard/settings/settings.routes.js | 6 +- app/javascript/dashboard/routes/index.js | 9 +- .../dashboard/store/modules/auth.js | 3 +- app/views/api/v1/account/index.json.jbuilder | 10 -- .../{ => accounts}/agents/index.json.jbuilder | 0 .../callbacks/facebook_pages.json.jbuilder} | 0 .../conversations/index.json.jbuilder | 0 .../contacts/index.json.jbuilder | 0 .../contacts/show.json.jbuilder | 0 .../contacts/update.json.jbuilder | 0 .../assignments/create.json.jbuilder | 0 .../conversations/index.json.jbuilder | 0 .../conversations/labels/create.json.jbuilder | 0 .../conversations/labels/index.json.jbuilder | 0 .../messages/create.json.jbuilder | 0 .../messages/index.json.jbuilder | 0 .../conversations/show.json.jbuilder | 0 .../conversations/toggle_status.json.jbuilder | 0 .../inbox_members/show.json.jbuilder | 0 .../inboxes/index.json.jbuilder | 0 .../{ => accounts}/labels/index.json.jbuilder | 0 .../labels/most_used.json.jbuilder | 0 .../notification_settings/show.json.jbuilder | 0 .../webhooks/_webhook.json.jbuilder | 0 .../webhooks/create.json.jbuilder | 0 .../webhooks/index.json.jbuilder | 0 .../webhooks/update.json.jbuilder | 0 app/views/devise/auth.json.jbuilder | 24 +-- config/routes.rb | 158 +++++++++--------- ...te_active_storage_tables.active_storage.rb | 4 +- .../20200225160650_rename_urls_to_url.rb | 2 +- .../20200225162150_add_type_to_webhook.rb | 2 +- .../actions/contact_merges_controller_spec.rb | 6 +- .../{ => accounts}/agents_controller_spec.rb | 26 +-- .../callbacks_controller_spec.rb | 24 +-- .../canned_responses_controller_spec.rb | 26 +-- .../contacts/conversations_controller_spec.rb | 12 +- .../contacts_controller_spec.rb | 29 ++-- .../assignments_controller_spec.rb | 6 +- .../conversations/labels_controller_spec.rb | 12 +- .../conversations/messages_controller_spec.rb | 12 +- .../conversations_controller_spec.rb | 24 +-- .../facebook_indicators_controller_spec.rb | 24 +-- .../inbox_members_controller_spec.rb | 16 +- .../{ => accounts}/inboxes_controller_spec.rb | 26 +-- .../{ => accounts}/labels_controller_spec.rb | 12 +- .../notification_settings_controller_spec.rb | 12 +- .../subscriptions_controller_spec.rb | 8 +- .../webhook_controller_spec.rb | 24 +-- .../api/v1/accounts_controller_spec.rb | 3 +- 105 files changed, 480 insertions(+), 402 deletions(-) rename app/controllers/api/v1/{ => accounts}/accounts_controller.rb (88%) rename app/controllers/api/v1/{ => accounts}/actions/contact_merges_controller.rb (88%) rename app/controllers/api/v1/{ => accounts}/agents_controller.rb (95%) rename app/controllers/api/v1/{ => accounts}/callbacks_controller.rb (90%) rename app/controllers/api/v1/{ => accounts}/canned_responses_controller.rb (92%) rename app/controllers/api/v1/{ => accounts}/contacts/conversations_controller.rb (85%) rename app/controllers/api/v1/{ => accounts}/contacts_controller.rb (93%) rename app/controllers/api/v1/{ => accounts}/conversations/assignments_controller.rb (69%) rename app/controllers/api/v1/{ => accounts}/conversations/labels_controller.rb (61%) rename app/controllers/api/v1/{ => accounts}/conversations/messages_controller.rb (81%) rename app/controllers/api/v1/{ => accounts}/conversations_controller.rb (82%) rename app/controllers/api/v1/{ => accounts}/facebook_indicators_controller.rb (89%) rename app/controllers/api/v1/{ => accounts}/inbox_members_controller.rb (94%) rename app/controllers/api/v1/{ => accounts}/inboxes_controller.rb (88%) rename app/controllers/api/v1/{ => accounts}/labels_controller.rb (58%) rename app/controllers/api/v1/{user => accounts}/notification_settings_controller.rb (88%) rename app/controllers/api/v1/{ => accounts}/reports_controller.rb (82%) rename app/controllers/api/v1/{ => accounts}/subscriptions_controller.rb (76%) rename app/controllers/api/v1/{account => accounts}/webhooks_controller.rb (90%) delete mode 100644 app/views/api/v1/account/index.json.jbuilder rename app/views/api/v1/{ => accounts}/agents/index.json.jbuilder (100%) rename app/views/api/v1/{callbacks/get_facebook_pages.json.jbuilder => accounts/callbacks/facebook_pages.json.jbuilder} (100%) rename app/views/api/v1/{ => accounts}/contacts/conversations/index.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/contacts/index.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/contacts/show.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/contacts/update.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/conversations/assignments/create.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/conversations/index.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/conversations/labels/create.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/conversations/labels/index.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/conversations/messages/create.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/conversations/messages/index.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/conversations/show.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/conversations/toggle_status.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/inbox_members/show.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/inboxes/index.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/labels/index.json.jbuilder (100%) rename app/views/api/v1/{ => accounts}/labels/most_used.json.jbuilder (100%) rename app/views/api/v1/{user => accounts}/notification_settings/show.json.jbuilder (100%) rename app/views/api/v1/{account => accounts}/webhooks/_webhook.json.jbuilder (100%) rename app/views/api/v1/{account => accounts}/webhooks/create.json.jbuilder (100%) rename app/views/api/v1/{account => accounts}/webhooks/index.json.jbuilder (100%) rename app/views/api/v1/{account => accounts}/webhooks/update.json.jbuilder (100%) rename spec/controllers/api/v1/{ => accounts}/actions/contact_merges_controller_spec.rb (86%) rename spec/controllers/api/v1/{ => accounts}/agents_controller_spec.rb (76%) rename spec/controllers/api/v1/{ => accounts}/callbacks_controller_spec.rb (81%) rename spec/controllers/api/v1/{ => accounts}/canned_responses_controller_spec.rb (76%) rename spec/controllers/api/v1/{ => accounts}/contacts/conversations_controller_spec.rb (76%) rename spec/controllers/api/v1/{ => accounts}/contacts_controller_spec.rb (72%) rename spec/controllers/api/v1/{ => accounts}/conversations/assignments_controller_spec.rb (71%) rename spec/controllers/api/v1/{ => accounts}/conversations/labels_controller_spec.rb (73%) rename spec/controllers/api/v1/{ => accounts}/conversations/messages_controller_spec.rb (72%) rename spec/controllers/api/v1/{ => accounts}/conversations_controller_spec.rb (74%) rename spec/controllers/api/v1/{ => accounts}/facebook_indicators_controller_spec.rb (81%) rename spec/controllers/api/v1/{ => accounts}/inbox_members_controller_spec.rb (80%) rename spec/controllers/api/v1/{ => accounts}/inboxes_controller_spec.rb (80%) rename spec/controllers/api/v1/{ => accounts}/labels_controller_spec.rb (80%) rename spec/controllers/api/v1/{user => accounts}/notification_settings_controller_spec.rb (80%) rename spec/controllers/api/v1/{ => accounts}/subscriptions_controller_spec.rb (81%) rename spec/controllers/api/v1/{account => accounts}/webhook_controller_spec.rb (79%) diff --git a/.rubocop.yml b/.rubocop.yml index 6d6ec1f13..da3bf7cd2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -41,17 +41,54 @@ RSpec/NestedGroups: Max: 4 RSpec/MessageSpies: Enabled: false +Metrics/MethodLength: + Exclude: + - 'db/migrate/20161123131628_devise_token_auth_create_users.rb' +Rails/CreateTableWithTimestamps: + Exclude: + - 'db/migrate/20170207092002_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb' +Style/GuardClause: + Exclude: + - 'app/builders/account_builder.rb' + - 'app/models/attachment.rb' + - 'app/models/message.rb' + - 'lib/webhooks/chargebee.rb' + - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' +Metrics/AbcSize: + Exclude: + - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' +Metrics/CyclomaticComplexity: + Exclude: + - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' +Rails/ReversibleMigration: + Exclude: + - 'db/migrate/20161025070152_removechannelsfrommodels.rb' + - 'db/migrate/20161025070645_remchannel.rb' + - 'db/migrate/20161025070645_remchannel.rb' + - 'db/migrate/20161110102609_removeinboxid.rb' + - 'db/migrate/20170519091539_add_avatar_to_fb.rb' + - 'db/migrate/20191020085608_rename_old_tables.rb' + - 'db/migrate/20191126185833_update_user_invite_foreign_key.rb' + - 'db/migrate/20191130164019_add_template_type_to_messages.rb' Rails/BulkChangeTable: Exclude: + - 'db/migrate/20161025070152_removechannelsfrommodels.rb' - 'db/migrate/20200121190901_create_account_users.rb' + - 'db/migrate/20170211092540_notnullableusers.rb' + - 'db/migrate/20170403095203_contactadder.rb' + - 'db/migrate/20170406104018_add_default_status_conv.rb' + - 'db/migrate/20170511134418_latlong.rb' + - 'db/migrate/20191027054756_create_contact_inboxes.rb' + - 'db/migrate/20191130164019_add_template_type_to_messages.rb' AllCops: Exclude: - - db/* - - bin/**/* - - db/**/* - - config/**/* - - public/**/* - - vendor/**/* - - node_modules/**/* - - lib/tasks/auto_annotate_models.rake - - config/environments/**/* + - 'bin/**/*' + - 'db/schema.rb' + - 'config/**/*' + - 'public/**/*' + - 'vendor/**/*' + - 'node_modules/**/*' + - 'lib/tasks/auto_annotate_models.rake' + - 'config/environments/**/*' + - 'tmp/**/*' + - 'storage/**/*' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 17dfb84f9..06a287ae1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -282,15 +282,6 @@ Style/GlobalVars: Exclude: - 'lib/redis/alfred.rb' -# Offense count: 7 -# Configuration parameters: MinBodyLength. -Style/GuardClause: - Exclude: - - 'app/builders/account_builder.rb' - - 'app/models/attachment.rb' - - 'app/models/message.rb' - - 'lib/webhooks/chargebee.rb' - # Offense count: 4 Style/IdenticalConditionalBranches: Exclude: diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts/accounts_controller.rb similarity index 88% rename from app/controllers/api/v1/accounts_controller.rb rename to app/controllers/api/v1/accounts/accounts_controller.rb index 96e488231..da5c0e77b 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts/accounts_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::AccountsController < Api::BaseController +class Api::V1::Accounts::AccountsController < Api::BaseController include AuthHelper skip_before_action :verify_authenticity_token, only: [:create] @@ -18,9 +18,7 @@ class Api::V1::AccountsController < Api::BaseController ).perform if @user send_auth_headers(@user) - render json: { - data: @user.token_validation_response - } + render 'devise/auth.json', locals: { resource: @user } else render_error_response(CustomExceptions::Account::SignupFailed.new({})) end diff --git a/app/controllers/api/v1/actions/contact_merges_controller.rb b/app/controllers/api/v1/accounts/actions/contact_merges_controller.rb similarity index 88% rename from app/controllers/api/v1/actions/contact_merges_controller.rb rename to app/controllers/api/v1/accounts/actions/contact_merges_controller.rb index 2eead4869..1296b6a55 100644 --- a/app/controllers/api/v1/actions/contact_merges_controller.rb +++ b/app/controllers/api/v1/accounts/actions/contact_merges_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::Actions::ContactMergesController < Api::BaseController +class Api::V1::Accounts::Actions::ContactMergesController < Api::BaseController before_action :set_base_contact, only: [:create] before_action :set_mergee_contact, only: [:create] diff --git a/app/controllers/api/v1/agents_controller.rb b/app/controllers/api/v1/accounts/agents_controller.rb similarity index 95% rename from app/controllers/api/v1/agents_controller.rb rename to app/controllers/api/v1/accounts/agents_controller.rb index aa0c665cd..ca796ceef 100644 --- a/app/controllers/api/v1/agents_controller.rb +++ b/app/controllers/api/v1/accounts/agents_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::AgentsController < Api::BaseController +class Api::V1::Accounts::AgentsController < Api::BaseController before_action :fetch_agent, except: [:create, :index] before_action :check_authorization before_action :find_user, only: [:create] diff --git a/app/controllers/api/v1/callbacks_controller.rb b/app/controllers/api/v1/accounts/callbacks_controller.rb similarity index 90% rename from app/controllers/api/v1/callbacks_controller.rb rename to app/controllers/api/v1/accounts/callbacks_controller.rb index 031d5accc..bb0e0ca0b 100644 --- a/app/controllers/api/v1/callbacks_controller.rb +++ b/app/controllers/api/v1/accounts/callbacks_controller.rb @@ -1,6 +1,4 @@ -require 'rest-client' -require 'telegram/bot' -class Api::V1::CallbacksController < Api::BaseController +class Api::V1::Accounts::CallbacksController < Api::BaseController before_action :inbox, only: [:reauthorize_page] def register_facebook_page @@ -18,7 +16,7 @@ class Api::V1::CallbacksController < Api::BaseController render json: inbox end - def get_facebook_pages + def facebook_pages @page_details = mark_already_existing_facebook_pages(fb_object.get_connections('me', 'accounts')) end @@ -67,7 +65,7 @@ class Api::V1::CallbacksController < Api::BaseController return [] if data.empty? data.inject([]) do |result, page_detail| - current_account.facebook_pages.exists?(page_id: page_detail['id']) ? page_detail.merge!(exists: true) : page_detail.merge!(exists: false) + page_detail[:exists] = current_account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false result << page_detail end end @@ -90,11 +88,12 @@ class Api::V1::CallbacksController < Api::BaseController response = uri.open(redirect: false) rescue OpenURI::HTTPRedirect => e uri = e.uri # assigned from the "Location" response header - retry if (tries -= 1) > 0 + retry if (tries -= 1).positive? raise end pic_url = response.base_uri.to_s rescue StandardError => e + Rails.logger.debug "Rescued: #{e.inspect}" pic_url = nil end pic_url diff --git a/app/controllers/api/v1/canned_responses_controller.rb b/app/controllers/api/v1/accounts/canned_responses_controller.rb similarity index 92% rename from app/controllers/api/v1/canned_responses_controller.rb rename to app/controllers/api/v1/accounts/canned_responses_controller.rb index aa82ea3c4..b76da5f8c 100644 --- a/app/controllers/api/v1/canned_responses_controller.rb +++ b/app/controllers/api/v1/accounts/canned_responses_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::CannedResponsesController < Api::BaseController +class Api::V1::Accounts::CannedResponsesController < Api::BaseController before_action :fetch_canned_response, only: [:update, :destroy] def index diff --git a/app/controllers/api/v1/contacts/conversations_controller.rb b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb similarity index 85% rename from app/controllers/api/v1/contacts/conversations_controller.rb rename to app/controllers/api/v1/accounts/contacts/conversations_controller.rb index bce503ad6..8fcb4df13 100644 --- a/app/controllers/api/v1/contacts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::Contacts::ConversationsController < Api::BaseController +class Api::V1::Accounts::Contacts::ConversationsController < Api::BaseController def index @conversations = current_account.conversations.includes( :assignee, :contact, :inbox diff --git a/app/controllers/api/v1/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb similarity index 93% rename from app/controllers/api/v1/contacts_controller.rb rename to app/controllers/api/v1/accounts/contacts_controller.rb index b5885d708..9d95f69aa 100644 --- a/app/controllers/api/v1/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::ContactsController < Api::BaseController +class Api::V1::Accounts::ContactsController < Api::BaseController protect_from_forgery with: :null_session before_action :check_authorization diff --git a/app/controllers/api/v1/conversations/assignments_controller.rb b/app/controllers/api/v1/accounts/conversations/assignments_controller.rb similarity index 69% rename from app/controllers/api/v1/conversations/assignments_controller.rb rename to app/controllers/api/v1/accounts/conversations/assignments_controller.rb index e411022fe..6da3c05da 100644 --- a/app/controllers/api/v1/conversations/assignments_controller.rb +++ b/app/controllers/api/v1/accounts/conversations/assignments_controller.rb @@ -1,7 +1,8 @@ -class Api::V1::Conversations::AssignmentsController < Api::BaseController +class Api::V1::Accounts::Conversations::AssignmentsController < Api::BaseController before_action :set_conversation, only: [:create] - def create # assign agent to a conversation + # assign agent to a conversation + def create # if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation assignee = current_account.users.find_by(id: params[:assignee_id]) @conversation.update_assignee(assignee) diff --git a/app/controllers/api/v1/conversations/labels_controller.rb b/app/controllers/api/v1/accounts/conversations/labels_controller.rb similarity index 61% rename from app/controllers/api/v1/conversations/labels_controller.rb rename to app/controllers/api/v1/accounts/conversations/labels_controller.rb index e9074ac03..3e80e2825 100644 --- a/app/controllers/api/v1/conversations/labels_controller.rb +++ b/app/controllers/api/v1/accounts/conversations/labels_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::Conversations::LabelsController < Api::BaseController +class Api::V1::Accounts::Conversations::LabelsController < Api::BaseController before_action :set_conversation, only: [:create, :index] def create @@ -6,7 +6,8 @@ class Api::V1::Conversations::LabelsController < Api::BaseController @labels = @conversation.label_list end - def index # all labels of the current conversation + # all labels of the current conversation + def index @labels = @conversation.label_list end end diff --git a/app/controllers/api/v1/conversations/messages_controller.rb b/app/controllers/api/v1/accounts/conversations/messages_controller.rb similarity index 81% rename from app/controllers/api/v1/conversations/messages_controller.rb rename to app/controllers/api/v1/accounts/conversations/messages_controller.rb index 793b3d054..d0ed39edb 100644 --- a/app/controllers/api/v1/conversations/messages_controller.rb +++ b/app/controllers/api/v1/accounts/conversations/messages_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::Conversations::MessagesController < Api::BaseController +class Api::V1::Accounts::Conversations::MessagesController < Api::BaseController before_action :set_conversation, only: [:index, :create] def index diff --git a/app/controllers/api/v1/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb similarity index 82% rename from app/controllers/api/v1/conversations_controller.rb rename to app/controllers/api/v1/accounts/conversations_controller.rb index cf0d55779..895091cca 100644 --- a/app/controllers/api/v1/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -1,5 +1,5 @@ -class Api::V1::ConversationsController < Api::BaseController - before_action :set_conversation, except: [:index] +class Api::V1::Accounts::ConversationsController < Api::BaseController + before_action :conversation, except: [:index] def index result = conversation_finder.perform @@ -25,7 +25,7 @@ class Api::V1::ConversationsController < Api::BaseController DateTime.strptime(params[:agent_last_seen_at].to_s, '%s') end - def set_conversation + def conversation @conversation ||= current_account.conversations.find_by(display_id: params[:id]) end diff --git a/app/controllers/api/v1/facebook_indicators_controller.rb b/app/controllers/api/v1/accounts/facebook_indicators_controller.rb similarity index 89% rename from app/controllers/api/v1/facebook_indicators_controller.rb rename to app/controllers/api/v1/accounts/facebook_indicators_controller.rb index dccf508c9..7cea774cf 100644 --- a/app/controllers/api/v1/facebook_indicators_controller.rb +++ b/app/controllers/api/v1/accounts/facebook_indicators_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::FacebookIndicatorsController < Api::BaseController +class Api::V1::Accounts::FacebookIndicatorsController < Api::BaseController before_action :set_access_token around_action :handle_with_exception @@ -26,6 +26,7 @@ class Api::V1::FacebookIndicatorsController < Api::BaseController def handle_with_exception yield rescue Facebook::Messenger::Error => e + Rails.logger.debug "Rescued: #{e.inspect}" true end diff --git a/app/controllers/api/v1/inbox_members_controller.rb b/app/controllers/api/v1/accounts/inbox_members_controller.rb similarity index 94% rename from app/controllers/api/v1/inbox_members_controller.rb rename to app/controllers/api/v1/accounts/inbox_members_controller.rb index 982ad00ba..f71b3869d 100644 --- a/app/controllers/api/v1/inbox_members_controller.rb +++ b/app/controllers/api/v1/accounts/inbox_members_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::InboxMembersController < Api::BaseController +class Api::V1::Accounts::InboxMembersController < Api::BaseController before_action :fetch_inbox, only: [:create, :show] before_action :current_agents_ids, only: [:create] diff --git a/app/controllers/api/v1/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb similarity index 88% rename from app/controllers/api/v1/inboxes_controller.rb rename to app/controllers/api/v1/accounts/inboxes_controller.rb index e9005a0c7..d99180588 100644 --- a/app/controllers/api/v1/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::InboxesController < Api::BaseController +class Api::V1::Accounts::InboxesController < Api::BaseController before_action :check_authorization before_action :fetch_inbox, only: [:destroy, :update] diff --git a/app/controllers/api/v1/labels_controller.rb b/app/controllers/api/v1/accounts/labels_controller.rb similarity index 58% rename from app/controllers/api/v1/labels_controller.rb rename to app/controllers/api/v1/accounts/labels_controller.rb index 4426b7018..c9f15bdae 100644 --- a/app/controllers/api/v1/labels_controller.rb +++ b/app/controllers/api/v1/accounts/labels_controller.rb @@ -1,5 +1,6 @@ -class Api::V1::LabelsController < Api::BaseController - def index # list all labels in account +class Api::V1::Accounts::LabelsController < Api::BaseController + # list all labels in account + def index @labels = current_account.all_conversation_tags end diff --git a/app/controllers/api/v1/user/notification_settings_controller.rb b/app/controllers/api/v1/accounts/notification_settings_controller.rb similarity index 88% rename from app/controllers/api/v1/user/notification_settings_controller.rb rename to app/controllers/api/v1/accounts/notification_settings_controller.rb index 78c5dc720..ba6e43804 100644 --- a/app/controllers/api/v1/user/notification_settings_controller.rb +++ b/app/controllers/api/v1/accounts/notification_settings_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::User::NotificationSettingsController < Api::BaseController +class Api::V1::Accounts::NotificationSettingsController < Api::BaseController before_action :set_user, :load_notification_setting def show; end diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/accounts/reports_controller.rb similarity index 82% rename from app/controllers/api/v1/reports_controller.rb rename to app/controllers/api/v1/accounts/reports_controller.rb index e155d3af7..c93574b6c 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/accounts/reports_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::ReportsController < Api::BaseController +class Api::V1::Accounts::ReportsController < Api::BaseController include CustomExceptions::Report include Constants::Report @@ -36,10 +36,6 @@ class Api::V1::ReportsController < Api::BaseController current_user.account end - def agent - @agent ||= current_account.users.find(params[:agent_id]) - end - def account_summary_metrics summary_metrics(ACCOUNT_METRICS, :account_summary_params, AVG_ACCOUNT_METRICS) end @@ -51,18 +47,18 @@ class Api::V1::ReportsController < Api::BaseController def summary_metrics(metrics, calc_function, avg_metrics) metrics.each_with_object({}) do |metric, result| data = ReportBuilder.new(current_account, send(calc_function, metric)).build - - if avg_metrics.include?(metric) - sum = data.inject(0) { |sum, hash| sum + hash[:value].to_i } - sum /= data.length unless sum.zero? - else - sum = data.inject(0) { |sum, hash| sum + hash[:value].to_i } - end - - result[metric] = sum + result[metric] = calculate_metric(data, metric, avg_metrics) end end + def calculate_metric(data, metric, avg_metrics) + sum = data.inject(0) { |val, hash| val + hash[:value].to_i } + if avg_metrics.include?(metric) + sum /= data.length unless sum.zero? + end + sum + end + def account_summary_params(metric) { metric: metric.to_s, diff --git a/app/controllers/api/v1/subscriptions_controller.rb b/app/controllers/api/v1/accounts/subscriptions_controller.rb similarity index 76% rename from app/controllers/api/v1/subscriptions_controller.rb rename to app/controllers/api/v1/accounts/subscriptions_controller.rb index 92e4f7f13..f9b3141d6 100644 --- a/app/controllers/api/v1/subscriptions_controller.rb +++ b/app/controllers/api/v1/accounts/subscriptions_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::SubscriptionsController < Api::BaseController +class Api::V1::Accounts::SubscriptionsController < Api::BaseController skip_before_action :check_subscription before_action :check_billing_enabled diff --git a/app/controllers/api/v1/account/webhooks_controller.rb b/app/controllers/api/v1/accounts/webhooks_controller.rb similarity index 90% rename from app/controllers/api/v1/account/webhooks_controller.rb rename to app/controllers/api/v1/accounts/webhooks_controller.rb index 730e7b9b1..dbdd953ed 100644 --- a/app/controllers/api/v1/account/webhooks_controller.rb +++ b/app/controllers/api/v1/accounts/webhooks_controller.rb @@ -1,4 +1,4 @@ -class Api::V1::Account::WebhooksController < Api::BaseController +class Api::V1::Accounts::WebhooksController < Api::BaseController before_action :check_authorization before_action :fetch_webhook, only: [:update, :destroy] diff --git a/app/controllers/devise_overrides/passwords_controller.rb b/app/controllers/devise_overrides/passwords_controller.rb index adea8687d..4289d5af2 100644 --- a/app/controllers/devise_overrides/passwords_controller.rb +++ b/app/controllers/devise_overrides/passwords_controller.rb @@ -11,9 +11,7 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController @recoverable = User.find_by(reset_password_token: reset_password_token) if @recoverable && reset_password_and_confirmation(@recoverable) send_auth_headers(@recoverable) - render json: { - data: @recoverable.token_validation_response - } + render 'devise/auth.json', locals: { resource: @recoverable } else render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422 end diff --git a/app/controllers/devise_overrides/sessions_controller.rb b/app/controllers/devise_overrides/sessions_controller.rb index 3a6614074..b9cec5447 100644 --- a/app/controllers/devise_overrides/sessions_controller.rb +++ b/app/controllers/devise_overrides/sessions_controller.rb @@ -4,6 +4,6 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle wrap_parameters format: [] def render_create_success - render 'devise/auth.json' + render 'devise/auth.json', locals: { resource: @resource } end end diff --git a/app/javascript/dashboard/api/ApiClient.js b/app/javascript/dashboard/api/ApiClient.js index a3b87dae1..0b2ed28d0 100644 --- a/app/javascript/dashboard/api/ApiClient.js +++ b/app/javascript/dashboard/api/ApiClient.js @@ -3,9 +3,25 @@ const API_VERSION = `/api/v1`; class ApiClient { - constructor(url) { + constructor(resource, options = {}) { this.apiVersion = API_VERSION; - this.url = `${this.apiVersion}/${url}`; + this.options = options; + this.resource = resource; + } + + get url() { + let url = this.apiVersion; + if (this.options.accountScoped) { + const isInsideAccountScopedURLs = window.location.pathname.includes( + '/app/accounts' + ); + + if (isInsideAccountScopedURLs) { + const accountId = window.location.pathname.split('/')[3]; + url = `${url}/accounts/${accountId}`; + } + } + return `${url}/${this.resource}`; } get() { diff --git a/app/javascript/dashboard/api/agents.js b/app/javascript/dashboard/api/agents.js index 62d8e6623..7cc5e6d0c 100644 --- a/app/javascript/dashboard/api/agents.js +++ b/app/javascript/dashboard/api/agents.js @@ -2,7 +2,7 @@ import ApiClient from './ApiClient'; class Agents extends ApiClient { constructor() { - super('agents'); + super('agents', { accountScoped: true }); } } diff --git a/app/javascript/dashboard/api/cannedResponse.js b/app/javascript/dashboard/api/cannedResponse.js index 3a17d2735..f558dcaca 100644 --- a/app/javascript/dashboard/api/cannedResponse.js +++ b/app/javascript/dashboard/api/cannedResponse.js @@ -4,7 +4,7 @@ import ApiClient from './ApiClient'; class CannedResponse extends ApiClient { constructor() { - super('canned_responses'); + super('canned_responses', { accountScoped: true }); } get({ searchKey }) { diff --git a/app/javascript/dashboard/api/contacts.js b/app/javascript/dashboard/api/contacts.js index bad89b182..0988141d3 100644 --- a/app/javascript/dashboard/api/contacts.js +++ b/app/javascript/dashboard/api/contacts.js @@ -3,7 +3,7 @@ import ApiClient from './ApiClient'; class ContactAPI extends ApiClient { constructor() { - super('contacts'); + super('contacts', { accountScoped: true }); } getConversations(contactId) { diff --git a/app/javascript/dashboard/api/conversations.js b/app/javascript/dashboard/api/conversations.js index fd36f8db3..876103694 100644 --- a/app/javascript/dashboard/api/conversations.js +++ b/app/javascript/dashboard/api/conversations.js @@ -3,7 +3,7 @@ import ApiClient from './ApiClient'; class ConversationApi extends ApiClient { constructor() { - super('conversations'); + super('conversations', { accountScoped: true }); } getLabels(conversationID) { diff --git a/app/javascript/dashboard/api/endPoints.js b/app/javascript/dashboard/api/endPoints.js index 10a0608bd..e5640af2b 100644 --- a/app/javascript/dashboard/api/endPoints.js +++ b/app/javascript/dashboard/api/endPoints.js @@ -28,23 +28,10 @@ const endPoints = { }, fetchFacebookPages: { - url: 'api/v1/callbacks/get_facebook_pages.json', + url: 'api/v1/callbacks/facebook_pages.json', params: { omniauth_token: '' }, }, - reports: { - account(metric, from, to) { - return { - url: `/api/v1/reports/account?metric=${metric}&since=${from}&to=${to}`, - }; - }, - accountSummary(accountId, from, to) { - return { - url: `/api/v1/reports/${accountId}/account_summary?since=${from}&to=${to}`, - }; - }, - }, - subscriptions: { get() { return { diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js index 6a86cff7f..d5212957a 100644 --- a/app/javascript/dashboard/api/inbox/conversation.js +++ b/app/javascript/dashboard/api/inbox/conversation.js @@ -3,7 +3,7 @@ import ApiClient from '../ApiClient'; class ConversationApi extends ApiClient { constructor() { - super('conversations'); + super('conversations', { accountScoped: true }); } get({ inboxId, status, assigneeType, page }) { diff --git a/app/javascript/dashboard/api/inbox/message.js b/app/javascript/dashboard/api/inbox/message.js index 00dda8a63..3cea5f7a8 100644 --- a/app/javascript/dashboard/api/inbox/message.js +++ b/app/javascript/dashboard/api/inbox/message.js @@ -4,7 +4,7 @@ import ApiClient from '../ApiClient'; class MessageApi extends ApiClient { constructor() { - super('conversations'); + super('conversations', { accountScoped: true }); } create({ conversationId, message, private: isPrivate }) { diff --git a/app/javascript/dashboard/api/inboxMembers.js b/app/javascript/dashboard/api/inboxMembers.js index 2d7001562..3716f89ab 100644 --- a/app/javascript/dashboard/api/inboxMembers.js +++ b/app/javascript/dashboard/api/inboxMembers.js @@ -3,7 +3,7 @@ import ApiClient from './ApiClient'; class InboxMembers extends ApiClient { constructor() { - super('inbox_members'); + super('inbox_members', { accountScoped: true }); } create({ inboxId, agentList }) { diff --git a/app/javascript/dashboard/api/inboxes.js b/app/javascript/dashboard/api/inboxes.js index fb3e63dfd..b5cea1d01 100644 --- a/app/javascript/dashboard/api/inboxes.js +++ b/app/javascript/dashboard/api/inboxes.js @@ -2,7 +2,7 @@ import ApiClient from './ApiClient'; class Inboxes extends ApiClient { constructor() { - super('inboxes'); + super('inboxes', { accountScoped: true }); } } diff --git a/app/javascript/dashboard/api/reports.js b/app/javascript/dashboard/api/reports.js index 94d6ac726..d2a96cda8 100644 --- a/app/javascript/dashboard/api/reports.js +++ b/app/javascript/dashboard/api/reports.js @@ -1,14 +1,22 @@ /* global axios */ +import ApiClient from './ApiClient'; -import endPoints from './endPoints'; +class ReportsAPI extends ApiClient { + constructor() { + super('reports', { accountScoped: true }); + } -export default { - getAccountReports(metric, from, to) { - const { url } = endPoints('reports').account(metric, from, to); - return axios.get(url); - }, - getAccountSummary(accountId, from, to) { - const urlData = endPoints('reports').accountSummary(accountId, from, to); - return axios.get(urlData.url); - }, -}; + getAccountReports(metric, since, until) { + return axios.get(`${this.url}/account`, { + params: { metric, since, until }, + }); + } + + getAccountSummary(accountId, since, until) { + return axios.get(`${this.url}/${accountId}/account_summary`, { + params: { since, until }, + }); + } +} + +export default new ReportsAPI(); diff --git a/app/javascript/dashboard/api/userNotificationSettings.js b/app/javascript/dashboard/api/userNotificationSettings.js index 15cea8942..33829a6bd 100644 --- a/app/javascript/dashboard/api/userNotificationSettings.js +++ b/app/javascript/dashboard/api/userNotificationSettings.js @@ -3,7 +3,7 @@ import ApiClient from './ApiClient'; class UserNotificationSettings extends ApiClient { constructor() { - super('user/notification_settings'); + super('notification_settings', { accountScoped: true }); } update(params) { diff --git a/app/javascript/dashboard/api/webhooks.js b/app/javascript/dashboard/api/webhooks.js index 229519dd7..1e03f25f7 100644 --- a/app/javascript/dashboard/api/webhooks.js +++ b/app/javascript/dashboard/api/webhooks.js @@ -2,7 +2,7 @@ import ApiClient from './ApiClient'; class WebHooks extends ApiClient { constructor() { - super('account/webhooks'); + super('webhooks', { accountScoped: true }); } } diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue index 52838fa3f..10c395818 100644 --- a/app/javascript/dashboard/components/layout/Sidebar.vue +++ b/app/javascript/dashboard/components/layout/Sidebar.vue @@ -43,7 +43,7 @@ >