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