Merge branch 'develop' into feat/custom-attrs-automations

This commit is contained in:
Fayaz Ahmed 2022-05-12 13:40:11 +05:30 committed by GitHub
commit 97481f92b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 423 additions and 229 deletions

View file

@ -38,9 +38,12 @@ class DashboardController < ActionController::Base
end end
def app_config def app_config
{ APP_VERSION: Chatwoot.config[:version], {
APP_VERSION: Chatwoot.config[:version],
VAPID_PUBLIC_KEY: VapidService.public_key, VAPID_PUBLIC_KEY: VapidService.public_key,
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'), ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'),
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', '') } FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
FACEBOOK_API_VERSION: 'v13.0'
}
end end
end end

View file

@ -21,6 +21,10 @@ class Platform::Api::V1::UsersController < PlatformController
def update def update
@resource.assign_attributes(user_update_params) @resource.assign_attributes(user_update_params)
# We are using devise's reconfirmable flow for changing emails
# But in case of platform APIs we don't want user to go through this extra step
@resource.skip_reconfirmation! if user_update_params[:email].present?
@resource.save! @resource.save!
end end

View file

@ -17,30 +17,30 @@ module ReportHelper
end end
def conversations_count def conversations_count
(get_grouped_values scope.conversations).count (get_grouped_values scope.conversations.where(account_id: account.id)).count
end end
def incoming_messages_count def incoming_messages_count
(get_grouped_values scope.messages.incoming.unscope(:order)).count (get_grouped_values scope.messages.where(account_id: account.id).incoming.unscope(:order)).count
end end
def outgoing_messages_count def outgoing_messages_count
(get_grouped_values scope.messages.outgoing.unscope(:order)).count (get_grouped_values scope.messages.where(account_id: account.id).outgoing.unscope(:order)).count
end end
def resolutions_count def resolutions_count
(get_grouped_values scope.conversations.resolved).count (get_grouped_values scope.conversations.where(account_id: account.id).resolved).count
end end
def avg_first_response_time def avg_first_response_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response')) grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
grouped_reporting_events.average(:value) grouped_reporting_events.average(:value)
end end
def avg_resolution_time def avg_resolution_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved')) grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
grouped_reporting_events.average(:value) grouped_reporting_events.average(:value)
@ -48,7 +48,7 @@ module ReportHelper
def avg_resolution_time_summary def avg_resolution_time_summary
reporting_events = scope.reporting_events reporting_events = scope.reporting_events
.where(name: 'conversation_resolved', created_at: range) .where(name: 'conversation_resolved', account_id: account.id, created_at: range)
avg_rt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value) avg_rt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
return 0 if avg_rt.blank? return 0 if avg_rt.blank?
@ -58,7 +58,7 @@ module ReportHelper
def avg_first_response_time_summary def avg_first_response_time_summary
reporting_events = scope.reporting_events reporting_events = scope.reporting_events
.where(name: 'first_response', created_at: range) .where(name: 'first_response', account_id: account.id, created_at: range)
avg_frt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value) avg_frt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
return 0 if avg_frt.blank? return 0 if avg_frt.blank?

View file

@ -1,25 +0,0 @@
export const createMessengerScript = pageId => `
<script>
window.fbAsyncInit = function() {
FB.init({
appId: "${window.chatwootConfig.fbAppId}",
xfbml: true,
version: "v4.0"
});
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) { return; }
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<div class="fb-messengermessageus"
messenger_app_id="${window.chatwootConfig.fbAppId}"
page_id="${pageId}"
color="blue"
size="standard" >
</div>
`;

View file

@ -341,10 +341,6 @@
"AUTO_ASSIGNMENT_SUCCESS_MESSAGE": "Auto assignment updated successfully", "AUTO_ASSIGNMENT_SUCCESS_MESSAGE": "Auto assignment updated successfully",
"ERROR_MESSAGE": "Could not update widget color. Please try again later." "ERROR_MESSAGE": "Could not update widget color. Please try again later."
}, },
"AUTO_ASSIGNMENT": {
"ENABLED": "Enabled",
"DISABLED": "Disabled"
},
"EMAIL_COLLECT_BOX": { "EMAIL_COLLECT_BOX": {
"ENABLED": "Enabled", "ENABLED": "Enabled",
"DISABLED": "Disabled" "DISABLED": "Disabled"
@ -402,6 +398,8 @@
"MESSENGER_SUB_HEAD": "Place this button inside your body tag", "MESSENGER_SUB_HEAD": "Place this button inside your body tag",
"INBOX_AGENTS": "Agents", "INBOX_AGENTS": "Agents",
"INBOX_AGENTS_SUB_TEXT": "Add or remove agents from this inbox", "INBOX_AGENTS_SUB_TEXT": "Add or remove agents from this inbox",
"AGENT_ASSIGNMENT": "Conversation Assignment",
"AGENT_ASSIGNMENT_SUB_TEXT": "Update conversation assignment settings",
"UPDATE": "Update", "UPDATE": "Update",
"ENABLE_EMAIL_COLLECT_BOX": "Enable email collect box", "ENABLE_EMAIL_COLLECT_BOX": "Enable email collect box",
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Enable or disable email collect box on new conversation", "ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Enable or disable email collect box on new conversation",

View file

@ -189,21 +189,6 @@
</p> </p>
</label> </label>
<label class="medium-9 columns settings-item">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT') }}
<select v-model="autoAssignment">
<option :value="true">
{{ $t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.ENABLED') }}
</option>
<option :value="false">
{{ $t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.DISABLED') }}
</option>
</select>
<p class="help-text">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT_SUB_TEXT') }}
</p>
</label>
<label class="medium-9 columns settings-item"> <label class="medium-9 columns settings-item">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.ENABLE_CSAT') }} {{ $t('INBOX_MGMT.SETTINGS_POPUP.ENABLE_CSAT') }}
<select v-model="csatSurveyEnabled"> <select v-model="csatSurveyEnabled">
@ -316,114 +301,11 @@
<facebook-reauthorize v-if="isAFacebookInbox" :inbox-id="inbox.id" /> <facebook-reauthorize v-if="isAFacebookInbox" :inbox-id="inbox.id" />
</div> </div>
<!-- update agents in inbox -->
<div v-if="selectedTabKey === 'collaborators'" class="settings--content"> <div v-if="selectedTabKey === 'collaborators'" class="settings--content">
<settings-section <collaborators-page :inbox="inbox" />
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_AGENTS')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_AGENTS_SUB_TEXT')"
>
<multiselect
v-model="selectedAgents"
:options="agentList"
track-by="id"
label="name"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:hide-selected="true"
placeholder="Pick some"
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
:deselect-label="$t('FORMS.MULTISELECT.ENTER_TO_REMOVE')"
@select="$v.selectedAgents.$touch"
/>
<woot-submit-button
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
:loading="isAgentListUpdating"
@click="updateAgents"
/>
</settings-section>
</div> </div>
<div v-if="selectedTabKey === 'configuration'"> <div v-if="selectedTabKey === 'configuration'">
<div v-if="isATwilioChannel" class="settings--content"> <configuration-page :inbox="inbox" />
<settings-section
:title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.TITLE')"
:sub-title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.SUBTITLE')"
>
<woot-code
:script="inbox.callback_webhook_url"
lang="html"
></woot-code>
</settings-section>
</div>
<div v-else-if="isALineChannel" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.TITLE')"
:sub-title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.SUBTITLE')"
>
<woot-code
:script="inbox.callback_webhook_url"
lang="html"
></woot-code>
</settings-section>
</div>
<div v-else-if="isAWebWidgetInbox">
<div class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_HEADING')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_SUB_HEAD')"
>
<woot-code :script="inbox.web_widget_script"></woot-code>
</settings-section>
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_VERIFICATION')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_DESCRIPTION')"
>
<woot-code :script="inbox.hmac_token"></woot-code>
</settings-section>
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_MANDATORY_VERIFICATION')"
:sub-title="
$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_MANDATORY_DESCRIPTION')
"
>
<div class="enter-to-send--checkbox">
<input
id="hmacMandatory"
v-model="hmacMandatory"
type="checkbox"
@change="handleHmacFlag"
/>
<label for="hmacMandatory">
{{ $t('INBOX_MGMT.EDIT.ENABLE_HMAC.LABEL') }}
</label>
</div>
</settings-section>
</div>
</div>
<div v-else-if="isAPIInbox" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_IDENTIFIER')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_IDENTIFIER_SUB_TEXT')"
>
<woot-code :script="inbox.inbox_identifier"></woot-code>
</settings-section>
</div>
<div v-else-if="isAnEmailChannel">
<div class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.FORWARD_EMAIL_TITLE')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.FORWARD_EMAIL_SUB_TEXT')"
>
<woot-code :script="inbox.forward_to_email"></woot-code>
</settings-section>
</div>
<imap-settings :inbox="inbox" />
<smtp-settings v-if="inbox.imap_enabled" :inbox="inbox" />
</div>
</div> </div>
<div v-if="selectedTabKey === 'preChatForm'"> <div v-if="selectedTabKey === 'preChatForm'">
<pre-chat-form-settings :inbox="inbox" /> <pre-chat-form-settings :inbox="inbox" />
@ -436,7 +318,6 @@
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { createMessengerScript } from 'dashboard/helper/scriptGenerator';
import { required } from 'vuelidate/lib/validators'; import { required } from 'vuelidate/lib/validators';
import { shouldBeUrl } from 'shared/helpers/Validators'; import { shouldBeUrl } from 'shared/helpers/Validators';
import configMixin from 'shared/mixins/configMixin'; import configMixin from 'shared/mixins/configMixin';
@ -448,8 +329,8 @@ import FacebookReauthorize from './facebook/Reauthorize';
import PreChatFormSettings from './PreChatForm/Settings'; import PreChatFormSettings from './PreChatForm/Settings';
import WeeklyAvailability from './components/WeeklyAvailability'; import WeeklyAvailability from './components/WeeklyAvailability';
import GreetingsEditor from 'shared/components/GreetingsEditor'; import GreetingsEditor from 'shared/components/GreetingsEditor';
import ImapSettings from './ImapSettings'; import ConfigurationPage from './settingsPage/ConfigurationPage';
import SmtpSettings from './SmtpSettings'; import CollaboratorsPage from './settingsPage/CollaboratorsPage';
export default { export default {
components: { components: {
@ -459,22 +340,18 @@ export default {
PreChatFormSettings, PreChatFormSettings,
WeeklyAvailability, WeeklyAvailability,
GreetingsEditor, GreetingsEditor,
ImapSettings, ConfigurationPage,
SmtpSettings, CollaboratorsPage,
}, },
mixins: [alertMixin, configMixin, inboxMixin], mixins: [alertMixin, configMixin, inboxMixin],
data() { data() {
return { return {
avatarFile: null, avatarFile: null,
avatarUrl: '', avatarUrl: '',
selectedAgents: [],
greetingEnabled: true, greetingEnabled: true,
tweetsEnabled: true, tweetsEnabled: true,
hmacMandatory: null,
greetingMessage: '', greetingMessage: '',
autoAssignment: false,
emailCollectEnabled: false, emailCollectEnabled: false,
isAgentListUpdating: false,
csatSurveyEnabled: false, csatSurveyEnabled: false,
allowMessagesAfterResolved: true, allowMessagesAfterResolved: true,
continuityViaEmail: true, continuityViaEmail: true,
@ -485,22 +362,11 @@ export default {
channelWelcomeTagline: '', channelWelcomeTagline: '',
selectedFeatureFlags: [], selectedFeatureFlags: [],
replyTime: '', replyTime: '',
autoAssignmentOptions: [
{
value: true,
label: this.$t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.ENABLED'),
},
{
value: false,
label: this.$t('INBOX_MGMT.EDIT.AUTO_ASSIGNMENT.DISABLED'),
},
],
selectedTabIndex: 0, selectedTabIndex: 0,
}; };
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
agentList: 'agents/getAgents',
uiFlags: 'inboxes/getUIFlags', uiFlags: 'inboxes/getUIFlags',
}), }),
selectedTabKey() { selectedTabKey() {
@ -568,9 +434,6 @@ export default {
} }
return this.inbox.name; return this.inbox.name;
}, },
messengerScript() {
return createMessengerScript(this.inbox.page_id);
},
inboxNameLabel() { inboxNameLabel() {
if (this.isAWebWidgetInbox) { if (this.isAWebWidgetInbox) {
return this.$t('INBOX_MGMT.ADD.WEBSITE_NAME.LABEL'); return this.$t('INBOX_MGMT.ADD.WEBSITE_NAME.LABEL');
@ -610,9 +473,6 @@ export default {
e.target.value e.target.value
); );
}, },
handleHmacFlag() {
this.updateInbox();
},
toggleInput(selected, current) { toggleInput(selected, current) {
if (selected.includes(current)) { if (selected.includes(current)) {
const newSelectedFlags = selected.filter(flag => flag !== current); const newSelectedFlags = selected.filter(flag => flag !== current);
@ -630,15 +490,12 @@ export default {
this.$store.dispatch('teams/get'); this.$store.dispatch('teams/get');
this.$store.dispatch('labels/get'); this.$store.dispatch('labels/get');
this.$store.dispatch('inboxes/get').then(() => { this.$store.dispatch('inboxes/get').then(() => {
this.fetchAttachedAgents();
this.avatarUrl = this.inbox.avatar_url; this.avatarUrl = this.inbox.avatar_url;
this.selectedInboxName = this.inbox.name; this.selectedInboxName = this.inbox.name;
this.webhookUrl = this.inbox.webhook_url; this.webhookUrl = this.inbox.webhook_url;
this.greetingEnabled = this.inbox.greeting_enabled || false; this.greetingEnabled = this.inbox.greeting_enabled || false;
this.tweetsEnabled = this.inbox.tweets_enabled || false; this.tweetsEnabled = this.inbox.tweets_enabled || false;
this.hmacMandatory = this.inbox.hmac_mandatory || false;
this.greetingMessage = this.inbox.greeting_message || ''; this.greetingMessage = this.inbox.greeting_message || '';
this.autoAssignment = this.inbox.enable_auto_assignment;
this.emailCollectEnabled = this.inbox.enable_email_collect; this.emailCollectEnabled = this.inbox.enable_email_collect;
this.csatSurveyEnabled = this.inbox.csat_survey_enabled; this.csatSurveyEnabled = this.inbox.csat_survey_enabled;
this.allowMessagesAfterResolved = this.inbox.allow_messages_after_resolved; this.allowMessagesAfterResolved = this.inbox.allow_messages_after_resolved;
@ -650,39 +507,11 @@ export default {
this.replyTime = this.inbox.reply_time; this.replyTime = this.inbox.reply_time;
}); });
}, },
async fetchAttachedAgents() {
try {
const response = await this.$store.dispatch('inboxMembers/get', {
inboxId: this.currentInboxId,
});
const {
data: { payload: inboxMembers },
} = response;
this.selectedAgents = inboxMembers;
} catch (error) {
// Handle error
}
},
async updateAgents() {
const agentList = this.selectedAgents.map(el => el.id);
this.isAgentListUpdating = true;
try {
await this.$store.dispatch('inboxMembers/create', {
inboxId: this.currentInboxId,
agentList,
});
this.showAlert(this.$t('AGENT_MGMT.EDIT.API.SUCCESS_MESSAGE'));
} catch (error) {
this.showAlert(this.$t('AGENT_MGMT.EDIT.API.ERROR_MESSAGE'));
}
this.isAgentListUpdating = false;
},
async updateInbox() { async updateInbox() {
try { try {
const payload = { const payload = {
id: this.currentInboxId, id: this.currentInboxId,
name: this.selectedInboxName, name: this.selectedInboxName,
enable_auto_assignment: this.autoAssignment,
enable_email_collect: this.emailCollectEnabled, enable_email_collect: this.emailCollectEnabled,
csat_survey_enabled: this.csatSurveyEnabled, csat_survey_enabled: this.csatSurveyEnabled,
allow_messages_after_resolved: this.allowMessagesAfterResolved, allow_messages_after_resolved: this.allowMessagesAfterResolved,
@ -696,7 +525,6 @@ export default {
welcome_tagline: this.channelWelcomeTagline || '', welcome_tagline: this.channelWelcomeTagline || '',
selectedFeatureFlags: this.selectedFeatureFlags, selectedFeatureFlags: this.selectedFeatureFlags,
reply_time: this.replyTime || 'in_a_few_minutes', reply_time: this.replyTime || 'in_a_few_minutes',
hmac_mandatory: this.hmacMandatory,
tweets_enabled: this.tweetsEnabled, tweets_enabled: this.tweetsEnabled,
continuity_via_email: this.continuityViaEmail, continuity_via_email: this.continuityViaEmail,
}, },
@ -737,11 +565,6 @@ export default {
required, required,
shouldBeUrl, shouldBeUrl,
}, },
selectedAgents: {
isEmpty() {
return !!this.selectedAgents.length;
},
},
}, },
}; };
</script> </script>

View file

@ -163,7 +163,7 @@ export default {
FB.init({ FB.init({
appId: window.chatwootConfig.fbAppId, appId: window.chatwootConfig.fbAppId,
xfbml: true, xfbml: true,
version: 'v12.0', version: window.chatwootConfig.fbApiVersion,
status: true, status: true,
}); });
window.fbSDKLoaded = true; window.fbSDKLoaded = true;

View file

@ -40,7 +40,7 @@ export default {
FB.init({ FB.init({
appId: window.chatwootConfig.fbAppId, appId: window.chatwootConfig.fbAppId,
xfbml: true, xfbml: true,
version: 'v12.0', version: window.chatwootConfig.fbApiVersion,
status: true, status: true,
}); });
window.fbSDKLoaded = true; window.fbSDKLoaded = true;

View file

@ -0,0 +1,148 @@
<template>
<div>
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_AGENTS')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_AGENTS_SUB_TEXT')"
>
<multiselect
v-model="selectedAgents"
:options="agentList"
track-by="id"
label="name"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:hide-selected="true"
placeholder="Pick some"
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
:deselect-label="$t('FORMS.MULTISELECT.ENTER_TO_REMOVE')"
@select="$v.selectedAgents.$touch"
/>
<woot-submit-button
:button-text="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
:loading="isAgentListUpdating"
@click="updateAgents"
/>
</settings-section>
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.AGENT_ASSIGNMENT')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.AGENT_ASSIGNMENT_SUB_TEXT')"
>
<label class="medium-9 columns settings-item">
<div class="enter-to-send--checkbox">
<input
id="enableAutoAssignment"
v-model="enableAutoAssignment"
type="checkbox"
@change="handleEnableAutoAssignment"
/>
<label for="enableAutoAssignment">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT') }}
</label>
</div>
<p class="help-text">
{{ $t('INBOX_MGMT.SETTINGS_POPUP.AUTO_ASSIGNMENT_SUB_TEXT') }}
</p>
</label>
</settings-section>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import SettingsSection from '../../../../../components/SettingsSection';
export default {
components: {
SettingsSection,
},
mixins: [alertMixin],
props: {
inbox: {
type: Object,
default: () => ({}),
},
},
data() {
return {
selectedAgents: [],
isAgentListUpdating: false,
enableAutoAssignment: false,
};
},
computed: {
...mapGetters({
agentList: 'agents/getAgents',
}),
},
watch: {
inbox() {
this.setDefaults();
},
},
mounted() {
this.setDefaults();
},
methods: {
setDefaults() {
this.enableAutoAssignment = this.inbox.enable_auto_assignment;
this.fetchAttachedAgents();
},
async fetchAttachedAgents() {
try {
const response = await this.$store.dispatch('inboxMembers/get', {
inboxId: this.inbox.id,
});
const {
data: { payload: inboxMembers },
} = response;
this.selectedAgents = inboxMembers;
} catch (error) {
// Handle error
}
},
handleEnableAutoAssignment() {
this.updateInbox();
},
async updateAgents() {
const agentList = this.selectedAgents.map(el => el.id);
this.isAgentListUpdating = true;
try {
await this.$store.dispatch('inboxMembers/create', {
inboxId: this.inbox.id,
agentList,
});
this.showAlert(this.$t('AGENT_MGMT.EDIT.API.SUCCESS_MESSAGE'));
} catch (error) {
this.showAlert(this.$t('AGENT_MGMT.EDIT.API.ERROR_MESSAGE'));
}
this.isAgentListUpdating = false;
},
async updateInbox() {
try {
const payload = {
id: this.inbox.id,
formData: false,
enable_auto_assignment: this.enableAutoAssignment,
};
await this.$store.dispatch('inboxes/updateInbox', payload);
this.showAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
} catch (error) {
this.showAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
}
},
},
validations: {
selectedAgents: {
isEmpty() {
return !!this.selectedAgents.length;
},
},
},
};
</script>

View file

@ -0,0 +1,130 @@
<template>
<div v-if="isATwilioChannel" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.TITLE')"
:sub-title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.SUBTITLE')"
>
<woot-code :script="inbox.callback_webhook_url" lang="html"></woot-code>
</settings-section>
</div>
<div v-else-if="isALineChannel" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.TITLE')"
:sub-title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.SUBTITLE')"
>
<woot-code :script="inbox.callback_webhook_url" lang="html"></woot-code>
</settings-section>
</div>
<div v-else-if="isAWebWidgetInbox">
<div class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_HEADING')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.MESSENGER_SUB_HEAD')"
>
<woot-code :script="inbox.web_widget_script"></woot-code>
</settings-section>
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_VERIFICATION')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_DESCRIPTION')"
>
<woot-code :script="inbox.hmac_token"></woot-code>
</settings-section>
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_MANDATORY_VERIFICATION')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.HMAC_MANDATORY_DESCRIPTION')"
>
<div class="enter-to-send--checkbox">
<input
id="hmacMandatory"
v-model="hmacMandatory"
type="checkbox"
@change="handleHmacFlag"
/>
<label for="hmacMandatory">
{{ $t('INBOX_MGMT.EDIT.ENABLE_HMAC.LABEL') }}
</label>
</div>
</settings-section>
</div>
</div>
<div v-else-if="isAPIInbox" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_IDENTIFIER')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_IDENTIFIER_SUB_TEXT')"
>
<woot-code :script="inbox.inbox_identifier"></woot-code>
</settings-section>
</div>
<div v-else-if="isAnEmailChannel">
<div class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.SETTINGS_POPUP.FORWARD_EMAIL_TITLE')"
:sub-title="$t('INBOX_MGMT.SETTINGS_POPUP.FORWARD_EMAIL_SUB_TEXT')"
>
<woot-code :script="inbox.forward_to_email"></woot-code>
</settings-section>
</div>
<imap-settings :inbox="inbox" />
<smtp-settings v-if="inbox.imap_enabled" :inbox="inbox" />
</div>
</template>
<script>
import alertMixin from 'shared/mixins/alertMixin';
import inboxMixin from 'shared/mixins/inboxMixin';
import SettingsSection from '../../../../../components/SettingsSection';
import ImapSettings from '../ImapSettings';
import SmtpSettings from '../SmtpSettings';
export default {
components: {
SettingsSection,
ImapSettings,
SmtpSettings,
},
mixins: [inboxMixin, alertMixin],
props: {
inbox: {
type: Object,
default: () => ({}),
},
},
data() {
return {
hmacMandatory: false,
};
},
watch: {
inbox() {
this.setDefaults();
},
},
mounted() {
this.setDefaults();
},
methods: {
setDefaults() {
this.hmacMandatory = this.inbox.hmac_mandatory || false;
},
handleHmacFlag() {
this.updateInbox();
},
async updateInbox() {
try {
const payload = {
id: this.inbox.id,
formData: false,
channel: {
hmac_mandatory: this.hmacMandatory,
},
};
await this.$store.dispatch('inboxes/updateInbox', payload);
this.showAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
} catch (error) {
this.showAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
}
},
},
};
</script>

View file

@ -22,7 +22,7 @@ class Instagram::MessageText < Instagram::WebhooksBaseService
return unsend_message if message_is_deleted? return unsend_message if message_is_deleted?
ensure_contact(contact_id) ensure_contact(contact_id) if contacts_first_message?(contact_id)
create_message create_message
end end
@ -36,7 +36,7 @@ class Instagram::MessageText < Instagram::WebhooksBaseService
rescue Koala::Facebook::AuthenticationError rescue Koala::Facebook::AuthenticationError
@inbox.channel.authorization_error! @inbox.channel.authorization_error!
raise raise
rescue StandardError => e rescue StandardError, Koala::Facebook::ClientError => e
result = {} result = {}
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
end end
@ -52,6 +52,10 @@ class Instagram::MessageText < Instagram::WebhooksBaseService
@messaging[:message][:is_deleted].present? @messaging[:message][:is_deleted].present?
end end
def contacts_first_message?(ig_scope_id)
@inbox.contact_inboxes.where(source_id: ig_scope_id).empty? && @inbox.channel.instagram_id.present?
end
def unsend_message def unsend_message
message_to_delete = @inbox.messages.find_by( message_to_delete = @inbox.messages.find_by(
source_id: @messaging[:message][:mid] source_id: @messaging[:message][:mid]

View file

@ -34,6 +34,7 @@
window.chatwootConfig = { window.chatwootConfig = {
hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>', hostURL: '<%= ENV.fetch('FRONTEND_URL', '') %>',
fbAppId: '<%= ENV.fetch('FB_APP_ID', nil) %>', fbAppId: '<%= ENV.fetch('FB_APP_ID', nil) %>',
fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>',
signupEnabled: '<%= @global_config['ENABLE_ACCOUNT_SIGNUP'] %>', signupEnabled: '<%= @global_config['ENABLE_ACCOUNT_SIGNUP'] %>',
<% if @global_config['VAPID_PUBLIC_KEY'] %> <% if @global_config['VAPID_PUBLIC_KEY'] %>
vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(@global_config['VAPID_PUBLIC_KEY']).bytes %>), vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(@global_config['VAPID_PUBLIC_KEY']).bytes %>),

View file

@ -37,7 +37,7 @@ class Integrations::Slack::IncomingMessageBuilder
if message.present? if message.present?
SUPPORTED_MESSAGE_TYPES.include?(message[:type]) && !attached_file_message? SUPPORTED_MESSAGE_TYPES.include?(message[:type]) && !attached_file_message?
else else
params[:event][:files].any? && !attached_file_message? params[:event][:files].present? && !attached_file_message?
end end
end end

View file

@ -17,6 +17,7 @@ class Seeders::AccountSeeder
def seed! def seed!
seed_canned_responses seed_canned_responses
seed_inboxes
end end
def seed_canned_responses(count: 50) def seed_canned_responses(count: 50)
@ -24,4 +25,65 @@ class Seeders::AccountSeeder
account.canned_responses.create(content: Faker::Quote.fortune_cookie, short_code: Faker::Alphanumeric.alpha(number: 10)) account.canned_responses.create(content: Faker::Quote.fortune_cookie, short_code: Faker::Alphanumeric.alpha(number: 10))
end end
end end
def seed_inboxes
seed_website_inbox
seed_facebook_inbox
seed_twitter_inbox
seed_whatsapp_inbox
seed_sms_inbox
seed_email_inbox
seed_api_inbox
seed_telegram_inbox
seed_line_inbox
end
def seed_website_inbox
channel = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc')
Inbox.create!(channel: channel, account: account, name: 'Acme Website')
end
def seed_facebook_inbox
channel = Channel::FacebookPage.create!(account: account, user_access_token: 'test', page_access_token: 'test', page_id: 'test')
Inbox.create!(channel: channel, account: account, name: 'Acme Facebook')
end
def seed_twitter_inbox
channel = Channel::TwitterProfile.create!(account: account, twitter_access_token: 'test', twitter_access_token_secret: 'test', profile_id: '123')
Inbox.create!(channel: channel, account: account, name: 'Acme Twitter')
end
def seed_whatsapp_inbox
channel = Channel::Whatsapp.create!(account: account, phone_number: '+123456789')
Inbox.create!(channel: channel, account: account, name: 'Acme Whatsapp')
end
def seed_sms_inbox
channel = Channel::Sms.create!(account: account, phone_number: '+123456789')
Inbox.create!(channel: channel, account: account, name: 'Acme SMS')
end
def seed_email_inbox
channel = Channel::Email.create!(account: account, email: 'test@acme.inc', forward_to_email: 'test_fwd@acme.inc')
Inbox.create!(channel: channel, account: account, name: 'Acme Email')
end
def seed_api_inbox
channel = Channel::Api.create!(account: account)
Inbox.create!(channel: channel, account: account, name: 'Acme API')
end
def seed_telegram_inbox
# rubocop:disable Rails/SkipsModelValidations
Channel::Telegram.insert({ account_id: account.id, bot_name: 'Acme', bot_token: 'test', created_at: Time.now.utc, updated_at: Time.now.utc },
returning: %w[id])
channel = Channel::Telegram.find_by(bot_token: 'test')
Inbox.create!(channel: channel, account: account, name: 'Acme Telegram')
# rubocop:enable Rails/SkipsModelValidations
end
def seed_line_inbox
channel = Channel::Line.create!(account: account, line_channel_id: 'test', line_channel_secret: 'test', line_channel_token: 'test')
Inbox.create!(channel: channel, account: account, name: 'Acme Line')
end
end end

View file

@ -223,6 +223,40 @@ RSpec.describe 'Reports API', type: :request do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
end end
end end
context 'when an agent has access to multiple accounts' do
let(:account1) { create(:account) }
let(:account2) { create(:account) }
let(:params) do
super().merge(
type: :agent,
since: 30.days.ago.to_i.to_s,
until: date_timestamp.to_s
)
end
it 'returns agent metrics from the current account' do
admin1 = create(:user, account: account1, role: :administrator)
inbox1 = create(:inbox, account: account1)
inbox2 = create(:inbox, account: account2)
create(:account_user, user: admin1, account: account2)
create(:conversation, account: account1, inbox: inbox1,
assignee: admin1, created_at: Time.zone.today - 2.days)
create(:conversation, account: account2, inbox: inbox2,
assignee: admin1, created_at: Time.zone.today - 2.days)
get "/api/v2/accounts/#{account1.id}/reports/summary",
params: params.merge({ id: admin1.id }),
headers: admin1.create_new_auth_token
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response['conversations_count']).to eq(1)
end
end
end end
describe 'GET /api/v2/accounts/:account_id/reports/inboxes' do describe 'GET /api/v2/accounts/:account_id/reports/inboxes' do

View file

@ -145,14 +145,17 @@ RSpec.describe 'Platform Users API', type: :request do
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
end end
it 'updates the user' do it 'updates the user attributes' do
create(:platform_app_permissible, platform_app: platform_app, permissible: user) create(:platform_app_permissible, platform_app: platform_app, permissible: user)
patch "/platform/api/v1/users/#{user.id}", params: { name: 'test123', custom_attributes: { test: 'test_update' } }, patch "/platform/api/v1/users/#{user.id}", params: {
name: 'test123', email: 'newtestemail@test.com', custom_attributes: { test: 'test_update' }
},
headers: { api_access_token: platform_app.access_token.token }, as: :json headers: { api_access_token: platform_app.access_token.token }, as: :json
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
data = JSON.parse(response.body) data = JSON.parse(response.body)
expect(data['name']).to eq('test123') expect(data['name']).to eq('test123')
expect(data['email']).to eq('newtestemail@test.com')
expect(data['custom_attributes']['test']).to eq('test_update') expect(data['custom_attributes']['test']).to eq('test_update')
end end
end end

View file

@ -61,6 +61,15 @@ describe Integrations::Slack::IncomingMessageBuilder do
expect(conversation.messages.count).to eql(messages_count) expect(conversation.messages.count).to eql(messages_count)
end end
it 'does not create message for invalid event type and event files is not present' do
messages_count = conversation.messages.count
message_with_attachments[:event][:files] = nil
builder = described_class.new(message_with_attachments)
allow(builder).to receive(:sender).and_return(nil)
builder.perform
expect(conversation.messages.count).to eql(messages_count)
end
it 'saves attachment if params files present' do it 'saves attachment if params files present' do
expect(hook).not_to eq nil expect(hook).not_to eq nil
messages_count = conversation.messages.count messages_count = conversation.messages.count