chore: Move agent availability to Account level (#3074)

- Move agent availability to the account level
This commit is contained in:
Sojan Jose 2021-10-07 13:21:46 +05:30 committed by GitHub
parent 1c6a539c0a
commit c54aae21ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 618 additions and 148 deletions

View file

@ -9,21 +9,18 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
@agents = agents
end
def create; end
def update
@agent.update!(agent_params.slice(:name).compact)
@agent.current_account_user.update!(agent_params.slice(:role, :availability, :auto_offline).compact)
end
def destroy
@agent.current_account_user.destroy
head :ok
end
def update
@agent.update!(agent_params.except(:role))
@agent.current_account_user.update!(role: agent_params[:role]) if agent_params[:role]
render partial: 'api/v1/models/agent.json.jbuilder', locals: { resource: @agent }
end
def create
render partial: 'api/v1/models/agent.json.jbuilder', locals: { resource: @user }
end
private
def check_authorization
@ -47,22 +44,25 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
end
def save_account_user
AccountUser.create!(
AccountUser.create!({
account_id: Current.account.id,
user_id: @user.id,
role: new_agent_params[:role],
inviter_id: current_user.id
)
}.merge({
role: new_agent_params[:role],
availability: new_agent_params[:availability],
auto_offline: new_agent_params[:auto_offline]
}.compact))
end
def agent_params
params.require(:agent).permit(:email, :name, :role)
params.require(:agent).permit(:name, :email, :name, :role, :availability, :auto_offline)
end
def new_agent_params
# intial string ensures the password requirements are met
temp_password = "1!aA#{SecureRandom.alphanumeric(12)}"
params.require(:agent).permit(:email, :name, :role)
params.require(:agent).permit(:email, :name, :role, :availability, :auto_offline)
.merge!(password: temp_password, password_confirmation: temp_password, inviter: current_user)
end

View file

@ -1,9 +1,7 @@
class Api::V1::ProfilesController < Api::BaseController
before_action :set_user
def show
render partial: 'api/v1/models/user.json.jbuilder', locals: { resource: @user }
end
def show; end
def update
if password_params[:password].present?
@ -15,19 +13,26 @@ class Api::V1::ProfilesController < Api::BaseController
@user.update!(profile_params)
end
def availability
@user.account_users.find_by!(account_id: availability_params[:account_id]).update!(availability: availability_params[:availability])
end
private
def set_user
@user = current_user
end
def availability_params
params.require(:profile).permit(:account_id, :availability)
end
def profile_params
params.require(:profile).permit(
:email,
:name,
:display_name,
:avatar,
:availability,
ui_settings: {}
)
end

View file

@ -161,9 +161,9 @@ export default {
});
},
updateAvailability({ availability }) {
return axios.put(endPoints('profileUpdate').url, {
profile: { availability },
updateAvailability(availabilityData) {
return axios.post(endPoints('availabilityUpdate').url, {
profile: { ...availabilityData },
});
},
};

View file

@ -13,6 +13,9 @@ const endPoints = {
profileUpdate: {
url: '/api/v1/profile',
},
availabilityUpdate: {
url: '/api/v1/profile/availability',
},
logout: {
url: 'auth/sign_out',
},

View file

@ -26,7 +26,9 @@
color-scheme="secondary"
class-names="status-change--dropdown-button"
:is-disabled="status.disabled"
@click="changeAvailabilityStatus(status.value)"
@click="
changeAvailabilityStatus(status.value, currentAccountId)
"
>
<availability-status-badge :status="status.value" />
{{ status.label }}
@ -75,7 +77,8 @@ export default {
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
getCurrentUserAvailabilityStatus: 'getCurrentUserAvailabilityStatus',
getCurrentAccountId: 'getCurrentAccountId',
}),
availabilityDisplayLabel() {
const availabilityIndex = AVAILABILITY_STATUS_KEYS.findIndex(
@ -85,8 +88,11 @@ export default {
availabilityIndex
];
},
currentAccountId() {
return this.getCurrentAccountId;
},
currentUserAvailabilityStatus() {
return this.currentUser.availability_status;
return this.getCurrentUserAvailabilityStatus;
},
availabilityStatuses() {
return this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.STATUSES_LIST').map(
@ -108,16 +114,16 @@ export default {
closeStatusMenu() {
this.isStatusMenuOpened = false;
},
changeAvailabilityStatus(availability) {
changeAvailabilityStatus(availability, accountId) {
if (this.isUpdating) {
return;
}
this.isUpdating = true;
this.$store
.dispatch('updateAvailability', {
availability,
availability: availability,
account_id: accountId,
})
.finally(() => {
this.isUpdating = false;

View file

@ -17,7 +17,8 @@ const i18nConfig = new VueI18n({
});
describe('AvailabilityStatus', () => {
const currentUser = { availability_status: 'online' };
const currentAvailabilityStatus = 'online' ;
const currentAccountId = '1';
let store = null;
let actions = null;
let modules = null;
@ -33,7 +34,8 @@ describe('AvailabilityStatus', () => {
modules = {
auth: {
getters: {
getCurrentUser: () => currentUser,
getCurrentUserAvailabilityStatus: () => currentAvailabilityStatus,
getCurrentAccountId: () => currentAccountId,
},
},
};
@ -77,7 +79,7 @@ describe('AvailabilityStatus', () => {
expect(actions.updateAvailability).toBeCalledWith(
expect.any(Object),
{ availability: 'offline' },
{ availability: 'offline', account_id: currentAccountId },
undefined
);
});

View file

@ -40,7 +40,11 @@ export const getters = {
},
getCurrentUserAvailabilityStatus(_state) {
return _state.currentUser.availability_status;
const { accounts = [] } = _state.currentUser;
const [currentAccount = {}] = accounts.filter(
account => account.id === _state.currentAccountId
);
return currentAccount.availability_status;
},
getCurrentAccountId(_state) {
@ -125,14 +129,17 @@ export const actions = {
}
},
updateAvailability: ({ commit, dispatch }, { availability }) => {
authAPI.updateAvailability({ availability }).then(response => {
updateAvailability: async ({ commit, dispatch }, params) => {
try {
const response = await authAPI.updateAvailability(params);
const userData = response.data;
const { id, availability_status: availabilityStatus } = userData;
const { id } = userData;
setUser(userData, getHeaderExpiry(response));
commit(types.default.SET_CURRENT_USER);
dispatch('agents/updatePresence', { [id]: availabilityStatus });
});
dispatch('agents/updatePresence', { [id]: params.availability });
} catch (error) {
// Ignore error
}
},
setCurrentAccountId({ commit }, accountId) {

View file

@ -54,13 +54,16 @@ describe('#actions', () => {
describe('#updateAvailability', () => {
it('sends correct actions if API is success', async () => {
axios.put.mockResolvedValue({
data: { id: 1, name: 'John', availability_status: 'offline' },
axios.post.mockResolvedValue({
data: {
id: 1,
account_users: [{ account_id: 1, availability_status: 'offline' }],
},
headers: { expiry: 581842904 },
});
await actions.updateAvailability(
{ commit, dispatch },
{ availability: 'offline' }
{ availability: 'offline', account_id: 1 },
);
expect(setUser).toHaveBeenCalledTimes(1);
expect(commit.mock.calls).toEqual([[types.default.SET_CURRENT_USER]]);

View file

@ -21,7 +21,11 @@ describe('#getters', () => {
it('get', () => {
expect(
getters.getCurrentUserAvailabilityStatus({
currentUser: { id: 1, name: 'Pranav', availability_status: 'busy' },
currentAccountId: 1,
currentUser: {
id: 1,
accounts: [{ id: 1, availability_status: 'busy' }],
},
})
).toEqual('busy');
});

View file

@ -1,6 +1,6 @@
import { createConsumer } from '@rails/actioncable';
const PRESENCE_INTERVAL = 60000;
const PRESENCE_INTERVAL = 20000;
class BaseActionCableConnector {
constructor(app, pubsubToken, websocketHost = '') {

View file

@ -2,14 +2,16 @@
#
# Table name: account_users
#
# id :bigint not null, primary key
# active_at :datetime
# role :integer default("agent")
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint
# inviter_id :bigint
# user_id :bigint
# id :bigint not null, primary key
# active_at :datetime
# auto_offline :boolean default(TRUE), not null
# availability :integer default("online"), not null
# role :integer default("agent")
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint
# inviter_id :bigint
# user_id :bigint
#
# Indexes
#
@ -24,15 +26,20 @@
#
class AccountUser < ApplicationRecord
include AvailabilityStatusable
belongs_to :account
belongs_to :user
belongs_to :inviter, class_name: 'User', optional: true
enum role: { agent: 0, administrator: 1 }
enum availability: { online: 0, offline: 1, busy: 2 }
accepts_nested_attributes_for :account
after_create_commit :notify_creation, :create_notification_setting
after_destroy :notify_deletion, :remove_user_from_account
after_save :update_presence_in_redis, if: :saved_change_to_availability?
validates :user_id, uniqueness: { scope: :account_id }
@ -56,4 +63,8 @@ class AccountUser < ApplicationRecord
def notify_deletion
Rails.configuration.dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: account)
end
def update_presence_in_redis
OnlineStatusTracker.set_status(account.id, user.id, availability)
end
end

View file

@ -2,29 +2,29 @@ module AvailabilityStatusable
extend ActiveSupport::Concern
def online_presence?
return if user_profile_page_context?
::OnlineStatusTracker.get_presence(availability_account_id, self.class.name, id)
obj_id = is_a?(Contact) ? id : user_id
::OnlineStatusTracker.get_presence(account_id, self.class.name, obj_id)
end
def availability_status
return availability if user_profile_page_context?
return 'offline' unless online_presence?
return 'online' if is_a? Contact
::OnlineStatusTracker.get_status(availability_account_id, id) || 'online'
if is_a? Contact
contact_availability_status
else
user_availability_status
end
end
def user_profile_page_context?
# at the moment profile pages aren't account scoped
# hence we will return availability attribute instead of true presence
# we will revisit this later
is_a?(User) && Current.account.blank?
private
def contact_availability_status
online_presence? ? 'online' : 'offline'
end
def availability_account_id
return account_id if is_a? Contact
def user_availability_status
# we are not considering presence in this case. Just returns the availability
return availability unless auto_offline
Current.account.id
# availability as a fallback in case the status is not present in redis
online_presence? ? (::OnlineStatusTracker.get_status(account_id, user_id) || availability) : 'offline'
end
end

View file

@ -39,7 +39,6 @@
class User < ApplicationRecord
include AccessTokenable
include AvailabilityStatusable
include Avatarable
# Include default devise modules.
include DeviseTokenAuth::Concerns::User
@ -57,6 +56,8 @@ class User < ApplicationRecord
:confirmable,
:password_has_required_content
# TODO: remove in a future version once online status is moved to account users
# remove the column availability from users
enum availability: { online: 0, offline: 1, busy: 2 }
# The validation below has been commented out as it does not
@ -89,8 +90,6 @@ class User < ApplicationRecord
before_validation :set_password_and_uid, on: :create
after_save :update_presence_in_redis, if: :saved_change_to_availability?
scope :order_by_full_name, -> { order('lower(name) ASC') }
def send_devise_notification(notification, *args)
@ -141,6 +140,14 @@ class User < ApplicationRecord
current_account_user&.role
end
def availability_status
current_account_user&.availability_status
end
def auto_offline
current_account_user&.auto_offline
end
def inviter
current_account_user&.inviter
end
@ -169,12 +176,4 @@ class User < ApplicationRecord
type: 'user'
}
end
private
def update_presence_in_redis
accounts.each do |account|
OnlineStatusTracker.set_status(account.id, id, availability)
end
end
end

View file

@ -0,0 +1 @@
json.partial! 'api/v1/models/agent.json.jbuilder', resource: @user

View file

@ -0,0 +1 @@
json.partial! 'api/v1/models/agent.json.jbuilder', resource: @agent

View file

@ -1,10 +1,11 @@
json.id resource.id
# could be nil for a deleted agent hence the safe operator before account id
json.account_id resource.account&.id
json.availability_status resource.availability_status
json.auto_offline resource.auto_offline
json.confirmed resource.confirmed?
json.email resource.email
json.available_name resource.available_name
json.id resource.id
json.custom_attributes resource.custom_attributes if resource.custom_attributes.present?
json.name resource.name
json.role resource.role

View file

@ -1,6 +1,5 @@
json.access_token resource.access_token.token
json.account_id resource.active_account_user&.account_id
json.availability_status resource.availability_status
json.available_name resource.available_name
json.avatar_url resource.avatar_url
json.confirmed resource.confirmed?
@ -22,5 +21,7 @@ json.accounts do
json.name account_user.account.name
json.active_at account_user.active_at
json.role account_user.role
json.availability_status account_user.availability_status
json.auto_offline account_user.auto_offline
end
end

View file

@ -0,0 +1 @@
json.partial! 'api/v1/models/user.json.jbuilder', resource: @user

View file

@ -0,0 +1 @@
json.partial! 'api/v1/models/user.json.jbuilder', resource: @user

View file

@ -3,6 +3,6 @@ json.payload do
json.id inbox_member.user.id
json.name inbox_member.user.available_name
json.avatar_url inbox_member.user.avatar_url
json.availability_status inbox_member.user.availability_status
json.availability_status inbox_member.user.account_users.find_by(account_id: @current_account.id).availability_status
end
end

View file

@ -1,6 +1,5 @@
json.access_token resource.access_token.token
json.account_id resource.active_account_user&.account_id
json.availability_status resource.availability_status
json.available_name resource.available_name
json.avatar_url resource.avatar_url
json.confirmed resource.confirmed?

View file

@ -40,7 +40,7 @@ Rails.application.routes.draw do
resource :contact_merge, only: [:create]
end
resources :agents, except: [:show, :edit, :new]
resources :agents, only: [:index, :create, :update, :destroy]
resources :agent_bots, only: [:index, :create, :show, :update, :destroy]
resources :callbacks, only: [] do
@ -159,7 +159,11 @@ Rails.application.routes.draw do
resources :webhooks, only: [:create]
end
resource :profile, only: [:show, :update]
resource :profile, only: [:show, :update] do
member do
post :availability
end
end
resource :notification_subscriptions, only: [:create]
namespace :widget do

View file

@ -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

View file

@ -35,6 +35,8 @@ ActiveRecord::Schema.define(version: 2021_09_29_150415) do
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "active_at"
t.integer "availability", default: 0, null: false
t.boolean "auto_offline", default: true, null: false
t.index ["account_id", "user_id"], name: "uniq_user_id_per_account_id", unique: true
t.index ["account_id"], name: "index_account_users_on_account_id"
t.index ["user_id"], name: "index_account_users_on_user_id"

View file

@ -1,5 +1,5 @@
module OnlineStatusTracker
PRESENCE_DURATION = 60.seconds
PRESENCE_DURATION = 20.seconds
# presence : sorted set with timestamp as the score & object id as value

View file

@ -94,7 +94,7 @@ RSpec.describe 'Agents API', type: :request do
expect(response).to have_http_status(:unauthorized)
end
it 'modifies an agent' do
it 'modifies an agent name' do
put "/api/v1/accounts/#{account.id}/agents/#{other_agent.id}",
params: params,
headers: admin.create_new_auth_token,
@ -103,6 +103,20 @@ RSpec.describe 'Agents API', type: :request do
expect(response).to have_http_status(:success)
expect(other_agent.reload.name).to eq(params[:name])
end
it 'modifies an agents account user attributes' do
put "/api/v1/accounts/#{account.id}/agents/#{other_agent.id}",
params: { role: 'administrator', availability: 'busy', auto_offline: false },
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
response_data = JSON.parse(response.body)
expect(response_data['role']).to eq('administrator')
expect(response_data['availability_status']).to eq('busy')
expect(response_data['auto_offline']).to eq(false)
expect(other_agent.account_users.first.role).to eq('administrator')
end
end
end

View file

@ -89,16 +89,6 @@ RSpec.describe 'Profile API', type: :request do
expect(agent.avatar.attached?).to eq(true)
end
it 'updates the availability status' do
put '/api/v1/profile',
params: { profile: { availability: 'offline' } },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(::OnlineStatusTracker.get_status(account.id, agent.id)).to eq('offline')
end
it 'updates the ui settings' do
put '/api/v1/profile',
params: { profile: { ui_settings: { is_contact_sidebar_open: false } } },
@ -111,4 +101,28 @@ RSpec.describe 'Profile API', type: :request do
end
end
end
describe 'POST /api/v1/profile/availability' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
post '/api/v1/profile/availability'
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated user' do
let(:agent) { create(:user, password: 'Test123!', account: account, role: :agent) }
it 'updates the availability status' do
post '/api/v1/profile/availability',
params: { profile: { availability: 'busy', account_id: account.id } },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(::OnlineStatusTracker.get_status(account.id, agent.id)).to eq('busy')
end
end
end
end

View file

@ -1,7 +1,7 @@
type: object
properties:
id:
type: number
type: integer
uid:
type: string
name:
@ -13,12 +13,19 @@ properties:
email:
type: string
account_id:
type: number
type: integer
role:
type: string
enum: ['agent', 'administrator']
confirmed:
type: boolean
availability_status:
type: string
enum: ['available', 'busy', 'offline']
description: The availability status of the agent computed by Chatwoot.
auto_offline:
type: boolean
description: Whether the availability status of agent is configured to go offline automatically when away.
custom_attributes:
type: object
description: Available for users who are created through platform APIs and has custom attributes associated.

View file

@ -53,6 +53,7 @@ x-tagGroups:
- name: Application
tags:
- Account AgentBots
- Agent
- Contact
- Conversation
- Conversation Assignment

View 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

View 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

View 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

View 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

View file

@ -116,63 +116,76 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
# ---------------- end of public api routes-----------#
# ------------ Application API routes ------------#
# AgentBots
# AgentBots
/api/v1/accounts/{account_id}/agent_bots:
parameters:
- $ref: '#/parameters/account_id'
get:
$ref: ./agent_bots/index.yml
$ref: ./application/agent_bots/index.yml
post:
$ref: ./agent_bots/create.yml
$ref: ./application/agent_bots/create.yml
/api/v1/accounts/{account_id}/agent_bots/{id}:
parameters:
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/agent_bot_id'
get:
$ref: './agent_bots/show.yml'
$ref: './application/agent_bots/show.yml'
patch:
$ref: ./agent_bots/update.yml
$ref: ./application/agent_bots/update.yml
delete:
$ref: ./agent_bots/delete.yml
$ref: ./application/agent_bots/delete.yml
# Agents
/api/v1/accounts/{account_id}/agents:
get:
$ref: ./application/agents/index.yml
post:
$ref: ./application/agents/create.yml
/api/v1/accounts/{account_id}/agents/{id}:
patch:
$ref: ./application/agents/update.yml
delete:
$ref: ./application/agents/delete.yml
# Contacts
/api/v1/accounts/{account_id}/contacts:
$ref: ./contact/list_create.yml
$ref: ./application/contacts/list_create.yml
/api/v1/accounts/{account_id}/contacts/{id}:
$ref: ./contact/crud.yml
$ref: ./application/contacts/crud.yml
/api/v1/accounts/{account_id}/contacts/{id}/conversations:
$ref: ./contact/conversations.yml
$ref: ./application/contacts/conversations.yml
/api/v1/accounts/{account_id}/contacts/search:
$ref: ./contact/search.yml
$ref: ./application/contacts/search.yml
/api/v1/accounts/{account_id}/contacts/{id}/contact_inboxes:
$ref: ./contact_inboxes/create.yml
$ref: ./application/contact_inboxes/create.yml
/api/v1/accounts/{account_id}/contacts/{id}/contactable_inboxes:
$ref: ./contactable_inboxes/get.yml
$ref: ./application/contactable_inboxes/get.yml
# Conversations
/api/v1/accounts/{account_id}/conversations:
parameters:
- $ref: '#/parameters/account_id'
$ref: ./conversation/index.yml
$ref: ./application/conversation/index.yml
/api/v1/accounts/{account_id}/conversations/:
parameters:
- $ref: '#/parameters/account_id'
$ref: ./conversation/create.yml
$ref: ./application/conversation/create.yml
/api/v1/accounts/{account_id}/conversations/{converstion_id}:
parameters:
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/conversation_id'
get:
$ref: ./conversation/show.yml
$ref: ./application/conversation/show.yml
/api/v1/accounts/{account_id}/conversations/{conversation_id}/toggle_status:
parameters:
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/conversation_id'
post:
$ref: ./conversation/toggle_status.yml
$ref: ./application/conversation/toggle_status.yml
# Conversations Assignments
@ -181,7 +194,7 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/conversation_id'
post:
$ref: ./conversation/assignments.yml
$ref: ./application/conversation/assignments.yml
# Conversation Labels
@ -190,56 +203,56 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/conversation_id'
get:
$ref: ./conversation/labels/index.yml
$ref: ./application/conversation/labels/index.yml
post:
$ref: ./conversation/labels/create.yml
$ref: ./application/conversation/labels/create.yml
# Inboxes
/api/v1/accounts/{account_id}/inboxes:
$ref: ./inboxes/index.yml
$ref: ./application/inboxes/index.yml
/api/v1/accounts/{account_id}/inboxes/{id}/:
$ref: ./inboxes/show.yml
$ref: ./application/inboxes/show.yml
/api/v1/accounts/{account_id}/inboxes/:
$ref: ./inboxes/create.yml
$ref: ./application/inboxes/create.yml
/api/v1/accounts/{account_id}/inboxes/{id}:
$ref: ./inboxes/update.yml
$ref: ./application/inboxes/update.yml
/api/v1/accounts/{account_id}/inboxes/{id}/agent_bot:
$ref: ./inboxes/get_agent_bot.yml
$ref: ./application/inboxes/get_agent_bot.yml
/api/v1/accounts/{account_id}/inboxes/{id}/set_agent_bot:
$ref: ./inboxes/set_agent_bot.yml
$ref: ./application/inboxes/set_agent_bot.yml
# Inbox Members
/api/v1/accounts/{account_id}/inbox_members:
get:
$ref: ./inboxes/inbox_members/show.yml
$ref: ./application/inboxes/inbox_members/show.yml
post:
$ref: ./inboxes/inbox_members/create.yml
$ref: ./application/inboxes/inbox_members/create.yml
patch:
$ref: ./inboxes/inbox_members/update.yml
$ref: ./application/inboxes/inbox_members/update.yml
delete:
$ref: ./inboxes/inbox_members/delete.yml
$ref: ./application/inboxes/inbox_members/delete.yml
# Messages
/api/v1/accounts/{account_id}/conversations/{id}/messages:
$ref: ./conversation/messages/create_attachment.yml
$ref: ./application/conversation/messages/create_attachment.yml
/api/v1/accounts/{account_id}/conversations/{converstion_id}/messages:
parameters:
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/conversation_id'
get:
$ref: ./conversation/messages/index.yml
$ref: ./application/conversation/messages/index.yml
post:
$ref: ./conversation/messages/create.yml
$ref: ./application/conversation/messages/create.yml
/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages/{message_id}:
parameters:
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/conversation_id'
- $ref: '#/parameters/message_id'
delete:
$ref: ./conversation/messages/delete.yml
$ref: ./application/conversation/messages/delete.yml
@ -248,14 +261,14 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
parameters:
- $ref: '#/parameters/account_id'
get:
$ref: './integrations/apps/show.yml'
$ref: './application/integrations/apps/show.yml'
/api/v1/accounts/{account_id}/integrations/hooks:
post:
$ref: './integrations/hooks/create.yml'
$ref: './application/integrations/hooks/create.yml'
patch:
$ref: ./integrations/hooks/update.yml
$ref: ./application/integrations/hooks/update.yml
delete:
$ref: ./integrations/hooks/delete.yml
$ref: ./application/integrations/hooks/delete.yml
@ -269,19 +282,19 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
parameters:
- $ref: '#/parameters/account_id'
get:
$ref: ./teams/index.yml
$ref: ./application/teams/index.yml
post:
$ref: ./teams/create.yml
$ref: ./application/teams/create.yml
/api/v1/accounts/{account_id}/teams/{id}:
parameters:
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/team_id'
get:
$ref: './teams/show.yml'
$ref: './application/teams/show.yml'
patch:
$ref: ./teams/update.yml
$ref: ./application/teams/update.yml
delete:
$ref: ./teams/delete.yml
$ref: ./application/teams/delete.yml
### Custom Filters goes here
@ -297,19 +310,19 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
required: false
description: The type of custom filter
get:
$ref: ./custom_filters/index.yml
$ref: ./application/custom_filters/index.yml
post:
$ref: ./custom_filters/create.yml
$ref: ./application/custom_filters/create.yml
/api/v1/accounts/{account_id}/custom_filters/{custom_filter_id}:
parameters:
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/custom_filter_id'
get:
$ref: './custom_filters/show.yml'
$ref: './application/custom_filters/show.yml'
patch:
$ref: ./custom_filters/update.yml
$ref: ./application/custom_filters/update.yml
delete:
$ref: ./custom_filters/delete.yml
$ref: ./application/custom_filters/delete.yml
### Reports
@ -335,7 +348,7 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
type: string
description: The timestamp from where report should stop.
get:
$ref: './reports/index.yml'
$ref: './application/reports/index.yml'
# Summary
/api/v2/accounts/{id}/reports/summary:
@ -358,4 +371,4 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
type: string
description: The timestamp from where report should stop.
get:
$ref: './reports/summary.yml'
$ref: './application/reports/summary.yml'

View file

@ -1103,6 +1103,219 @@
}
}
},
"/api/v1/accounts/{account_id}/agents": {
"get": {
"tags": [
"Agent"
],
"operationId": "get-account-agents",
"summary": "List Agents in Account",
"description": "Get Details of Agents in an Account",
"security": [
{
"userApiKey": [
]
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "array",
"description": "Array of all active agents",
"items": {
"$ref": "#/definitions/agent"
}
}
},
"403": {
"description": "Access denied"
}
}
},
"post": {
"tags": [
"Agent"
],
"operationId": "add-new-agent-to-account",
"summary": "Add a New Agent",
"description": "Add a new Agent to Account",
"security": [
{
"userApiKey": [
]
}
],
"parameters": [
{
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Full Name of the agent",
"required": true
},
"email": {
"type": "string",
"description": "Email of the Agent",
"required": true
},
"role": {
"type": "string",
"enum": [
"agent",
"administrator"
],
"description": "Whether its administrator or agent",
"required": true
},
"availability_status": {
"type": "string",
"enum": [
"available",
"busy",
"offline"
],
"description": "The availability status of the agent."
},
"auto_offline": {
"type": "boolean",
"description": "Whether the availability status of agent is configured to go offline automatically when away."
}
}
}
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/agent"
}
},
"403": {
"description": "Access denied"
}
}
}
},
"/api/v1/accounts/{account_id}/agents/{id}": {
"patch": {
"tags": [
"Agent"
],
"operationId": "update-agent-in-account",
"summary": "Update Agent in Account",
"description": "Update an Agent in Account",
"security": [
{
"userApiKey": [
]
}
],
"parameters": [
{
"in": "path",
"name": "id",
"schema": {
"type": "integer"
},
"required": true,
"description": "The ID of the agent to be updated."
},
{
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"properties": {
"role": {
"type": "string",
"enum": [
"agent",
"administrator"
],
"description": "Whether its administrator or agent",
"required": true
},
"availability_status": {
"type": "string",
"enum": [
"available",
"busy",
"offline"
],
"description": "The availability status of the agent."
},
"auto_offline": {
"type": "boolean",
"description": "Whether the availability status of agent is configured to go offline automatically when away."
}
}
}
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/agent"
}
},
"404": {
"description": "Agent not found"
},
"403": {
"description": "Access denied"
}
}
},
"delete": {
"tags": [
"Agent"
],
"operationId": "delete-agent-from-account",
"summary": "Remove an Agent from Account",
"description": "Remove an Agent from Account",
"security": [
{
"userApiKey": [
]
}
],
"parameters": [
{
"in": "path",
"name": "id",
"schema": {
"type": "integer"
},
"required": true,
"description": "The ID of the agent to be deleted"
}
],
"responses": {
"200": {
"description": "Success"
},
"404": {
"description": "Agent not found"
},
"403": {
"description": "Access denied"
}
}
}
},
"/api/v1/accounts/{account_id}/contacts": {
"get": {
"tags": [
@ -3432,7 +3645,7 @@
"type": "object",
"properties": {
"id": {
"type": "number"
"type": "integer"
},
"uid": {
"type": "string"
@ -3450,7 +3663,7 @@
"type": "string"
},
"account_id": {
"type": "number"
"type": "integer"
},
"role": {
"type": "string",
@ -3462,6 +3675,19 @@
"confirmed": {
"type": "boolean"
},
"availability_status": {
"type": "string",
"enum": [
"available",
"busy",
"offline"
],
"description": "The availability status of the agent computed by Chatwoot."
},
"auto_offline": {
"type": "boolean",
"description": "Whether the availability status of agent is configured to go offline automatically when away."
},
"custom_attributes": {
"type": "object",
"description": "Available for users who are created through platform APIs and has custom attributes associated."
@ -4556,6 +4782,7 @@
"name": "Application",
"tags": [
"Account AgentBots",
"Agent",
"Contact",
"Conversation",
"Conversation Assignment",