feat: allow adding custom attributes to conversations from the SDK (#6782)
* feat: add conversation attributes method to sdk and widget app * feat: add endpoints to update custom attributes * refactor: update SDK api * feat: add api and actions for conversation updates * fix: error message * test: custom attributes on conversations controller --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -65,6 +65,16 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
|||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_custom_attributes
|
||||||
|
conversation.update!(custom_attributes: permitted_params[:custom_attributes])
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_custom_attributes
|
||||||
|
conversation.custom_attributes = conversation.custom_attributes.excluding(params[:custom_attribute])
|
||||||
|
conversation.save!
|
||||||
|
render json: conversation
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def trigger_typing_event(event)
|
def trigger_typing_event(event)
|
||||||
|
|||||||
@@ -114,6 +114,26 @@ const runSDK = ({ baseUrl, websiteToken }) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setConversationCustomAttributes(customAttributes = {}) {
|
||||||
|
if (!customAttributes || !Object.keys(customAttributes).length) {
|
||||||
|
throw new Error('Custom attributes should have atleast one key');
|
||||||
|
} else {
|
||||||
|
IFrameHelper.sendMessage('set-conversation-custom-attributes', {
|
||||||
|
customAttributes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteConversationCustomAttribute(customAttribute = '') {
|
||||||
|
if (!customAttribute) {
|
||||||
|
throw new Error('Custom attribute is required');
|
||||||
|
} else {
|
||||||
|
IFrameHelper.sendMessage('delete-conversation-custom-attribute', {
|
||||||
|
customAttribute,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
setLabel(label = '') {
|
setLabel(label = '') {
|
||||||
IFrameHelper.sendMessage('set-label', { label });
|
IFrameHelper.sendMessage('set-label', { label });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -274,6 +274,16 @@ export default {
|
|||||||
'contacts/deleteCustomAttribute',
|
'contacts/deleteCustomAttribute',
|
||||||
message.customAttribute
|
message.customAttribute
|
||||||
);
|
);
|
||||||
|
} else if (message.event === 'set-conversation-custom-attributes') {
|
||||||
|
this.$store.dispatch(
|
||||||
|
'conversation/setCustomAttributes',
|
||||||
|
message.customAttributes
|
||||||
|
);
|
||||||
|
} else if (message.event === 'delete-conversation-custom-attribute') {
|
||||||
|
this.$store.dispatch(
|
||||||
|
'conversation/deleteCustomAttribute',
|
||||||
|
message.customAttribute
|
||||||
|
);
|
||||||
} else if (message.event === 'set-locale') {
|
} else if (message.event === 'set-locale') {
|
||||||
this.setLocale(message.locale);
|
this.setLocale(message.locale);
|
||||||
this.setBubbleLabel();
|
this.setBubbleLabel();
|
||||||
|
|||||||
@@ -50,6 +50,24 @@ const toggleStatus = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setCustomAttributes = async customAttributes => {
|
||||||
|
return API.post(
|
||||||
|
`/api/v1/widget/conversations/set_custom_attributes${window.location.search}`,
|
||||||
|
{
|
||||||
|
custom_attributes: customAttributes,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteCustomAttribute = async customAttribute => {
|
||||||
|
return API.post(
|
||||||
|
`/api/v1/widget/conversations/destroy_custom_attributes${window.location.search}`,
|
||||||
|
{
|
||||||
|
custom_attribute: [customAttribute],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createConversationAPI,
|
createConversationAPI,
|
||||||
sendMessageAPI,
|
sendMessageAPI,
|
||||||
@@ -60,4 +78,6 @@ export {
|
|||||||
setUserLastSeenAt,
|
setUserLastSeenAt,
|
||||||
sendEmailTranscript,
|
sendEmailTranscript,
|
||||||
toggleStatus,
|
toggleStatus,
|
||||||
|
setCustomAttributes,
|
||||||
|
deleteCustomAttribute,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
toggleTyping,
|
toggleTyping,
|
||||||
setUserLastSeenAt,
|
setUserLastSeenAt,
|
||||||
toggleStatus,
|
toggleStatus,
|
||||||
|
setCustomAttributes,
|
||||||
|
deleteCustomAttribute,
|
||||||
} from 'widget/api/conversation';
|
} from 'widget/api/conversation';
|
||||||
|
|
||||||
import { createTemporaryMessage, getNonDeletedMessages } from './helpers';
|
import { createTemporaryMessage, getNonDeletedMessages } from './helpers';
|
||||||
@@ -139,4 +141,20 @@ export const actions = {
|
|||||||
resolveConversation: async () => {
|
resolveConversation: async () => {
|
||||||
await toggleStatus();
|
await toggleStatus();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setCustomAttributes: async (_, customAttributes = {}) => {
|
||||||
|
try {
|
||||||
|
await setCustomAttributes(customAttributes);
|
||||||
|
} catch (error) {
|
||||||
|
// IgnoreError
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteCustomAttribute: async (_, customAttribute) => {
|
||||||
|
try {
|
||||||
|
await deleteCustomAttribute(customAttribute);
|
||||||
|
} catch (error) {
|
||||||
|
// IgnoreError
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -226,6 +226,8 @@ Rails.application.routes.draw do
|
|||||||
resources :messages, only: [:index, :create, :update]
|
resources :messages, only: [:index, :create, :update]
|
||||||
resources :conversations, only: [:index, :create] do
|
resources :conversations, only: [:index, :create] do
|
||||||
collection do
|
collection do
|
||||||
|
post :destroy_custom_attributes
|
||||||
|
post :set_custom_attributes
|
||||||
post :update_last_seen
|
post :update_last_seen
|
||||||
post :toggle_typing
|
post :toggle_typing
|
||||||
post :transcript
|
post :transcript
|
||||||
|
|||||||
@@ -255,4 +255,59 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/widget/conversations/set_custom_attributes' do
|
||||||
|
let(:params) { { website_token: web_widget.website_token, custom_attributes: { 'product_name': 'Chatwoot' } } }
|
||||||
|
|
||||||
|
context 'with invalid website token' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post '/api/v1/widget/conversations/set_custom_attributes', params: { website_token: '' }
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with correct website token' do
|
||||||
|
it 'sets the values when provided' do
|
||||||
|
post '/api/v1/widget/conversations/set_custom_attributes',
|
||||||
|
headers: { 'X-Auth-Token' => token },
|
||||||
|
params: params,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
conversation.reload
|
||||||
|
# conversation custom attributes should have "product_name" key with value "Chatwoot"
|
||||||
|
expect(conversation.custom_attributes).to include('product_name' => 'Chatwoot')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/widget/conversations/destroy_custom_attributes' do
|
||||||
|
let(:params) { { website_token: web_widget.website_token, custom_attribute: ['product_name'] } }
|
||||||
|
|
||||||
|
context 'with invalid website token' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post '/api/v1/widget/conversations/destroy_custom_attributes', params: { website_token: '' }
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with correct website token' do
|
||||||
|
it 'sets the values when provided' do
|
||||||
|
# ensure conversation has the attribute
|
||||||
|
conversation.custom_attributes = { 'product_name': 'Chatwoot' }
|
||||||
|
conversation.save!
|
||||||
|
expect(conversation.custom_attributes).to include('product_name' => 'Chatwoot')
|
||||||
|
|
||||||
|
post '/api/v1/widget/conversations/destroy_custom_attributes',
|
||||||
|
headers: { 'X-Auth-Token' => token },
|
||||||
|
params: params,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
conversation.reload
|
||||||
|
# conversation custom attributes should not have "product_name" key with value "Chatwoot"
|
||||||
|
expect(conversation.custom_attributes).not_to include('product_name' => 'Chatwoot')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user