mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	chore: Move agent availability to Account level (#3074)
- Move agent availability to the account level
This commit is contained in:
		| @@ -9,21 +9,18 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController | ||||
|     @agents = agents | ||||
|   end | ||||
|  | ||||
|   def create; end | ||||
|  | ||||
|   def update | ||||
|     @agent.update!(agent_params.slice(:name).compact) | ||||
|     @agent.current_account_user.update!(agent_params.slice(:role, :availability, :auto_offline).compact) | ||||
|   end | ||||
|  | ||||
|   def destroy | ||||
|     @agent.current_account_user.destroy | ||||
|     head :ok | ||||
|   end | ||||
|  | ||||
|   def update | ||||
|     @agent.update!(agent_params.except(:role)) | ||||
|     @agent.current_account_user.update!(role: agent_params[:role]) if agent_params[:role] | ||||
|     render partial: 'api/v1/models/agent.json.jbuilder', locals: { resource: @agent } | ||||
|   end | ||||
|  | ||||
|   def create | ||||
|     render partial: 'api/v1/models/agent.json.jbuilder', locals: { resource: @user } | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def check_authorization | ||||
| @@ -47,22 +44,25 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController | ||||
|   end | ||||
|  | ||||
|   def save_account_user | ||||
|     AccountUser.create!( | ||||
|     AccountUser.create!({ | ||||
|       account_id: Current.account.id, | ||||
|       user_id: @user.id, | ||||
|       role: new_agent_params[:role], | ||||
|       inviter_id: current_user.id | ||||
|     ) | ||||
|     }.merge({ | ||||
|       role: new_agent_params[:role], | ||||
|       availability: new_agent_params[:availability], | ||||
|       auto_offline: new_agent_params[:auto_offline] | ||||
|     }.compact)) | ||||
|   end | ||||
|  | ||||
|   def agent_params | ||||
|     params.require(:agent).permit(:email, :name, :role) | ||||
|     params.require(:agent).permit(:name, :email, :name, :role, :availability, :auto_offline) | ||||
|   end | ||||
|  | ||||
|   def new_agent_params | ||||
|     # intial string ensures the password requirements are met | ||||
|     temp_password = "1!aA#{SecureRandom.alphanumeric(12)}" | ||||
|     params.require(:agent).permit(:email, :name, :role) | ||||
|     params.require(:agent).permit(:email, :name, :role, :availability, :auto_offline) | ||||
|           .merge!(password: temp_password, password_confirmation: temp_password, inviter: current_user) | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| class Api::V1::ProfilesController < Api::BaseController | ||||
|   before_action :set_user | ||||
|  | ||||
|   def show | ||||
|     render partial: 'api/v1/models/user.json.jbuilder', locals: { resource: @user } | ||||
|   end | ||||
|   def show; end | ||||
|  | ||||
|   def update | ||||
|     if password_params[:password].present? | ||||
| @@ -15,19 +13,26 @@ class Api::V1::ProfilesController < Api::BaseController | ||||
|     @user.update!(profile_params) | ||||
|   end | ||||
|  | ||||
|   def availability | ||||
|     @user.account_users.find_by!(account_id: availability_params[:account_id]).update!(availability: availability_params[:availability]) | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def set_user | ||||
|     @user = current_user | ||||
|   end | ||||
|  | ||||
|   def availability_params | ||||
|     params.require(:profile).permit(:account_id, :availability) | ||||
|   end | ||||
|  | ||||
|   def profile_params | ||||
|     params.require(:profile).permit( | ||||
|       :email, | ||||
|       :name, | ||||
|       :display_name, | ||||
|       :avatar, | ||||
|       :availability, | ||||
|       ui_settings: {} | ||||
|     ) | ||||
|   end | ||||
|   | ||||
| @@ -161,9 +161,9 @@ export default { | ||||
|     }); | ||||
|   }, | ||||
|  | ||||
|   updateAvailability({ availability }) { | ||||
|     return axios.put(endPoints('profileUpdate').url, { | ||||
|       profile: { availability }, | ||||
|   updateAvailability(availabilityData) { | ||||
|     return axios.post(endPoints('availabilityUpdate').url, { | ||||
|       profile: { ...availabilityData }, | ||||
|     }); | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -13,6 +13,9 @@ const endPoints = { | ||||
|   profileUpdate: { | ||||
|     url: '/api/v1/profile', | ||||
|   }, | ||||
|   availabilityUpdate: { | ||||
|     url: '/api/v1/profile/availability', | ||||
|   }, | ||||
|   logout: { | ||||
|     url: 'auth/sign_out', | ||||
|   }, | ||||
|   | ||||
| @@ -26,7 +26,9 @@ | ||||
|                 color-scheme="secondary" | ||||
|                 class-names="status-change--dropdown-button" | ||||
|                 :is-disabled="status.disabled" | ||||
|                 @click="changeAvailabilityStatus(status.value)" | ||||
|                 @click=" | ||||
|                   changeAvailabilityStatus(status.value, currentAccountId) | ||||
|                 " | ||||
|               > | ||||
|                 <availability-status-badge :status="status.value" /> | ||||
|                 {{ status.label }} | ||||
| @@ -75,7 +77,8 @@ export default { | ||||
|  | ||||
|   computed: { | ||||
|     ...mapGetters({ | ||||
|       currentUser: 'getCurrentUser', | ||||
|       getCurrentUserAvailabilityStatus: 'getCurrentUserAvailabilityStatus', | ||||
|       getCurrentAccountId: 'getCurrentAccountId', | ||||
|     }), | ||||
|     availabilityDisplayLabel() { | ||||
|       const availabilityIndex = AVAILABILITY_STATUS_KEYS.findIndex( | ||||
| @@ -85,8 +88,11 @@ export default { | ||||
|         availabilityIndex | ||||
|       ]; | ||||
|     }, | ||||
|     currentAccountId() { | ||||
|       return this.getCurrentAccountId; | ||||
|     }, | ||||
|     currentUserAvailabilityStatus() { | ||||
|       return this.currentUser.availability_status; | ||||
|       return this.getCurrentUserAvailabilityStatus; | ||||
|     }, | ||||
|     availabilityStatuses() { | ||||
|       return this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUSES_LIST').map( | ||||
| @@ -108,16 +114,16 @@ export default { | ||||
|     closeStatusMenu() { | ||||
|       this.isStatusMenuOpened = false; | ||||
|     }, | ||||
|     changeAvailabilityStatus(availability) { | ||||
|     changeAvailabilityStatus(availability, accountId) { | ||||
|       if (this.isUpdating) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       this.isUpdating = true; | ||||
|  | ||||
|       this.$store | ||||
|         .dispatch('updateAvailability', { | ||||
|           availability, | ||||
|           availability: availability, | ||||
|           account_id: accountId, | ||||
|         }) | ||||
|         .finally(() => { | ||||
|           this.isUpdating = false; | ||||
|   | ||||
| @@ -17,7 +17,8 @@ const i18nConfig = new VueI18n({ | ||||
| }); | ||||
|  | ||||
| describe('AvailabilityStatus', () => { | ||||
|   const currentUser = { availability_status: 'online' }; | ||||
|   const currentAvailabilityStatus =  'online' ; | ||||
|   const currentAccountId = '1'; | ||||
|   let store = null; | ||||
|   let actions = null; | ||||
|   let modules = null; | ||||
| @@ -33,7 +34,8 @@ describe('AvailabilityStatus', () => { | ||||
|     modules = { | ||||
|       auth: { | ||||
|         getters: { | ||||
|           getCurrentUser: () => currentUser, | ||||
|           getCurrentUserAvailabilityStatus: () => currentAvailabilityStatus, | ||||
|           getCurrentAccountId: () => currentAccountId, | ||||
|         }, | ||||
|       }, | ||||
|     }; | ||||
| @@ -77,7 +79,7 @@ describe('AvailabilityStatus', () => { | ||||
|  | ||||
|     expect(actions.updateAvailability).toBeCalledWith( | ||||
|       expect.any(Object), | ||||
|       { availability: 'offline' }, | ||||
|       { availability: 'offline', account_id: currentAccountId }, | ||||
|       undefined | ||||
|     ); | ||||
|   }); | ||||
|   | ||||
| @@ -40,7 +40,11 @@ export const getters = { | ||||
|   }, | ||||
|  | ||||
|   getCurrentUserAvailabilityStatus(_state) { | ||||
|     return _state.currentUser.availability_status; | ||||
|     const { accounts = [] } = _state.currentUser; | ||||
|     const [currentAccount = {}] = accounts.filter( | ||||
|       account => account.id === _state.currentAccountId | ||||
|     ); | ||||
|     return currentAccount.availability_status; | ||||
|   }, | ||||
|  | ||||
|   getCurrentAccountId(_state) { | ||||
| @@ -125,14 +129,17 @@ export const actions = { | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   updateAvailability: ({ commit, dispatch }, { availability }) => { | ||||
|     authAPI.updateAvailability({ availability }).then(response => { | ||||
|   updateAvailability: async ({ commit, dispatch }, params) => { | ||||
|     try { | ||||
|       const response = await authAPI.updateAvailability(params); | ||||
|       const userData = response.data; | ||||
|       const { id, availability_status: availabilityStatus } = userData; | ||||
|       const { id } = userData; | ||||
|       setUser(userData, getHeaderExpiry(response)); | ||||
|       commit(types.default.SET_CURRENT_USER); | ||||
|       dispatch('agents/updatePresence', { [id]: availabilityStatus }); | ||||
|     }); | ||||
|       dispatch('agents/updatePresence', { [id]: params.availability }); | ||||
|     } catch (error) { | ||||
|       // Ignore error | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   setCurrentAccountId({ commit }, accountId) { | ||||
|   | ||||
| @@ -54,13 +54,16 @@ describe('#actions', () => { | ||||
|  | ||||
|   describe('#updateAvailability', () => { | ||||
|     it('sends correct actions if API is success', async () => { | ||||
|       axios.put.mockResolvedValue({ | ||||
|         data: { id: 1, name: 'John', availability_status: 'offline' }, | ||||
|       axios.post.mockResolvedValue({ | ||||
|         data: { | ||||
|           id: 1, | ||||
|           account_users: [{ account_id: 1, availability_status: 'offline' }], | ||||
|         }, | ||||
|         headers: { expiry: 581842904 }, | ||||
|       }); | ||||
|       await actions.updateAvailability( | ||||
|         { commit, dispatch }, | ||||
|         { availability: 'offline' } | ||||
|         { availability: 'offline', account_id: 1 }, | ||||
|       ); | ||||
|       expect(setUser).toHaveBeenCalledTimes(1); | ||||
|       expect(commit.mock.calls).toEqual([[types.default.SET_CURRENT_USER]]); | ||||
|   | ||||
| @@ -21,7 +21,11 @@ describe('#getters', () => { | ||||
|   it('get', () => { | ||||
|     expect( | ||||
|       getters.getCurrentUserAvailabilityStatus({ | ||||
|         currentUser: { id: 1, name: 'Pranav', availability_status: 'busy' }, | ||||
|         currentAccountId: 1, | ||||
|         currentUser: { | ||||
|           id: 1, | ||||
|           accounts: [{ id: 1, availability_status: 'busy' }], | ||||
|         }, | ||||
|       }) | ||||
|     ).toEqual('busy'); | ||||
|   }); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { createConsumer } from '@rails/actioncable'; | ||||
|  | ||||
| const PRESENCE_INTERVAL = 60000; | ||||
| const PRESENCE_INTERVAL = 20000; | ||||
|  | ||||
| class BaseActionCableConnector { | ||||
|   constructor(app, pubsubToken, websocketHost = '') { | ||||
|   | ||||
| @@ -2,14 +2,16 @@ | ||||
| # | ||||
| # Table name: account_users | ||||
| # | ||||
| #  id         :bigint           not null, primary key | ||||
| #  active_at  :datetime | ||||
| #  role       :integer          default("agent") | ||||
| #  created_at :datetime         not null | ||||
| #  updated_at :datetime         not null | ||||
| #  account_id :bigint | ||||
| #  inviter_id :bigint | ||||
| #  user_id    :bigint | ||||
| #  id           :bigint           not null, primary key | ||||
| #  active_at    :datetime | ||||
| #  auto_offline :boolean          default(TRUE), not null | ||||
| #  availability :integer          default("online"), not null | ||||
| #  role         :integer          default("agent") | ||||
| #  created_at   :datetime         not null | ||||
| #  updated_at   :datetime         not null | ||||
| #  account_id   :bigint | ||||
| #  inviter_id   :bigint | ||||
| #  user_id      :bigint | ||||
| # | ||||
| # Indexes | ||||
| # | ||||
| @@ -24,15 +26,20 @@ | ||||
| # | ||||
|  | ||||
| class AccountUser < ApplicationRecord | ||||
|   include AvailabilityStatusable | ||||
|  | ||||
|   belongs_to :account | ||||
|   belongs_to :user | ||||
|   belongs_to :inviter, class_name: 'User', optional: true | ||||
|  | ||||
|   enum role: { agent: 0, administrator: 1 } | ||||
|   enum availability: { online: 0, offline: 1, busy: 2 } | ||||
|  | ||||
|   accepts_nested_attributes_for :account | ||||
|  | ||||
|   after_create_commit :notify_creation, :create_notification_setting | ||||
|   after_destroy :notify_deletion, :remove_user_from_account | ||||
|   after_save :update_presence_in_redis, if: :saved_change_to_availability? | ||||
|  | ||||
|   validates :user_id, uniqueness: { scope: :account_id } | ||||
|  | ||||
| @@ -56,4 +63,8 @@ class AccountUser < ApplicationRecord | ||||
|   def notify_deletion | ||||
|     Rails.configuration.dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: account) | ||||
|   end | ||||
|  | ||||
|   def update_presence_in_redis | ||||
|     OnlineStatusTracker.set_status(account.id, user.id, availability) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -2,29 +2,29 @@ module AvailabilityStatusable | ||||
|   extend ActiveSupport::Concern | ||||
|  | ||||
|   def online_presence? | ||||
|     return if user_profile_page_context? | ||||
|  | ||||
|     ::OnlineStatusTracker.get_presence(availability_account_id, self.class.name, id) | ||||
|     obj_id = is_a?(Contact) ? id : user_id | ||||
|     ::OnlineStatusTracker.get_presence(account_id, self.class.name, obj_id) | ||||
|   end | ||||
|  | ||||
|   def availability_status | ||||
|     return availability if user_profile_page_context? | ||||
|     return 'offline' unless online_presence? | ||||
|     return 'online' if is_a? Contact | ||||
|  | ||||
|     ::OnlineStatusTracker.get_status(availability_account_id, id) || 'online' | ||||
|     if is_a? Contact | ||||
|       contact_availability_status | ||||
|     else | ||||
|       user_availability_status | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def user_profile_page_context? | ||||
|     # at the moment profile pages aren't account scoped | ||||
|     # hence we will return availability attribute instead of true presence | ||||
|     # we will revisit this later | ||||
|     is_a?(User) && Current.account.blank? | ||||
|   private | ||||
|  | ||||
|   def contact_availability_status | ||||
|     online_presence? ? 'online' : 'offline' | ||||
|   end | ||||
|  | ||||
|   def availability_account_id | ||||
|     return account_id if is_a? Contact | ||||
|   def user_availability_status | ||||
|     # we are not considering presence in this case. Just returns the availability | ||||
|     return availability unless auto_offline | ||||
|  | ||||
|     Current.account.id | ||||
|     # availability as a fallback in case the status is not present in redis | ||||
|     online_presence? ? (::OnlineStatusTracker.get_status(account_id, user_id) || availability) : 'offline' | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -39,7 +39,6 @@ | ||||
|  | ||||
| class User < ApplicationRecord | ||||
|   include AccessTokenable | ||||
|   include AvailabilityStatusable | ||||
|   include Avatarable | ||||
|   # Include default devise modules. | ||||
|   include DeviseTokenAuth::Concerns::User | ||||
| @@ -57,6 +56,8 @@ class User < ApplicationRecord | ||||
|          :confirmable, | ||||
|          :password_has_required_content | ||||
|  | ||||
|   # TODO: remove in a future version once online status is moved to account users | ||||
|   # remove the column availability from users | ||||
|   enum availability: { online: 0, offline: 1, busy: 2 } | ||||
|  | ||||
|   # The validation below has been commented out as it does not | ||||
| @@ -89,8 +90,6 @@ class User < ApplicationRecord | ||||
|  | ||||
|   before_validation :set_password_and_uid, on: :create | ||||
|  | ||||
|   after_save :update_presence_in_redis, if: :saved_change_to_availability? | ||||
|  | ||||
|   scope :order_by_full_name, -> { order('lower(name) ASC') } | ||||
|  | ||||
|   def send_devise_notification(notification, *args) | ||||
| @@ -141,6 +140,14 @@ class User < ApplicationRecord | ||||
|     current_account_user&.role | ||||
|   end | ||||
|  | ||||
|   def availability_status | ||||
|     current_account_user&.availability_status | ||||
|   end | ||||
|  | ||||
|   def auto_offline | ||||
|     current_account_user&.auto_offline | ||||
|   end | ||||
|  | ||||
|   def inviter | ||||
|     current_account_user&.inviter | ||||
|   end | ||||
| @@ -169,12 +176,4 @@ class User < ApplicationRecord | ||||
|       type: 'user' | ||||
|     } | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def update_presence_in_redis | ||||
|     accounts.each do |account| | ||||
|       OnlineStatusTracker.set_status(account.id, id, availability) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										1
									
								
								app/views/api/v1/accounts/agents/create.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/views/api/v1/accounts/agents/create.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| json.partial! 'api/v1/models/agent.json.jbuilder', resource: @user | ||||
							
								
								
									
										1
									
								
								app/views/api/v1/accounts/agents/update.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/views/api/v1/accounts/agents/update.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| json.partial! 'api/v1/models/agent.json.jbuilder', resource: @agent | ||||
| @@ -1,10 +1,11 @@ | ||||
| json.id resource.id | ||||
| # could be nil for a deleted agent hence the safe operator before account id | ||||
| json.account_id resource.account&.id | ||||
| json.availability_status resource.availability_status | ||||
| json.auto_offline resource.auto_offline | ||||
| json.confirmed resource.confirmed? | ||||
| json.email resource.email | ||||
| json.available_name resource.available_name | ||||
| json.id resource.id | ||||
| json.custom_attributes resource.custom_attributes if resource.custom_attributes.present? | ||||
| json.name resource.name | ||||
| json.role resource.role | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| json.access_token resource.access_token.token | ||||
| json.account_id resource.active_account_user&.account_id | ||||
| json.availability_status resource.availability_status | ||||
| json.available_name resource.available_name | ||||
| json.avatar_url resource.avatar_url | ||||
| json.confirmed resource.confirmed? | ||||
| @@ -22,5 +21,7 @@ json.accounts do | ||||
|     json.name account_user.account.name | ||||
|     json.active_at account_user.active_at | ||||
|     json.role account_user.role | ||||
|     json.availability_status account_user.availability_status | ||||
|     json.auto_offline account_user.auto_offline | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										1
									
								
								app/views/api/v1/profiles/availability.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/views/api/v1/profiles/availability.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| json.partial! 'api/v1/models/user.json.jbuilder', resource: @user | ||||
							
								
								
									
										1
									
								
								app/views/api/v1/profiles/show.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/views/api/v1/profiles/show.json.jbuilder
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| json.partial! 'api/v1/models/user.json.jbuilder', resource: @user | ||||
| @@ -3,6 +3,6 @@ json.payload do | ||||
|     json.id inbox_member.user.id | ||||
|     json.name inbox_member.user.available_name | ||||
|     json.avatar_url inbox_member.user.avatar_url | ||||
|     json.availability_status inbox_member.user.availability_status | ||||
|     json.availability_status inbox_member.user.account_users.find_by(account_id: @current_account.id).availability_status | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| json.access_token resource.access_token.token | ||||
| json.account_id resource.active_account_user&.account_id | ||||
| json.availability_status resource.availability_status | ||||
| json.available_name resource.available_name | ||||
| json.avatar_url resource.avatar_url | ||||
| json.confirmed resource.confirmed? | ||||
|   | ||||
| @@ -40,7 +40,7 @@ Rails.application.routes.draw do | ||||
|             resource :contact_merge, only: [:create] | ||||
|           end | ||||
|  | ||||
|           resources :agents, except: [:show, :edit, :new] | ||||
|           resources :agents, only: [:index, :create, :update, :destroy] | ||||
|           resources :agent_bots, only: [:index, :create, :show, :update, :destroy] | ||||
|  | ||||
|           resources :callbacks, only: [] do | ||||
| @@ -159,7 +159,11 @@ Rails.application.routes.draw do | ||||
|         resources :webhooks, only: [:create] | ||||
|       end | ||||
|  | ||||
|       resource :profile, only: [:show, :update] | ||||
|       resource :profile, only: [:show, :update] do | ||||
|         member do | ||||
|           post :availability | ||||
|         end | ||||
|       end | ||||
|       resource :notification_subscriptions, only: [:create] | ||||
|  | ||||
|       namespace :widget do | ||||
|   | ||||
| @@ -0,0 +1,21 @@ | ||||
| class AddOnlineStatusToAccountUsers < ActiveRecord::Migration[6.1] | ||||
|   def change | ||||
|     change_table :account_users, bulk: true do |t| | ||||
|       t.integer :availability, default: 0, null: false | ||||
|       t.boolean :auto_offline, default: true, null: false | ||||
|     end | ||||
|  | ||||
|     update_existing_user_availability | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def update_existing_user_availability | ||||
|     User.find_in_batches do |user_batch| | ||||
|       user_batch.each do |user| | ||||
|         availability = user.availability | ||||
|         user.account_users.update(availability: availability) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @@ -35,6 +35,8 @@ ActiveRecord::Schema.define(version: 2021_09_29_150415) do | ||||
|     t.datetime "created_at", precision: 6, null: false | ||||
|     t.datetime "updated_at", precision: 6, null: false | ||||
|     t.datetime "active_at" | ||||
|     t.integer "availability", default: 0, null: false | ||||
|     t.boolean "auto_offline", default: true, null: false | ||||
|     t.index ["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" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| module OnlineStatusTracker | ||||
|   PRESENCE_DURATION = 60.seconds | ||||
|   PRESENCE_DURATION = 20.seconds | ||||
|  | ||||
|   # presence : sorted set with timestamp as the score & object id as value | ||||
|  | ||||
|   | ||||
| @@ -94,7 +94,7 @@ RSpec.describe 'Agents API', type: :request do | ||||
|         expect(response).to have_http_status(:unauthorized) | ||||
|       end | ||||
|  | ||||
|       it 'modifies an agent' do | ||||
|       it 'modifies an agent name' do | ||||
|         put "/api/v1/accounts/#{account.id}/agents/#{other_agent.id}", | ||||
|             params: params, | ||||
|             headers: admin.create_new_auth_token, | ||||
| @@ -103,6 +103,20 @@ RSpec.describe 'Agents API', type: :request do | ||||
|         expect(response).to have_http_status(:success) | ||||
|         expect(other_agent.reload.name).to eq(params[:name]) | ||||
|       end | ||||
|  | ||||
|       it 'modifies an agents account user attributes' do | ||||
|         put "/api/v1/accounts/#{account.id}/agents/#{other_agent.id}", | ||||
|             params: { role: 'administrator', availability: 'busy', auto_offline: false }, | ||||
|             headers: admin.create_new_auth_token, | ||||
|             as: :json | ||||
|  | ||||
|         expect(response).to have_http_status(:success) | ||||
|         response_data = JSON.parse(response.body) | ||||
|         expect(response_data['role']).to eq('administrator') | ||||
|         expect(response_data['availability_status']).to eq('busy') | ||||
|         expect(response_data['auto_offline']).to eq(false) | ||||
|         expect(other_agent.account_users.first.role).to eq('administrator') | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -89,16 +89,6 @@ RSpec.describe 'Profile API', type: :request do | ||||
|         expect(agent.avatar.attached?).to eq(true) | ||||
|       end | ||||
|  | ||||
|       it 'updates the availability status' do | ||||
|         put '/api/v1/profile', | ||||
|             params: { profile: { availability: 'offline' } }, | ||||
|             headers: agent.create_new_auth_token, | ||||
|             as: :json | ||||
|  | ||||
|         expect(response).to have_http_status(:success) | ||||
|         expect(::OnlineStatusTracker.get_status(account.id, agent.id)).to eq('offline') | ||||
|       end | ||||
|  | ||||
|       it 'updates the ui settings' do | ||||
|         put '/api/v1/profile', | ||||
|             params: { profile: { ui_settings: { is_contact_sidebar_open: false } } }, | ||||
| @@ -111,4 +101,28 @@ RSpec.describe 'Profile API', type: :request do | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe 'POST /api/v1/profile/availability' do | ||||
|     context 'when it is an unauthenticated user' do | ||||
|       it 'returns unauthorized' do | ||||
|         post '/api/v1/profile/availability' | ||||
|  | ||||
|         expect(response).to have_http_status(:unauthorized) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when it is an authenticated user' do | ||||
|       let(:agent) { create(:user, password: 'Test123!', account: account, role: :agent) } | ||||
|  | ||||
|       it 'updates the availability status' do | ||||
|         post '/api/v1/profile/availability', | ||||
|              params: { profile: { availability: 'busy', account_id: account.id } }, | ||||
|              headers: agent.create_new_auth_token, | ||||
|              as: :json | ||||
|  | ||||
|         expect(response).to have_http_status(:success) | ||||
|         expect(::OnlineStatusTracker.get_status(account.id, agent.id)).to eq('busy') | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| type: object | ||||
| properties: | ||||
|   id: | ||||
|     type: number | ||||
|     type: integer | ||||
|   uid: | ||||
|     type: string | ||||
|   name: | ||||
| @@ -13,12 +13,19 @@ properties: | ||||
|   email: | ||||
|     type: string | ||||
|   account_id: | ||||
|     type: number | ||||
|     type: integer | ||||
|   role: | ||||
|     type: string | ||||
|     enum: ['agent', 'administrator'] | ||||
|   confirmed: | ||||
|     type: boolean | ||||
|   availability_status:  | ||||
|     type: string | ||||
|     enum: ['available', 'busy', 'offline'] | ||||
|     description: The availability status of the agent computed by Chatwoot. | ||||
|   auto_offline: | ||||
|     type: boolean | ||||
|     description: Whether the availability status of agent is configured to go offline automatically when away. | ||||
|   custom_attributes:  | ||||
|     type: object | ||||
|     description: Available for users who are created through platform APIs and has custom attributes associated. | ||||
|   | ||||
| @@ -53,6 +53,7 @@ x-tagGroups: | ||||
|   - name: Application | ||||
|     tags: | ||||
|       - Account AgentBots | ||||
|       - Agent | ||||
|       - Contact | ||||
|       - Conversation | ||||
|       - Conversation Assignment | ||||
|   | ||||
							
								
								
									
										42
									
								
								swagger/paths/application/agents/create.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								swagger/paths/application/agents/create.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| tags: | ||||
|   - Agent | ||||
| operationId: add-new-agent-to-account | ||||
| summary: Add a New Agent | ||||
| description: Add a new Agent to Account | ||||
| security: | ||||
|   - userApiKey: [] | ||||
| parameters: | ||||
|   - name: data | ||||
|     in: body | ||||
|     required: true | ||||
|     schema: | ||||
|       type: object | ||||
|       properties: | ||||
|         name:  | ||||
|           type: string | ||||
|           description: Full Name of the agent | ||||
|           required: true | ||||
|         email: | ||||
|           type: string | ||||
|           description: Email of the Agent | ||||
|           required: true | ||||
|         role:  | ||||
|           type: string | ||||
|           enum: ['agent', 'administrator'] | ||||
|           description: Whether its administrator or agent | ||||
|           required: true | ||||
|         availability_status:  | ||||
|           type: string | ||||
|           enum: ['available', 'busy', 'offline'] | ||||
|           description: The availability status of the agent. | ||||
|         auto_offline: | ||||
|           type: boolean | ||||
|           description: Whether the availability status of agent is configured to go offline automatically when away. | ||||
| responses: | ||||
|   200: | ||||
|     description: Success | ||||
|     schema: | ||||
|         description: 'Newly Created Agent' | ||||
|         $ref: '#/definitions/agent' | ||||
|   403: | ||||
|     description: Access denied | ||||
							
								
								
									
										21
									
								
								swagger/paths/application/agents/delete.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								swagger/paths/application/agents/delete.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| tags: | ||||
|   - Agent | ||||
| operationId: delete-agent-from-account | ||||
| summary: Remove an Agent from Account | ||||
| description: Remove an Agent from Account | ||||
| security: | ||||
|   - userApiKey: [] | ||||
| parameters: | ||||
|   - in: path | ||||
|     name: id | ||||
|     schema: | ||||
|       type: integer | ||||
|     required: true | ||||
|     description: The ID of the agent to be deleted | ||||
| responses: | ||||
|   200: | ||||
|     description: Success | ||||
|   404: | ||||
|     description: Agent not found | ||||
|   403: | ||||
|     description: Access denied | ||||
							
								
								
									
										17
									
								
								swagger/paths/application/agents/index.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								swagger/paths/application/agents/index.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| tags: | ||||
|   - Agent | ||||
| operationId: get-account-agents | ||||
| summary: List Agents in Account | ||||
| description: Get Details of Agents in an Account | ||||
| security: | ||||
|   - userApiKey: [] | ||||
| responses: | ||||
|   200: | ||||
|     description: Success | ||||
|     schema: | ||||
|         type: array | ||||
|         description: 'Array of all active agents' | ||||
|         items: | ||||
|           $ref: '#/definitions/agent' | ||||
|   403: | ||||
|     description: Access denied | ||||
							
								
								
									
										42
									
								
								swagger/paths/application/agents/update.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								swagger/paths/application/agents/update.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| tags: | ||||
|   - Agent | ||||
| operationId: update-agent-in-account | ||||
| summary: Update Agent in Account | ||||
| description: Update an Agent in Account | ||||
| security: | ||||
|   - userApiKey: [] | ||||
| parameters: | ||||
|   - in: path | ||||
|     name: id | ||||
|     schema: | ||||
|       type: integer | ||||
|     required: true | ||||
|     description: The ID of the agent to be updated. | ||||
|   - name: data | ||||
|     in: body | ||||
|     required: true | ||||
|     schema: | ||||
|       type: object | ||||
|       properties: | ||||
|         role:  | ||||
|           type: string | ||||
|           enum: ['agent', 'administrator'] | ||||
|           description: Whether its administrator or agent | ||||
|           required: true | ||||
|         availability_status:  | ||||
|           type: string | ||||
|           enum: ['available', 'busy', 'offline'] | ||||
|           description: The availability status of the agent. | ||||
|         auto_offline: | ||||
|           type: boolean | ||||
|           description: Whether the availability status of agent is configured to go offline automatically when away. | ||||
| responses: | ||||
|   200: | ||||
|     description: Success | ||||
|     schema: | ||||
|         description: 'The updated agent' | ||||
|         $ref: '#/definitions/agent' | ||||
|   404: | ||||
|     description: Agent not found | ||||
|   403: | ||||
|     description: Access denied | ||||
| @@ -116,63 +116,76 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat | ||||
| # ---------------- end of public api routes-----------# | ||||
|  | ||||
| # ------------  Application API routes  ------------# | ||||
| # AgentBots | ||||
|  | ||||
|  | ||||
| # AgentBots | ||||
| /api/v1/accounts/{account_id}/agent_bots: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|   get: | ||||
|     $ref: ./agent_bots/index.yml | ||||
|     $ref: ./application/agent_bots/index.yml | ||||
|   post: | ||||
|     $ref: ./agent_bots/create.yml | ||||
|     $ref: ./application/agent_bots/create.yml | ||||
| /api/v1/accounts/{account_id}/agent_bots/{id}: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/agent_bot_id' | ||||
|   get: | ||||
|     $ref: './agent_bots/show.yml' | ||||
|     $ref: './application/agent_bots/show.yml' | ||||
|   patch: | ||||
|     $ref: ./agent_bots/update.yml | ||||
|     $ref: ./application/agent_bots/update.yml | ||||
|   delete: | ||||
|     $ref: ./agent_bots/delete.yml | ||||
|     $ref: ./application/agent_bots/delete.yml | ||||
|  | ||||
| # Agents | ||||
| /api/v1/accounts/{account_id}/agents: | ||||
|   get: | ||||
|     $ref: ./application/agents/index.yml | ||||
|   post: | ||||
|     $ref: ./application/agents/create.yml | ||||
| /api/v1/accounts/{account_id}/agents/{id}: | ||||
|   patch: | ||||
|     $ref: ./application/agents/update.yml | ||||
|   delete:  | ||||
|     $ref: ./application/agents/delete.yml | ||||
|  | ||||
|  | ||||
| # Contacts | ||||
| /api/v1/accounts/{account_id}/contacts: | ||||
|   $ref: ./contact/list_create.yml | ||||
|   $ref: ./application/contacts/list_create.yml | ||||
| /api/v1/accounts/{account_id}/contacts/{id}: | ||||
|   $ref: ./contact/crud.yml | ||||
|   $ref: ./application/contacts/crud.yml | ||||
| /api/v1/accounts/{account_id}/contacts/{id}/conversations: | ||||
|   $ref: ./contact/conversations.yml | ||||
|   $ref: ./application/contacts/conversations.yml | ||||
| /api/v1/accounts/{account_id}/contacts/search: | ||||
|   $ref: ./contact/search.yml | ||||
|   $ref: ./application/contacts/search.yml | ||||
| /api/v1/accounts/{account_id}/contacts/{id}/contact_inboxes: | ||||
|   $ref: ./contact_inboxes/create.yml | ||||
|   $ref: ./application/contact_inboxes/create.yml | ||||
| /api/v1/accounts/{account_id}/contacts/{id}/contactable_inboxes: | ||||
|   $ref: ./contactable_inboxes/get.yml | ||||
|   $ref: ./application/contactable_inboxes/get.yml | ||||
|  | ||||
|  | ||||
| # Conversations | ||||
| /api/v1/accounts/{account_id}/conversations: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|   $ref: ./conversation/index.yml | ||||
|   $ref: ./application/conversation/index.yml | ||||
| /api/v1/accounts/{account_id}/conversations/: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|   $ref: ./conversation/create.yml | ||||
|   $ref: ./application/conversation/create.yml | ||||
| /api/v1/accounts/{account_id}/conversations/{converstion_id}: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/conversation_id' | ||||
|   get: | ||||
|     $ref: ./conversation/show.yml | ||||
|     $ref: ./application/conversation/show.yml | ||||
| /api/v1/accounts/{account_id}/conversations/{conversation_id}/toggle_status: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/conversation_id' | ||||
|   post: | ||||
|     $ref: ./conversation/toggle_status.yml | ||||
|     $ref: ./application/conversation/toggle_status.yml | ||||
|  | ||||
| # Conversations Assignments | ||||
|  | ||||
| @@ -181,7 +194,7 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/conversation_id' | ||||
|   post: | ||||
|     $ref: ./conversation/assignments.yml | ||||
|     $ref: ./application/conversation/assignments.yml | ||||
|  | ||||
| # Conversation Labels | ||||
|  | ||||
| @@ -190,56 +203,56 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/conversation_id' | ||||
|   get: | ||||
|     $ref: ./conversation/labels/index.yml | ||||
|     $ref: ./application/conversation/labels/index.yml | ||||
|   post: | ||||
|     $ref: ./conversation/labels/create.yml | ||||
|     $ref: ./application/conversation/labels/create.yml | ||||
|  | ||||
|  | ||||
| # Inboxes | ||||
| /api/v1/accounts/{account_id}/inboxes: | ||||
|   $ref: ./inboxes/index.yml | ||||
|   $ref: ./application/inboxes/index.yml | ||||
| /api/v1/accounts/{account_id}/inboxes/{id}/: | ||||
|   $ref: ./inboxes/show.yml | ||||
|   $ref: ./application/inboxes/show.yml | ||||
| /api/v1/accounts/{account_id}/inboxes/: | ||||
|   $ref: ./inboxes/create.yml | ||||
|   $ref: ./application/inboxes/create.yml | ||||
| /api/v1/accounts/{account_id}/inboxes/{id}: | ||||
|   $ref: ./inboxes/update.yml | ||||
|   $ref: ./application/inboxes/update.yml | ||||
| /api/v1/accounts/{account_id}/inboxes/{id}/agent_bot: | ||||
|   $ref: ./inboxes/get_agent_bot.yml | ||||
|   $ref: ./application/inboxes/get_agent_bot.yml | ||||
| /api/v1/accounts/{account_id}/inboxes/{id}/set_agent_bot: | ||||
|   $ref: ./inboxes/set_agent_bot.yml | ||||
|   $ref: ./application/inboxes/set_agent_bot.yml | ||||
|  | ||||
| # Inbox Members | ||||
| /api/v1/accounts/{account_id}/inbox_members: | ||||
|   get: | ||||
|     $ref: ./inboxes/inbox_members/show.yml | ||||
|     $ref: ./application/inboxes/inbox_members/show.yml | ||||
|   post: | ||||
|     $ref: ./inboxes/inbox_members/create.yml | ||||
|     $ref: ./application/inboxes/inbox_members/create.yml | ||||
|   patch: | ||||
|     $ref: ./inboxes/inbox_members/update.yml | ||||
|     $ref: ./application/inboxes/inbox_members/update.yml | ||||
|   delete:  | ||||
|     $ref: ./inboxes/inbox_members/delete.yml | ||||
|     $ref: ./application/inboxes/inbox_members/delete.yml | ||||
|    | ||||
|  | ||||
|  | ||||
| # Messages | ||||
| /api/v1/accounts/{account_id}/conversations/{id}/messages: | ||||
|   $ref: ./conversation/messages/create_attachment.yml | ||||
|   $ref: ./application/conversation/messages/create_attachment.yml | ||||
| /api/v1/accounts/{account_id}/conversations/{converstion_id}/messages: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/conversation_id' | ||||
|   get: | ||||
|     $ref: ./conversation/messages/index.yml | ||||
|     $ref: ./application/conversation/messages/index.yml | ||||
|   post: | ||||
|     $ref: ./conversation/messages/create.yml | ||||
|     $ref: ./application/conversation/messages/create.yml | ||||
| /api/v1/accounts/{account_id}/conversations/{conversation_id}/messages/{message_id}: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/conversation_id' | ||||
|     - $ref: '#/parameters/message_id' | ||||
|   delete: | ||||
|     $ref: ./conversation/messages/delete.yml | ||||
|     $ref: ./application/conversation/messages/delete.yml | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -248,14 +261,14 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|   get: | ||||
|     $ref: './integrations/apps/show.yml' | ||||
|     $ref: './application/integrations/apps/show.yml' | ||||
| /api/v1/accounts/{account_id}/integrations/hooks: | ||||
|   post: | ||||
|     $ref: './integrations/hooks/create.yml' | ||||
|     $ref: './application/integrations/hooks/create.yml' | ||||
|   patch: | ||||
|     $ref: ./integrations/hooks/update.yml | ||||
|     $ref: ./application/integrations/hooks/update.yml | ||||
|   delete: | ||||
|     $ref: ./integrations/hooks/delete.yml | ||||
|     $ref: ./application/integrations/hooks/delete.yml | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -269,19 +282,19 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|   get: | ||||
|     $ref: ./teams/index.yml | ||||
|     $ref: ./application/teams/index.yml | ||||
|   post: | ||||
|     $ref: ./teams/create.yml | ||||
|     $ref: ./application/teams/create.yml | ||||
| /api/v1/accounts/{account_id}/teams/{id}: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/team_id' | ||||
|   get: | ||||
|     $ref: './teams/show.yml' | ||||
|     $ref: './application/teams/show.yml' | ||||
|   patch: | ||||
|     $ref: ./teams/update.yml | ||||
|     $ref: ./application/teams/update.yml | ||||
|   delete: | ||||
|     $ref: ./teams/delete.yml | ||||
|     $ref: ./application/teams/delete.yml | ||||
|  | ||||
| ### Custom Filters goes here | ||||
|  | ||||
| @@ -297,19 +310,19 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat | ||||
|       required: false | ||||
|       description: The type of custom filter | ||||
|   get: | ||||
|     $ref: ./custom_filters/index.yml | ||||
|     $ref: ./application/custom_filters/index.yml | ||||
|   post: | ||||
|     $ref: ./custom_filters/create.yml | ||||
|     $ref: ./application/custom_filters/create.yml | ||||
| /api/v1/accounts/{account_id}/custom_filters/{custom_filter_id}: | ||||
|   parameters: | ||||
|     - $ref: '#/parameters/account_id' | ||||
|     - $ref: '#/parameters/custom_filter_id' | ||||
|   get: | ||||
|     $ref: './custom_filters/show.yml' | ||||
|     $ref: './application/custom_filters/show.yml' | ||||
|   patch: | ||||
|     $ref: ./custom_filters/update.yml | ||||
|     $ref: ./application/custom_filters/update.yml | ||||
|   delete: | ||||
|     $ref: ./custom_filters/delete.yml | ||||
|     $ref: ./application/custom_filters/delete.yml | ||||
|  | ||||
| ### Reports | ||||
|  | ||||
| @@ -335,7 +348,7 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat | ||||
|         type: string | ||||
|       description: The timestamp from where report should stop. | ||||
|   get: | ||||
|     $ref: './reports/index.yml' | ||||
|     $ref: './application/reports/index.yml' | ||||
|  | ||||
| # Summary | ||||
| /api/v2/accounts/{id}/reports/summary: | ||||
| @@ -358,4 +371,4 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat | ||||
|         type: string | ||||
|       description: The timestamp from where report should stop. | ||||
|   get: | ||||
|     $ref: './reports/summary.yml' | ||||
|     $ref: './application/reports/summary.yml' | ||||
|   | ||||
| @@ -1103,6 +1103,219 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/api/v1/accounts/{account_id}/agents": { | ||||
|       "get": { | ||||
|         "tags": [ | ||||
|           "Agent" | ||||
|         ], | ||||
|         "operationId": "get-account-agents", | ||||
|         "summary": "List Agents in Account", | ||||
|         "description": "Get Details of Agents in an Account", | ||||
|         "security": [ | ||||
|           { | ||||
|             "userApiKey": [ | ||||
|  | ||||
|             ] | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "description": "Success", | ||||
|             "schema": { | ||||
|               "type": "array", | ||||
|               "description": "Array of all active agents", | ||||
|               "items": { | ||||
|                 "$ref": "#/definitions/agent" | ||||
|               } | ||||
|             } | ||||
|           }, | ||||
|           "403": { | ||||
|             "description": "Access denied" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "post": { | ||||
|         "tags": [ | ||||
|           "Agent" | ||||
|         ], | ||||
|         "operationId": "add-new-agent-to-account", | ||||
|         "summary": "Add a New Agent", | ||||
|         "description": "Add a new Agent to Account", | ||||
|         "security": [ | ||||
|           { | ||||
|             "userApiKey": [ | ||||
|  | ||||
|             ] | ||||
|           } | ||||
|         ], | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "name": "data", | ||||
|             "in": "body", | ||||
|             "required": true, | ||||
|             "schema": { | ||||
|               "type": "object", | ||||
|               "properties": { | ||||
|                 "name": { | ||||
|                   "type": "string", | ||||
|                   "description": "Full Name of the agent", | ||||
|                   "required": true | ||||
|                 }, | ||||
|                 "email": { | ||||
|                   "type": "string", | ||||
|                   "description": "Email of the Agent", | ||||
|                   "required": true | ||||
|                 }, | ||||
|                 "role": { | ||||
|                   "type": "string", | ||||
|                   "enum": [ | ||||
|                     "agent", | ||||
|                     "administrator" | ||||
|                   ], | ||||
|                   "description": "Whether its administrator or agent", | ||||
|                   "required": true | ||||
|                 }, | ||||
|                 "availability_status": { | ||||
|                   "type": "string", | ||||
|                   "enum": [ | ||||
|                     "available", | ||||
|                     "busy", | ||||
|                     "offline" | ||||
|                   ], | ||||
|                   "description": "The availability status of the agent." | ||||
|                 }, | ||||
|                 "auto_offline": { | ||||
|                   "type": "boolean", | ||||
|                   "description": "Whether the availability status of agent is configured to go offline automatically when away." | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "description": "Success", | ||||
|             "schema": { | ||||
|               "$ref": "#/definitions/agent" | ||||
|             } | ||||
|           }, | ||||
|           "403": { | ||||
|             "description": "Access denied" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/api/v1/accounts/{account_id}/agents/{id}": { | ||||
|       "patch": { | ||||
|         "tags": [ | ||||
|           "Agent" | ||||
|         ], | ||||
|         "operationId": "update-agent-in-account", | ||||
|         "summary": "Update Agent in Account", | ||||
|         "description": "Update an Agent in Account", | ||||
|         "security": [ | ||||
|           { | ||||
|             "userApiKey": [ | ||||
|  | ||||
|             ] | ||||
|           } | ||||
|         ], | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "in": "path", | ||||
|             "name": "id", | ||||
|             "schema": { | ||||
|               "type": "integer" | ||||
|             }, | ||||
|             "required": true, | ||||
|             "description": "The ID of the agent to be updated." | ||||
|           }, | ||||
|           { | ||||
|             "name": "data", | ||||
|             "in": "body", | ||||
|             "required": true, | ||||
|             "schema": { | ||||
|               "type": "object", | ||||
|               "properties": { | ||||
|                 "role": { | ||||
|                   "type": "string", | ||||
|                   "enum": [ | ||||
|                     "agent", | ||||
|                     "administrator" | ||||
|                   ], | ||||
|                   "description": "Whether its administrator or agent", | ||||
|                   "required": true | ||||
|                 }, | ||||
|                 "availability_status": { | ||||
|                   "type": "string", | ||||
|                   "enum": [ | ||||
|                     "available", | ||||
|                     "busy", | ||||
|                     "offline" | ||||
|                   ], | ||||
|                   "description": "The availability status of the agent." | ||||
|                 }, | ||||
|                 "auto_offline": { | ||||
|                   "type": "boolean", | ||||
|                   "description": "Whether the availability status of agent is configured to go offline automatically when away." | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "description": "Success", | ||||
|             "schema": { | ||||
|               "$ref": "#/definitions/agent" | ||||
|             } | ||||
|           }, | ||||
|           "404": { | ||||
|             "description": "Agent not found" | ||||
|           }, | ||||
|           "403": { | ||||
|             "description": "Access denied" | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       "delete": { | ||||
|         "tags": [ | ||||
|           "Agent" | ||||
|         ], | ||||
|         "operationId": "delete-agent-from-account", | ||||
|         "summary": "Remove an Agent from Account", | ||||
|         "description": "Remove an Agent from Account", | ||||
|         "security": [ | ||||
|           { | ||||
|             "userApiKey": [ | ||||
|  | ||||
|             ] | ||||
|           } | ||||
|         ], | ||||
|         "parameters": [ | ||||
|           { | ||||
|             "in": "path", | ||||
|             "name": "id", | ||||
|             "schema": { | ||||
|               "type": "integer" | ||||
|             }, | ||||
|             "required": true, | ||||
|             "description": "The ID of the agent to be deleted" | ||||
|           } | ||||
|         ], | ||||
|         "responses": { | ||||
|           "200": { | ||||
|             "description": "Success" | ||||
|           }, | ||||
|           "404": { | ||||
|             "description": "Agent not found" | ||||
|           }, | ||||
|           "403": { | ||||
|             "description": "Access denied" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "/api/v1/accounts/{account_id}/contacts": { | ||||
|       "get": { | ||||
|         "tags": [ | ||||
| @@ -3432,7 +3645,7 @@ | ||||
|       "type": "object", | ||||
|       "properties": { | ||||
|         "id": { | ||||
|           "type": "number" | ||||
|           "type": "integer" | ||||
|         }, | ||||
|         "uid": { | ||||
|           "type": "string" | ||||
| @@ -3450,7 +3663,7 @@ | ||||
|           "type": "string" | ||||
|         }, | ||||
|         "account_id": { | ||||
|           "type": "number" | ||||
|           "type": "integer" | ||||
|         }, | ||||
|         "role": { | ||||
|           "type": "string", | ||||
| @@ -3462,6 +3675,19 @@ | ||||
|         "confirmed": { | ||||
|           "type": "boolean" | ||||
|         }, | ||||
|         "availability_status": { | ||||
|           "type": "string", | ||||
|           "enum": [ | ||||
|             "available", | ||||
|             "busy", | ||||
|             "offline" | ||||
|           ], | ||||
|           "description": "The availability status of the agent computed by Chatwoot." | ||||
|         }, | ||||
|         "auto_offline": { | ||||
|           "type": "boolean", | ||||
|           "description": "Whether the availability status of agent is configured to go offline automatically when away." | ||||
|         }, | ||||
|         "custom_attributes": { | ||||
|           "type": "object", | ||||
|           "description": "Available for users who are created through platform APIs and has custom attributes associated." | ||||
| @@ -4556,6 +4782,7 @@ | ||||
|       "name": "Application", | ||||
|       "tags": [ | ||||
|         "Account AgentBots", | ||||
|         "Agent", | ||||
|         "Contact", | ||||
|         "Conversation", | ||||
|         "Conversation Assignment", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose