chore: Move agent availability to Account level (#3074)
- Move agent availability to the account level
This commit is contained in:
parent
1c6a539c0a
commit
c54aae21ff
84 changed files with 618 additions and 148 deletions
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue