+
-
-
-
-
diff --git a/app/javascript/dashboard/featureFlags.js b/app/javascript/dashboard/featureFlags.js
index 2936d22ea..f2fd3e757 100644
--- a/app/javascript/dashboard/featureFlags.js
+++ b/app/javascript/dashboard/featureFlags.js
@@ -19,4 +19,5 @@ export const FEATURE_FLAGS = {
INSERT_ARTICLE_IN_REPLY: 'insert_article_in_reply',
INBOX_VIEW: 'inbox_view',
SLA: 'sla',
+ RESPONSE_BOT: 'response_bot',
};
diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json
index a6d476bc9..56ca21773 100644
--- a/app/javascript/dashboard/i18n/locale/en/report.json
+++ b/app/javascript/dashboard/i18n/locale/en/report.json
@@ -35,6 +35,14 @@
"NAME": "Resolution Count",
"DESC": "( Total )"
},
+ "BOT_RESOLUTION_COUNT": {
+ "NAME": "Resolution Count",
+ "DESC": "( Total )"
+ },
+ "BOT_HANDOFF_COUNT": {
+ "NAME": "Handoff Count",
+ "DESC": "( Total )"
+ },
"REPLY_TIME": {
"NAME": "Customer waiting time",
"TOOLTIP_TEXT": "Waiting time is %{metricValue} (based on %{conversationCount} replies)"
@@ -86,20 +94,49 @@
"MONTH": "Month",
"YEAR": "Year"
},
- "GROUP_BY_DAY_OPTIONS": [{ "id": 1, "groupBy": "Day" }],
+ "GROUP_BY_DAY_OPTIONS": [
+ {
+ "id": 1,
+ "groupBy": "Day"
+ }
+ ],
"GROUP_BY_WEEK_OPTIONS": [
- { "id": 1, "groupBy": "Day" },
- { "id": 2, "groupBy": "Week" }
+ {
+ "id": 1,
+ "groupBy": "Day"
+ },
+ {
+ "id": 2,
+ "groupBy": "Week"
+ }
],
"GROUP_BY_MONTH_OPTIONS": [
- { "id": 1, "groupBy": "Day" },
- { "id": 2, "groupBy": "Week" },
- { "id": 3, "groupBy": "Month" }
+ {
+ "id": 1,
+ "groupBy": "Day"
+ },
+ {
+ "id": 2,
+ "groupBy": "Week"
+ },
+ {
+ "id": 3,
+ "groupBy": "Month"
+ }
],
"GROUP_BY_YEAR_OPTIONS": [
- { "id": 2, "groupBy": "Week" },
- { "id": 3, "groupBy": "Month" },
- { "id": 4, "groupBy": "Year" }
+ {
+ "id": 2,
+ "groupBy": "Week"
+ },
+ {
+ "id": 3,
+ "groupBy": "Month"
+ },
+ {
+ "id": 4,
+ "groupBy": "Year"
+ }
],
"BUSINESS_HOURS": "Business Hours"
},
@@ -404,6 +441,27 @@
}
}
},
+ "BOT_REPORTS": {
+ "HEADER": "Bot Reports",
+ "METRIC": {
+ "TOTAL_CONVERSATIONS": {
+ "LABEL": "No. of Conversations",
+ "TOOLTIP": "Total number of conversations handled by the bot"
+ },
+ "TOTAL_RESPONSES": {
+ "LABEL": "Total Responses",
+ "TOOLTIP": "Total number of responses sent by the bot"
+ },
+ "RESOLUTION_RATE": {
+ "LABEL": "Resolution Rate",
+ "TOOLTIP": "Total number of conversations resolved by the bot / Total number of conversations handled by the bot * 100"
+ },
+ "HANDOFF_RATE": {
+ "LABEL": "Handoff Rate",
+ "TOOLTIP": "Total number of conversations handed off to agents / Total number of conversations handled by the bot * 100"
+ }
+ }
+ },
"OVERVIEW_REPORTS": {
"HEADER": "Overview",
"LIVE": "Live",
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index 63725f949..9a4bde2c8 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -234,6 +234,7 @@
"CAMPAIGNS": "Campaigns",
"ONGOING": "Ongoing",
"ONE_OFF": "One off",
+ "REPORTS_BOT": "Bot",
"REPORTS_AGENT": "Agents",
"REPORTS_LABEL": "Labels",
"REPORTS_INBOX": "Inbox",
diff --git a/app/javascript/dashboard/mixins/reportMixin.js b/app/javascript/dashboard/mixins/reportMixin.js
index 2b8a5f87d..d57af1ad2 100644
--- a/app/javascript/dashboard/mixins/reportMixin.js
+++ b/app/javascript/dashboard/mixins/reportMixin.js
@@ -2,11 +2,19 @@ import { mapGetters } from 'vuex';
import { formatTime } from '@chatwoot/utils';
export default {
+ props: {
+ accountSummaryKey: {
+ type: String,
+ default: 'getAccountSummary',
+ },
+ },
computed: {
...mapGetters({
- accountSummary: 'getAccountSummary',
accountReport: 'getAccountReports',
}),
+ accountSummary() {
+ return this.$store.getters[this.accountSummaryKey];
+ },
},
methods: {
calculateTrend(key) {
diff --git a/app/javascript/dashboard/mixins/specs/reportMixin.spec.js b/app/javascript/dashboard/mixins/specs/reportMixin.spec.js
index d981de4e9..c0bc1e15f 100644
--- a/app/javascript/dashboard/mixins/specs/reportMixin.spec.js
+++ b/app/javascript/dashboard/mixins/specs/reportMixin.spec.js
@@ -11,11 +11,42 @@ describe('reportMixin', () => {
beforeEach(() => {
getters = {
getAccountSummary: () => reportFixtures.summary,
+ getBotSummary: () => reportFixtures.botSummary,
getAccountReports: () => reportFixtures.report,
};
store = new Vuex.Store({ getters });
});
+ it('display the metric for account', async () => {
+ const Component = {
+ render() {},
+ title: 'TestComponent',
+ mixins: [reportMixin],
+ };
+ const wrapper = shallowMount(Component, { store, localVue });
+ await wrapper.setProps({
+ accountSummaryKey: 'getAccountSummary',
+ });
+ expect(wrapper.vm.displayMetric('conversations_count')).toEqual('5,000');
+ expect(wrapper.vm.displayMetric('avg_first_response_time')).toEqual(
+ '3 Min 18 Sec'
+ );
+ });
+
+ it('display the metric for bot', async () => {
+ const Component = {
+ render() {},
+ title: 'TestComponent',
+ mixins: [reportMixin],
+ };
+ const wrapper = shallowMount(Component, { store, localVue });
+ await wrapper.setProps({
+ accountSummaryKey: 'getBotSummary',
+ });
+ expect(wrapper.vm.displayMetric('bot_resolutions_count')).toEqual('10');
+ expect(wrapper.vm.displayMetric('bot_handoffs_count')).toEqual('20');
+ });
+
it('display the metric', () => {
const Component = {
render() {},
diff --git a/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js b/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
index ab6b6fecf..591bf7c1f 100644
--- a/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
+++ b/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
@@ -15,6 +15,14 @@ export default {
},
resolutions_count: 3,
},
+ botSummary: {
+ bot_resolutions_count: 10,
+ bot_handoffs_count: 20,
+ previous: {
+ bot_resolutions_count: 8,
+ bot_handoffs_count: 5,
+ },
+ },
report: {
data: [
{ value: '0.00', timestamp: 1647541800, count: 0 },
diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue
index 8eb7201ae..99cee0325 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue
@@ -74,6 +74,7 @@
import { required, minLength } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
+import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
export default {
mixins: [alertMixin],
@@ -125,10 +126,9 @@ export default {
});
this.errorMessage = this.$t('PROFILE_SETTINGS.PASSWORD_UPDATE_SUCCESS');
} catch (error) {
- this.errorMessage = this.$t('RESET_PASSWORD.API.ERROR_MESSAGE');
- if (error?.response?.data?.message) {
- this.errorMessage = error.response.data.message;
- }
+ this.errorMessage =
+ parseAPIErrorResponse(error) ||
+ this.$t('RESET_PASSWORD.API.ERROR_MESSAGE');
} finally {
this.isPasswordChanging = false;
this.showAlert(this.errorMessage);
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue
new file mode 100644
index 000000000..a75051aaf
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
index c601b4277..273f814b7 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
@@ -7,7 +7,7 @@
:key="metric.KEY"
class="p-4 rounded-md mb-3"
>
-
+
+import { ref, watch, onMounted } from 'vue';
+import ReportMetricCard from './ReportMetricCard.vue';
+import ReportsAPI from 'dashboard/api/reports';
+
+const props = defineProps({
+ filters: {
+ type: Object,
+ required: true,
+ },
+});
+
+const conversationCount = ref('0');
+const messageCount = ref('0');
+const resolutionRate = ref('0');
+const handoffRate = ref('0');
+
+const formatToPercent = value => {
+ return value ? `${value}%` : '--';
+};
+
+const fetchMetrics = () => {
+ if (!props.filters.to || !props.filters.from) {
+ return;
+ }
+ ReportsAPI.getBotMetrics(props.filters).then(response => {
+ conversationCount.value = response.data.conversation_count.toLocaleString();
+ messageCount.value = response.data.message_count.toLocaleString();
+ resolutionRate.value = response.data.resolution_rate.toString();
+ handoffRate.value = response.data.handoff_rate.toString();
+ });
+};
+
+watch(() => props.filters, fetchMetrics, { deep: true });
+
+onMounted(fetchMetrics);
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue
index 94fe1359d..4faf4de2b 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue
@@ -1,6 +1,8 @@
-
-
{{ metric.NAME }}
+
+
+ {{ metric.NAME }}
+
{{ displayMetric(metric.KEY) }}
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetrics.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetrics.vue
index 5fe1fa358..77106e8d5 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetrics.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetrics.vue
@@ -6,17 +6,20 @@
:label="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL')"
:info-text="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP')"
:value="responseCount"
+ class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"
/>
import('./LabelReports.vue');
const InboxReports = () => import('./InboxReports.vue');
const TeamReports = () => import('./TeamReports.vue');
const CsatResponses = () => import('./CsatResponses.vue');
+const BotReports = () => import('./BotReports.vue');
const LiveReports = () => import('./LiveReports.vue');
export default {
@@ -66,6 +67,23 @@ export default {
},
],
},
+ {
+ path: frontendURL('accounts/:accountId/reports'),
+ component: SettingsContent,
+ props: {
+ headerTitle: 'BOT_REPORTS.HEADER',
+ icon: 'bot',
+ keepAlive: false,
+ },
+ children: [
+ {
+ path: 'bot',
+ name: 'bot_reports',
+ roles: ['administrator'],
+ component: BotReports,
+ },
+ ],
+ },
{
path: frontendURL('accounts/:accountId/reports'),
component: SettingsContent,
diff --git a/app/javascript/dashboard/store/modules/reports.js b/app/javascript/dashboard/store/modules/reports.js
index b529730fe..c4068b54f 100644
--- a/app/javascript/dashboard/store/modules/reports.js
+++ b/app/javascript/dashboard/store/modules/reports.js
@@ -19,6 +19,8 @@ const state = {
avg_first_response_time: false,
avg_resolution_time: false,
resolutions_count: false,
+ bot_resolutions_count: false,
+ bot_handoffs_count: false,
reply_time: false,
},
data: {
@@ -28,6 +30,8 @@ const state = {
avg_first_response_time: [],
avg_resolution_time: [],
resolutions_count: [],
+ bot_resolutions_count: [],
+ bot_handoffs_count: [],
reply_time: [],
},
},
@@ -39,6 +43,13 @@ const state = {
outgoing_messages_count: 0,
reply_time: 0,
resolutions_count: 0,
+ bot_resolutions_count: 0,
+ bot_handoffs_count: 0,
+ previous: {},
+ },
+ botSummary: {
+ bot_resolutions_count: 0,
+ bot_handoffs_count: 0,
previous: {},
},
overview: {
@@ -60,6 +71,9 @@ const getters = {
getAccountSummary(_state) {
return _state.accountSummary;
},
+ getBotSummary(_state) {
+ return _state.botSummary;
+ },
getAccountConversationMetric(_state) {
return _state.overview.accountConversationMetric;
},
@@ -125,6 +139,20 @@ export const actions = {
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
});
},
+ fetchBotSummary({ commit }, reportObj) {
+ Report.getBotSummary({
+ from: reportObj.from,
+ to: reportObj.to,
+ groupBy: reportObj.groupBy,
+ businessHours: reportObj.businessHours,
+ })
+ .then(botSummary => {
+ commit(types.default.SET_BOT_SUMMARY, botSummary.data);
+ })
+ .catch(() => {
+ commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
+ });
+ },
fetchAccountConversationMetric({ commit }, reportObj) {
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, true);
Report.getConversationMetric(reportObj.type)
@@ -243,6 +271,9 @@ const mutations = {
[types.default.SET_ACCOUNT_SUMMARY](_state, summaryData) {
_state.accountSummary = summaryData;
},
+ [types.default.SET_BOT_SUMMARY](_state, summaryData) {
+ _state.botSummary = summaryData;
+ },
[types.default.SET_ACCOUNT_CONVERSATION_METRIC](_state, metricData) {
_state.overview.accountConversationMetric = metricData;
},
diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js
index c9d2d1767..6f7d36cf7 100644
--- a/app/javascript/dashboard/store/mutation-types.js
+++ b/app/javascript/dashboard/store/mutation-types.js
@@ -166,6 +166,7 @@ export default {
SET_HEATMAP_DATA: 'SET_HEATMAP_DATA',
TOGGLE_HEATMAP_LOADING: 'TOGGLE_HEATMAP_LOADING',
SET_ACCOUNT_SUMMARY: 'SET_ACCOUNT_SUMMARY',
+ SET_BOT_SUMMARY: 'SET_BOT_SUMMARY',
TOGGLE_ACCOUNT_REPORT_LOADING: 'TOGGLE_ACCOUNT_REPORT_LOADING',
SET_ACCOUNT_CONVERSATION_METRIC: 'SET_ACCOUNT_CONVERSATION_METRIC',
TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING:
diff --git a/app/models/contact.rb b/app/models/contact.rb
index 1f3d2fb90..a60e9f4d2 100644
--- a/app/models/contact.rb
+++ b/app/models/contact.rb
@@ -61,7 +61,7 @@ class Contact < ApplicationRecord
after_create_commit :dispatch_create_event, :ip_lookup
after_update_commit :dispatch_update_event
after_destroy_commit :dispatch_destroy_event
- before_save :update_contact_location_and_country_code
+ before_save :sync_contact_attributes
enum contact_type: { visitor: 0, lead: 1, customer: 2 }
@@ -207,11 +207,8 @@ class Contact < ApplicationRecord
self.custom_attributes = {} if custom_attributes.blank?
end
- def update_contact_location_and_country_code
- # TODO: Ensure that location and country_code are updated from additional_attributes.
- # We will remove this once all contacts are updated and both the location and country_code fields are standardized throughout the app.
- self.location = additional_attributes['city']
- self.country_code = additional_attributes['country']
+ def sync_contact_attributes
+ ::Contacts::SyncAttributes.new(self).perform
end
def dispatch_create_event
diff --git a/app/services/contacts/sync_attributes.rb b/app/services/contacts/sync_attributes.rb
new file mode 100644
index 000000000..bc10def66
--- /dev/null
+++ b/app/services/contacts/sync_attributes.rb
@@ -0,0 +1,37 @@
+class Contacts::SyncAttributes
+ attr_reader :contact
+
+ def initialize(contact)
+ @contact = contact
+ end
+
+ def perform
+ update_contact_location_and_country_code
+ set_contact_type
+ end
+
+ private
+
+ def update_contact_location_and_country_code
+ # Ensure that location and country_code are updated from additional_attributes.
+ # TODO: Remove this once all contacts are updated and both the location and country_code fields are standardized throughout the app.
+ @contact.location = @contact.additional_attributes['city']
+ @contact.country_code = @contact.additional_attributes['country']
+ end
+
+ def set_contact_type
+ # If the contact is already a lead or customer then do not change the contact type
+ return unless @contact.contact_type == 'visitor'
+ # If the contact has an email or phone number or social details( facebook_user_id, instagram_user_id, etc) then it is a lead
+ # If contact is from external channel like facebook, instagram, whatsapp, etc then it is a lead
+ return unless @contact.email.present? || @contact.phone_number.present? || social_details_present?
+
+ @contact.contact_type = 'lead'
+ end
+
+ def social_details_present?
+ @contact.additional_attributes.keys.any? do |key|
+ key.start_with?('social_') && @contact.additional_attributes[key].present?
+ end
+ end
+end
diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb
index d18994a00..a36231634 100644
--- a/app/services/telegram/incoming_message_service.rb
+++ b/app/services/telegram/incoming_message_service.rb
@@ -130,6 +130,7 @@ class Telegram::IncomingMessageService
@message.attachments.new(
account_id: @message.account_id,
file_type: :location,
+ fallback_title: location_fallback_title,
coordinates_lat: location['latitude'],
coordinates_long: location['longitude']
)
@@ -139,6 +140,16 @@ class Telegram::IncomingMessageService
@file ||= visual_media_params || params[:message][:voice].presence || params[:message][:audio].presence || params[:message][:document].presence
end
+ def location_fallback_title
+ return '' if venue.blank?
+
+ venue[:title] || ''
+ end
+
+ def venue
+ @venue ||= params.dig(:message, :venue).presence
+ end
+
def location
@location ||= params.dig(:message, :location).presence
end
diff --git a/spec/models/contact_spec.rb b/spec/models/contact_spec.rb
index ff186466a..2ca65fa4e 100644
--- a/spec/models/contact_spec.rb
+++ b/spec/models/contact_spec.rb
@@ -22,11 +22,13 @@ RSpec.describe Contact do
it 'sets email to lowercase' do
contact = create(:contact, email: 'Test@test.com')
expect(contact.email).to eq('test@test.com')
+ expect(contact.contact_type).to eq('lead')
end
it 'sets email to nil when empty string' do
contact = create(:contact, email: '')
expect(contact.email).to be_nil
+ expect(contact.contact_type).to eq('visitor')
end
it 'sets custom_attributes to {} when nil' do
@@ -83,4 +85,21 @@ RSpec.describe Contact do
expect(contact.country_code).to eq 'US'
end
end
+
+ context 'when a contact is created' do
+ it 'has contact type "visitor" by default' do
+ contact = create(:contact)
+ expect(contact.contact_type).to eq 'visitor'
+ end
+
+ it 'has contact type "lead" when email is present' do
+ contact = create(:contact, email: 'test@test.com')
+ expect(contact.contact_type).to eq 'lead'
+ end
+
+ it 'has contact type "lead" when contacted through a social channel' do
+ contact = create(:contact, additional_attributes: { social_facebook_user_id: '123' })
+ expect(contact.contact_type).to eq 'lead'
+ end
+ end
end
diff --git a/spec/services/contacts/sync_attributes_spec.rb b/spec/services/contacts/sync_attributes_spec.rb
new file mode 100644
index 000000000..447bcd740
--- /dev/null
+++ b/spec/services/contacts/sync_attributes_spec.rb
@@ -0,0 +1,46 @@
+# spec/services/contacts/sync_attributes_spec.rb
+
+require 'rails_helper'
+
+RSpec.describe Contacts::SyncAttributes do
+ describe '#perform' do
+ let(:contact) { create(:contact, additional_attributes: { 'city' => 'New York', 'country' => 'US' }) }
+
+ context 'when contact has neither email/phone number nor social details' do
+ it 'does not change contact type' do
+ described_class.new(contact).perform
+ expect(contact.reload.contact_type).to eq('visitor')
+ end
+ end
+
+ context 'when contact has email or phone number' do
+ it 'sets contact type to lead' do
+ contact.email = 'test@test.com'
+ contact.save
+ described_class.new(contact).perform
+
+ expect(contact.reload.contact_type).to eq('lead')
+ end
+ end
+
+ context 'when contact has social details' do
+ it 'sets contact type to lead' do
+ contact.additional_attributes['social_facebook_user_id'] = '123456789'
+ contact.save
+ described_class.new(contact).perform
+
+ expect(contact.reload.contact_type).to eq('lead')
+ end
+ end
+
+ context 'when location and country code are updated from additional attributes' do
+ it 'updates location and country code' do
+ described_class.new(contact).perform
+
+ # Expect location and country code to be updated
+ expect(contact.reload.location).to eq('New York')
+ expect(contact.reload.country_code).to eq('US')
+ end
+ end
+ end
+end
diff --git a/spec/services/telegram/incoming_message_service_spec.rb b/spec/services/telegram/incoming_message_service_spec.rb
index 795202894..3cade6343 100644
--- a/spec/services/telegram/incoming_message_service_spec.rb
+++ b/spec/services/telegram/incoming_message_service_spec.rb
@@ -255,6 +255,30 @@ describe Telegram::IncomingMessageService do
expect(Contact.all.first.name).to eq('Sojan Jose')
expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('location')
end
+
+ it 'creates appropriate conversations, message and contacts if venue is present' do
+ params = {
+ 'update_id' => 2_342_342_343_242,
+ 'message' => {
+ 'location': {
+ 'latitude': 37.7893768,
+ 'longitude': -122.3895553
+ },
+ venue: {
+ title: 'San Francisco'
+ }
+ }.merge(message_params)
+ }.with_indifferent_access
+ described_class.new(inbox: telegram_channel.inbox, params: params).perform
+ expect(telegram_channel.inbox.conversations.count).not_to eq(0)
+ expect(Contact.all.first.name).to eq('Sojan Jose')
+
+ attachment = telegram_channel.inbox.messages.first.attachments.first
+ expect(attachment.file_type).to eq('location')
+ expect(attachment.coordinates_lat).to eq(37.7893768)
+ expect(attachment.coordinates_long).to eq(-122.3895553)
+ expect(attachment.fallback_title).to eq('San Francisco')
+ end
end
context 'when valid callback_query params' do