Chore: API Improvements (#2956)

- API to fetch info of a single inbox
- Document passing custom_attributes in the API
- Ability to filter contacts with contact identifier in search API
This commit is contained in:
Sojan Jose
2021-09-04 17:56:46 +05:30
committed by GitHub
parent b866c54ad5
commit 2ebd38c3b7
29 changed files with 248 additions and 29 deletions

View File

@@ -79,6 +79,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
@resolved_contacts = Current.account.contacts
.where.not(email: [nil, ''])
.or(Current.account.contacts.where.not(phone_number: [nil, '']))
.or(Current.account.contacts.where.not(identifier: [nil, '']))
@resolved_contacts = @resolved_contacts.tagged_with(params[:labels], any: true) if params[:labels].present?
@resolved_contacts
end

View File

@@ -1,12 +1,15 @@
class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
before_action :fetch_inbox, except: [:index, :create]
before_action :fetch_agent_bot, only: [:set_agent_bot]
before_action :check_authorization
# we are already handling the authorization in fetch inbox
before_action :check_authorization, except: [:show]
def index
@inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, { avatar_attachment: [:blob] }))
end
def show; end
def assignable_agents
@assignable_agents = (Current.account.users.where(id: @inbox.members.select(:user_id)) + Current.account.administrators).uniq
end

View File

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

View File

@@ -97,7 +97,7 @@ Rails.application.routes.draw do
end
resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy]
resources :custom_filters, only: [:index, :show, :create, :update, :destroy]
resources :inboxes, only: [:index, :create, :update, :destroy] do
resources :inboxes, only: [:index, :show, :create, :update, :destroy] do
get :assignable_agents, on: :member
get :campaigns, on: :member
get :agent_bot, on: :member

View File

@@ -3,7 +3,7 @@ require 'rails_helper'
describe ::ContactBuilder do
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account) }
let(:contact) { create(:contact, account: account, identifier: '123') }
let(:contact) { create(:contact, email: 'xyc@example.com', phone_number: '+23423424123', account: account, identifier: '123') }
let(:existing_contact_inbox) { create(:contact_inbox, contact: contact, inbox: inbox) }
describe '#perform' do

View File

@@ -2,7 +2,7 @@ require 'rails_helper'
describe ::ContactInboxBuilder do
let(:account) { create(:account) }
let(:contact) { create(:contact, account: account) }
let(:contact) { create(:contact, email: 'xyc@example.com', phone_number: '+23423424123', account: account) }
describe '#perform' do
describe 'twilio sms inbox' do

View File

@@ -14,10 +14,10 @@ RSpec.describe 'Contacts API', type: :request do
context 'when it is an authenticated user' do
let(:admin) { create(:user, account: account, role: :administrator) }
let!(:contact) { create(:contact, account: account) }
let!(:contact) { create(:contact, :with_email, account: account) }
let!(:contact_inbox) { create(:contact_inbox, contact: contact) }
it 'returns all contacts with contact inboxes' do
it 'returns all resolved contacts along with contact inboxes' do
get "/api/v1/accounts/#{account.id}/contacts",
headers: admin.create_new_auth_token,
as: :json
@@ -52,8 +52,8 @@ RSpec.describe 'Contacts API', type: :request do
expect(response_body['payload'].first['last_seen_at']).present?
end
it 'filters contacts based on label filter' do
contact_with_label1, contact_with_label2 = FactoryBot.create_list(:contact, 2, account: account)
it 'filters resolved contacts based on label filter' do
contact_with_label1, contact_with_label2 = FactoryBot.create_list(:contact, 2, :with_email, account: account)
contact_with_label1.update_labels(['label1'])
contact_with_label2.update_labels(['label2'])
@@ -126,7 +126,7 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response.body).not_to include(contact.email)
expect(response.body).not_to include(contact.name)
end
it 'returns all contacts who are online' do
@@ -137,7 +137,7 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response.body).to include(contact.email)
expect(response.body).to include(contact.name)
end
end
end
@@ -153,10 +153,10 @@ RSpec.describe 'Contacts API', type: :request do
context 'when it is an authenticated user' do
let(:admin) { create(:user, account: account, role: :administrator) }
let!(:contact1) { create(:contact, account: account) }
let!(:contact2) { create(:contact, name: 'testcontact', account: account, email: 'test@test.com') }
let!(:contact1) { create(:contact, :with_email, account: account) }
let!(:contact2) { create(:contact, :with_email, name: 'testcontact', account: account, email: 'test@test.com') }
it 'returns all contacts with contact inboxes' do
it 'returns all resolved contacts with contact inboxes' do
get "/api/v1/accounts/#{account.id}/contacts/search",
params: { q: contact2.email },
headers: admin.create_new_auth_token,
@@ -189,7 +189,7 @@ RSpec.describe 'Contacts API', type: :request do
expect(response.body).not_to include(contact1.email)
end
it 'matches the contact respecting the identifier character casing' do
it 'matches the resolved contact respecting the identifier character casing' do
contact_normal = create(:contact, name: 'testcontact', account: account, identifier: 'testidentifer')
contact_special = create(:contact, name: 'testcontact', account: account, identifier: 'TestIdentifier')
get "/api/v1/accounts/#{account.id}/contacts/search",
@@ -224,7 +224,7 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response.body).to include(contact.email)
expect(response.body).to include(contact.name)
end
end
end

View File

@@ -44,6 +44,51 @@ RSpec.describe 'Inboxes API', type: :request do
end
end
describe 'GET /api/v1/accounts/{account.id}/inboxes/{inbox.id}' do
let(:inbox) { create(:inbox, account: account) }
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
get "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated user' do
let(:agent) { create(:user, account: account, role: :agent) }
let(:admin) { create(:user, account: account, role: :administrator) }
let(:inbox) { create(:inbox, account: account) }
it 'returns unauthorized for an agent who is not assigned' do
get "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unauthorized)
end
it 'returns the inbox if administrator' do
get "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(JSON.parse(response.body, symbolize_names: true)[:id]).to eq(inbox.id)
end
it 'returns the inbox if assigned inbox is assigned as agent' do
create(:inbox_member, user: agent, inbox: inbox)
get "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(JSON.parse(response.body, symbolize_names: true)[:id]).to eq(inbox.id)
end
end
end
describe 'GET /api/v1/accounts/{account.id}/inboxes/{inbox.id}/assignable_agents' do
let(:inbox) { create(:inbox, account: account) }

View File

@@ -2,7 +2,7 @@ require 'rails_helper'
RSpec.describe 'Public Inbox Contact Conversation Messages API', type: :request do
let!(:api_channel) { create(:channel_api) }
let!(:contact) { create(:contact) }
let!(:contact) { create(:contact, phone_number: '+324234324', email: 'dfsadf@sfsda.com') }
let!(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: api_channel.inbox) }
let!(:conversation) { create(:conversation, contact: contact, contact_inbox: contact_inbox) }

View File

@@ -3,9 +3,15 @@
FactoryBot.define do
factory :contact do
sequence(:name) { |n| "Contact #{n}" }
sequence(:email) { |n| "contact-#{n}@example.com" }
phone_number { Faker::PhoneNumber.cell_phone_in_e164 }
avatar { fixture_file_upload(Rails.root.join('spec/assets/avatar.png'), 'image/png') }
account
trait :with_email do
sequence(:email) { |n| "contact-#{n}@example.com" }
end
trait :with_phone_number do
phone_number { Faker::PhoneNumber.cell_phone_in_e164 }
end
end
end

View File

@@ -13,7 +13,7 @@ FactoryBot.define do
account: conversation.account,
channel: create(:channel_widget, account: conversation.account)
)
conversation.contact ||= create(:contact, account: conversation.account)
conversation.contact ||= create(:contact, :with_email, account: conversation.account)
conversation.contact_inbox ||= create(:contact_inbox, contact: conversation.contact, inbox: conversation.inbox)
end
end

View File

@@ -2,7 +2,7 @@ require 'rails_helper'
describe Contacts::ContactableInboxesService do
let(:account) { create(:account) }
let(:contact) { create(:contact, account: account) }
let(:contact) { create(:contact, account: account, email: 'contact@example.com', phone_number: '+2320000') }
let!(:twilio_sms) { create(:channel_twilio_sms, account: account) }
let!(:twilio_sms_inbox) { create(:inbox, channel: twilio_sms, account: account) }
let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) }

View File

@@ -34,7 +34,7 @@ describe Twilio::OneoffSmsCampaignService do
end
it 'send messages to contacts in the audience and marks the campaign completed' do
contact_with_label1, contact_with_label2, contact_with_both_labels = FactoryBot.create_list(:contact, 3, account: account)
contact_with_label1, contact_with_label2, contact_with_both_labels = FactoryBot.create_list(:contact, 3, :with_phone_number, account: account)
contact_with_label1.update_labels([label1.title])
contact_with_label2.update_labels([label2.title])
contact_with_both_labels.update_labels([label1.title, label2.title])

View File

@@ -15,3 +15,6 @@ properties:
identifier:
type: string
description: A unique identifier for the contact in external system
custom_attributes:
type: object
description: An object where you can store custom attributes for contact. example {"type":"customer", "age":30}

View File

@@ -12,3 +12,7 @@ properties:
identifier:
type: string
description: A unique identifier for the contact in external system
custom_attributes:
type: object
description: An object where you can store custom attributes for contact. example {"type":"customer", "age":30}

View File

@@ -2,8 +2,10 @@ get:
tags:
- Contact
operationId: contactConversations
summary: Conversations
summary: Contact Conversations
description: Get conversations associated to that contact
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number

View File

@@ -3,7 +3,9 @@ get:
- Contact
operationId: contactDetails
summary: Show Contact
description: Get a contact belonging to the account using ID
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number
@@ -24,7 +26,9 @@ put:
- Contact
operationId: contactUpdate
summary: Update Contact
description: Update a contact belonging to the account using ID
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number

View File

@@ -2,9 +2,10 @@ get:
tags:
- Contact
operationId: contactList
description: Listing all the contacts with pagination (Page size = 15)
description: Listing all the resolved contacts with pagination (Page size = 15) . Resolved contacts are the ones with a value for identifier, email or phone number
summary: List Contacts
parameters:
- $ref: '#/parameters/account_id'
- $ref: '#/parameters/contact_sort_param'
- $ref: '#/parameters/page'
responses:
@@ -21,9 +22,10 @@ post:
tags:
- Contact
operationId: contactCreate
description: Create New Contact
description: Create a new Contact
summary: Create Contact
parameters:
- $ref: '#/parameters/account_id'
- name: data
in: body
required: true

View File

@@ -2,9 +2,10 @@ get:
tags:
- Contact
operationId: contactSearch
description: Search the contacts using a search key, currently supports email search (Page size = 15)
description: Search the resolved contacts using a search key, currently supports email search (Page size = 15). Resolved contacts are the ones with a value for identifier, email or phone number
summary: Search Contacts
parameters:
- $ref: '#/parameters/account_id'
- name: q
in: query
type: string

View File

@@ -5,6 +5,12 @@ post:
description: Create a contact inbox record for an inbox
summary: Create contact inbox
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number
description: ID of the contact
required: true
- name: data
in: body
required: true

View File

@@ -4,6 +4,13 @@ get:
operationId: contactableInboxesGet
description: Get List of contactable Inboxes
summary: Get Contactable Inboxes
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number
description: ID of the contact
required: true
responses:
200:
description: Success

View File

@@ -5,6 +5,7 @@ post:
summary: Create an inbox
description: You can create more than one website inbox in each account
parameters:
- $ref: '#/parameters/account_id'
- name: data
in: body
required: true

View File

@@ -5,6 +5,7 @@ get:
summary: Show Inbox Agent Bot
description: See if an agent bot is associated to the Inbox
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number

View File

@@ -4,6 +4,8 @@ get:
operationId: listAllInboxes
summary: List all inboxes
description: List all inboxes available in the current account
parameters:
- $ref: '#/parameters/account_id'
responses:
200:
description: Success

View File

@@ -5,6 +5,7 @@ post:
summary: Add or remove agent bot
description: To add an agent bot pass agent_bot id, to remove agent bot from an inbox pass null
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number

View File

@@ -0,0 +1,22 @@
get:
tags:
- Inbox
operationId: GetInboxe
summary: Get an inbox
description: Get an inbox available in the current account
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number
description: ID of the inbox
required: true
responses:
200:
description: Success
schema:
$ref: '#/definitions/inbox'
404:
description: Inbox not found
403:
description: Access denied

View File

@@ -5,6 +5,7 @@ patch:
summary: Update Inbox
description: Add avatar and disable auto assignment for an inbox
parameters:
- $ref: '#/parameters/account_id'
- name: id
in: path
type: number

View File

@@ -198,6 +198,8 @@ public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversat
# Inboxes
/api/v1/accounts/{account_id}/inboxes:
$ref: ./inboxes/index.yml
/api/v1/accounts/{account_id}/inboxes/{id}/:
$ref: ./inboxes/show.yml
/api/v1/accounts/{account_id}/inboxes/:
$ref: ./inboxes/create.yml
/api/v1/accounts/{account_id}/inboxes/{id}:

View File

@@ -1109,9 +1109,12 @@
"Contact"
],
"operationId": "contactList",
"description": "Listing all the contacts with pagination (Page size = 15)",
"description": "Listing all the resolved contacts with pagination (Page size = 15) . Resolved contacts are the ones with a value for identifier, email or phone number",
"summary": "List Contacts",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"$ref": "#/parameters/contact_sort_param"
},
@@ -1139,9 +1142,12 @@
"Contact"
],
"operationId": "contactCreate",
"description": "Create New Contact",
"description": "Create a new Contact",
"summary": "Create Contact",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "data",
"in": "body",
@@ -1174,7 +1180,11 @@
],
"operationId": "contactDetails",
"summary": "Show Contact",
"description": "Get a contact belonging to the account using ID",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
@@ -1204,7 +1214,11 @@
],
"operationId": "contactUpdate",
"summary": "Update Contact",
"description": "Update a contact belonging to the account using ID",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
@@ -1243,8 +1257,12 @@
"Contact"
],
"operationId": "contactConversations",
"summary": "Conversations",
"summary": "Contact Conversations",
"description": "Get conversations associated to that contact",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
@@ -1275,9 +1293,12 @@
"Contact"
],
"operationId": "contactSearch",
"description": "Search the contacts using a search key, currently supports email search (Page size = 15)",
"description": "Search the resolved contacts using a search key, currently supports email search (Page size = 15). Resolved contacts are the ones with a value for identifier, email or phone number",
"summary": "Search Contacts",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "q",
"in": "query",
@@ -1321,6 +1342,16 @@
"description": "Create a contact inbox record for an inbox",
"summary": "Create contact inbox",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
"type": "number",
"description": "ID of the contact",
"required": true
},
{
"name": "data",
"in": "body",
@@ -1367,6 +1398,18 @@
"operationId": "contactableInboxesGet",
"description": "Get List of contactable Inboxes",
"summary": "Get Contactable Inboxes",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
"type": "number",
"description": "ID of the contact",
"required": true
}
],
"responses": {
"200": {
"description": "Success",
@@ -1847,6 +1890,11 @@
"operationId": "listAllInboxes",
"summary": "List all inboxes",
"description": "List all inboxes available in the current account",
"parameters": [
{
"$ref": "#/parameters/account_id"
}
],
"responses": {
"200": {
"description": "Success",
@@ -1867,6 +1915,42 @@
}
}
},
"/api/v1/accounts/{account_id}/inboxes/{id}/": {
"get": {
"tags": [
"Inbox"
],
"operationId": "GetInboxe",
"summary": "Get an inbox",
"description": "Get an inbox available in the current account",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
"type": "number",
"description": "ID of the inbox",
"required": true
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/inbox"
}
},
"404": {
"description": "Inbox not found"
},
"403": {
"description": "Access denied"
}
}
}
},
"/api/v1/accounts/{account_id}/inboxes/": {
"post": {
"tags": [
@@ -1876,6 +1960,9 @@
"summary": "Create an inbox",
"description": "You can create more than one website inbox in each account",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "data",
"in": "body",
@@ -1951,6 +2038,9 @@
"summary": "Update Inbox",
"description": "Add avatar and disable auto assignment for an inbox",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
@@ -2033,6 +2123,9 @@
"summary": "Show Inbox Agent Bot",
"description": "See if an agent bot is associated to the Inbox",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
@@ -2066,6 +2159,9 @@
"summary": "Add or remove agent bot",
"description": "To add an agent bot pass agent_bot id, to remove agent bot from an inbox pass null",
"parameters": [
{
"$ref": "#/parameters/account_id"
},
{
"name": "id",
"in": "path",
@@ -3522,6 +3618,10 @@
"identifier": {
"type": "string",
"description": "A unique identifier for the contact in external system"
},
"custom_attributes": {
"type": "object",
"description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}"
}
}
},
@@ -3543,6 +3643,10 @@
"identifier": {
"type": "string",
"description": "A unique identifier for the contact in external system"
},
"custom_attributes": {
"type": "object",
"description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}"
}
}
},