Merge branch 'develop' into feat/5913-search-improvements

This commit is contained in:
Tejaswini Chile 2022-12-18 21:07:27 +05:30 committed by GitHub
commit ff8c0654c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 429 additions and 55 deletions

View file

@ -18,6 +18,10 @@ class Api::V1::ProfilesController < Api::BaseController
head :ok
end
def auto_offline
@user.account_users.find_by!(account_id: auto_offline_params[:account_id]).update!(auto_offline: auto_offline_params[:auto_offline] || false)
end
def availability
@user.account_users.find_by!(account_id: availability_params[:account_id]).update!(availability: availability_params[:availability])
end
@ -37,6 +41,10 @@ class Api::V1::ProfilesController < Api::BaseController
params.require(:profile).permit(:account_id, :availability)
end
def auto_offline_params
params.require(:profile).permit(:account_id, :auto_offline)
end
def profile_params
params.require(:profile).permit(
:email,

View file

@ -144,6 +144,12 @@ export default {
});
},
updateAutoOffline(accountId, autoOffline = false) {
return axios.post(endPoints('autoOffline').url, {
profile: { account_id: accountId, auto_offline: autoOffline },
});
},
deleteAvatar() {
return axios.delete(endPoints('deleteAvatar').url);
},

View file

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

View file

@ -18,12 +18,35 @@
</woot-button>
</woot-dropdown-item>
<woot-dropdown-divider />
<woot-dropdown-item class="auto-offline--toggle">
<div class="info-wrap">
<fluent-icon
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
icon="info"
size="14"
class="info-icon"
/>
<span class="auto-offline--text">
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
</span>
</div>
<woot-switch
size="small"
class="auto-offline--switch"
:value="currentUserAutoOffline"
@input="updateAutoOffline"
/>
</woot-dropdown-item>
<woot-dropdown-divider />
</woot-dropdown-menu>
</template>
<script>
import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway';
import alertMixin from 'shared/mixins/alertMixin';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader';
@ -41,7 +64,7 @@ export default {
AvailabilityStatusBadge,
},
mixins: [clickaway],
mixins: [clickaway, alertMixin],
data() {
return {
@ -54,6 +77,7 @@ export default {
...mapGetters({
getCurrentUserAvailability: 'getCurrentUserAvailability',
currentAccountId: 'getCurrentAccountId',
currentUserAutoOffline: 'getCurrentUserAutoOffline',
}),
availabilityDisplayLabel() {
const availabilityIndex = AVAILABILITY_STATUS_KEYS.findIndex(
@ -85,21 +109,30 @@ export default {
closeStatusMenu() {
this.isStatusMenuOpened = false;
},
updateAutoOffline(autoOffline) {
this.$store.dispatch('updateAutoOffline', {
accountId: this.currentAccountId,
autoOffline,
});
},
changeAvailabilityStatus(availability) {
const accountId = this.currentAccountId;
if (this.isUpdating) {
return;
}
this.isUpdating = true;
this.$store
.dispatch('updateAvailability', {
availability: availability,
account_id: accountId,
})
.finally(() => {
this.isUpdating = false;
try {
this.$store.dispatch('updateAvailability', {
availability,
account_id: this.currentAccountId,
});
} catch (error) {
this.showAlert(
this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.SET_AVAILABILITY_ERROR')
);
} finally {
this.isUpdating = false;
}
},
},
};
@ -143,4 +176,32 @@ export default {
align-items: baseline;
}
}
.auto-offline--toggle {
align-items: center;
display: flex;
justify-content: space-between;
padding: var(--space-smaller) 0 var(--space-smaller) var(--space-small);
margin: 0;
.info-wrap {
display: flex;
align-items: center;
}
.info-icon {
margin-top: -1px;
}
.auto-offline--switch {
margin: -1px var(--space-micro) 0;
}
.auto-offline--text {
margin: 0 var(--space-smaller);
font-size: var(--font-size-mini);
font-weight: var(--font-weight-medium);
color: var(--s-700);
}
}
</style>

View file

@ -135,7 +135,7 @@ export default {
.dropdown-pane {
left: var(--space-slab);
bottom: var(--space-larger);
min-width: 16.8rem;
z-index: var(--z-index-much-higher);
min-width: 22rem;
z-index: var(--z-index-low);
}
</style>

View file

@ -199,8 +199,8 @@ export default {
&.smooth {
background: transparent;
border: 1px solid var(--s-75);
color: var(--s-800);
border: 1px solid var(--s-100);
color: var(--s-700);
}
}

View file

@ -2,7 +2,7 @@
<button
type="button"
class="toggle-button"
:class="{ active: value }"
:class="{ active: value, small: size === 'small' }"
role="switch"
:aria-checked="value.toString()"
@click="onClick"
@ -15,6 +15,7 @@
export default {
props: {
value: { type: Boolean, default: false },
size: { type: String, default: '' },
},
methods: {
onClick() {
@ -45,6 +46,20 @@ export default {
background-color: var(--w-500);
}
&.small {
width: 22px;
height: 14px;
span {
height: var(--space-one);
width: var(--space-one);
&.active {
transform: translate(var(--space-small), var(--space-zero));
}
}
}
span {
--space-one-point-five: 1.5rem;
background-color: var(--white);

View file

@ -91,6 +91,7 @@
</span>
<span class="unread">{{ unreadCount > 9 ? '9+' : unreadCount }}</span>
</div>
<card-labels :conversation-id="chat.id" />
</div>
<woot-context-menu
v-if="showContextMenu"
@ -125,8 +126,8 @@ import InboxName from '../InboxName';
import inboxMixin from 'shared/mixins/inboxMixin';
import ConversationContextMenu from './contextMenu/Index.vue';
import alertMixin from 'shared/mixins/alertMixin';
import timeAgo from 'dashboard/components/ui/TimeAgo';
import TimeAgo from 'dashboard/components/ui/TimeAgo';
import CardLabels from './conversationCardComponents/CardLabels.vue';
const ATTACHMENT_ICONS = {
image: 'image',
audio: 'headphones-sound-wave',
@ -138,10 +139,11 @@ const ATTACHMENT_ICONS = {
export default {
components: {
CardLabels,
InboxName,
Thumbnail,
ConversationContextMenu,
timeAgo,
TimeAgo,
},
mixins: [
@ -370,11 +372,15 @@ export default {
</script>
<style lang="scss" scoped>
.conversation {
align-items: center;
align-items: flex-start;
&:hover {
background: var(--color-background-light);
}
&::v-deep .user-thumbnail-box {
margin-top: var(--space-normal);
}
}
.conversation-selected {
@ -383,8 +389,10 @@ export default {
.has-inbox-name {
&::v-deep .user-thumbnail-box {
margin-top: var(--space-normal);
align-items: flex-start;
margin-top: var(--space-large);
}
.checkbox-wrapper {
margin-top: var(--space-large);
}
.conversation--meta {
margin-top: var(--space-normal);
@ -429,6 +437,7 @@ export default {
margin-top: var(--space-minus-micro);
vertical-align: middle;
}
.checkbox-wrapper {
height: 40px;
width: 40px;
@ -438,6 +447,7 @@ export default {
border-radius: 100%;
margin-top: var(--space-normal);
cursor: pointer;
&:hover {
background-color: var(--w-100);
}

View file

@ -0,0 +1,133 @@
<template>
<div
v-show="activeLabels.length"
ref="labelContainer"
class="label-container"
>
<div class="labels-wrap" :class="{ expand: showAllLabels }">
<woot-label
v-for="(label, index) in activeLabels"
:key="label.id"
:title="label.title"
:description="label.description"
:color="label.color"
variant="smooth"
small
:class="{ hidden: !showAllLabels && index > labelPosition }"
/>
<woot-button
v-if="showExpandLabelButton"
:title="
showAllLabels
? $t('CONVERSATION.CARD.HIDE_LABELS')
: $t('CONVERSATION.CARD.SHOW_LABELS')
"
class="show-more--button"
color-scheme="secondary"
variant="hollow"
:icon="showAllLabels ? 'chevron-left' : 'chevron-right'"
size="tiny"
@click="onShowLabels"
/>
</div>
</div>
</template>
<script>
import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin';
export default {
mixins: [conversationLabelMixin],
props: {
conversationId: {
type: Number,
required: true,
},
},
data() {
return {
showAllLabels: false,
showExpandLabelButton: false,
labelPosition: -1,
};
},
watch: {
activeLabels() {
this.$nextTick(() => this.computeVisibleLabelPosition());
},
},
mounted() {
this.computeVisibleLabelPosition();
},
methods: {
onShowLabels(e) {
e.stopPropagation();
this.showAllLabels = !this.showAllLabels;
},
computeVisibleLabelPosition() {
const labelContainer = this.$refs.labelContainer;
const labels = this.$refs.labelContainer.querySelectorAll('.label');
let labelOffset = 0;
Array.from(labels).forEach((label, index) => {
labelOffset += label.offsetWidth + 8;
if (labelOffset < labelContainer.clientWidth - 16) {
this.labelPosition = index;
} else {
this.showExpandLabelButton = true;
}
});
},
},
};
</script>
<style lang="scss" scoped>
.show-more--button {
height: var(--space-medium);
position: sticky;
flex-shrink: 0;
margin-right: var(--space-medium);
&.secondary:focus {
color: var(--s-700);
border-color: var(--s-300);
}
}
.label-container {
margin-top: var(--space-micro);
}
.labels-wrap {
display: flex;
align-items: center;
min-width: 0;
flex-shrink: 1;
&.expand {
height: auto;
overflow: visible;
flex-flow: row wrap;
.label {
margin-bottom: var(--space-smaller);
}
.show-more--button {
margin-bottom: var(--space-smaller);
}
}
.secondary {
border: 1px solid var(--s-100);
}
.label {
margin-bottom: 0;
}
}
.hidden {
visibility: hidden;
position: absolute;
}
</style>

View file

@ -41,6 +41,10 @@
"NO_RESPONSE": "No response",
"RATING_TITLE": "Rating",
"FEEDBACK_TITLE": "Feedback",
"CARD": {
"SHOW_LABELS": "Show labels",
"HIDE_LABELS": "Hide labels"
},
"HEADER": {
"RESOLVE_ACTION": "Resolve",
"REOPEN_ACTION": "Reopen",

View file

@ -99,7 +99,9 @@
},
"AVAILABILITY": {
"LABEL": "Availability",
"STATUSES_LIST": ["Online", "Busy", "Offline"]
"STATUSES_LIST": ["Online", "Busy", "Offline"],
"SET_AVAILABILITY_SUCCESS": "Availability has been set successfully",
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again"
},
"EMAIL": {
"LABEL": "Your email address",
@ -222,6 +224,10 @@
"CATEGORY": "Category",
"CATEGORY_EMPTY_MESSAGE": "No categories found"
},
"SET_AUTO_OFFLINE": {
"TEXT": "Mark offline automatically",
"INFO_TEXT": "Let the system automatically mark you offline when you aren't using the app or dashboard."
},
"DOCS": "Read docs"
},
"BILLING_SETTINGS": {

View file

@ -40,6 +40,7 @@
:portals="portals"
:active-portal-slug="selectedPortalSlug"
:active-locale="selectedLocaleInPortal"
@fetch-portal="fetchPortalAndItsCategories"
@close-popover="closePortalPopover"
/>
<add-category
@ -226,12 +227,6 @@ export default {
},
},
watch: {
'$route.params.portalSlug'() {
this.fetchPortalsAndItsCategories();
},
},
mounted() {
window.addEventListener('resize', this.handleResize);
this.handleResize();
@ -240,7 +235,7 @@ export default {
const slug = this.$route.params.portalSlug;
if (slug) this.lastActivePortalSlug = slug;
this.fetchPortalsAndItsCategories();
this.fetchPortalAndItsCategories();
},
beforeDestroy() {
bus.$off(BUS_EVENTS.TOGGLE_SIDEMENU, this.toggleSidebar);
@ -267,7 +262,7 @@ export default {
toggleSidebar() {
this.isSidebarOpen = !this.isSidebarOpen;
},
async fetchPortalsAndItsCategories() {
async fetchPortalAndItsCategories() {
await this.$store.dispatch('portals/index');
const selectedPortalParam = {
portalSlug: this.selectedPortalSlug,

View file

@ -248,7 +248,7 @@ export default {
this.$emit('open-site', this.portal.slug);
},
openSettings() {
this.fetchPortalsAndItsCategories();
this.fetchPortalAndItsCategories();
this.navigateToPortalEdit();
},
onClickOpenDeleteModal(portal) {
@ -258,12 +258,18 @@ export default {
closeDeletePopup() {
this.showDeleteConfirmationPopup = false;
},
fetchPortalsAndItsCategories() {
this.$store.dispatch('portals/index').then(() => {
this.$store.dispatch('categories/index', {
portalSlug: this.portal.slug,
});
});
async fetchPortalAndItsCategories() {
await this.$store.dispatch('portals/index');
const {
slug,
config: { allowed_locales: allowedLocales },
} = this.portal;
const selectedPortalParam = {
portalSlug: slug,
locale: allowedLocales[0].code,
};
this.$store.dispatch('portals/show', selectedPortalParam);
this.$store.dispatch('categories/index', selectedPortalParam);
},
async onClickDeletePortal() {
const { slug } = this.selectedPortalForDelete;

View file

@ -36,7 +36,8 @@
:active-portal-slug="activePortalSlug"
:active-locale="activeLocale"
:active="portal.slug === activePortalSlug"
@open-portal-page="onPortalSelect"
@open-portal-page="closePortalPopover"
@fetch-portal="fetchPortalAndItsCategories"
/>
</div>
</div>
@ -69,15 +70,15 @@ export default {
closePortalPopover() {
this.$emit('close-popover');
},
onPortalSelect() {
this.$emit('close-popover');
},
openPortalPage() {
this.closePortalPopover();
this.$router.push({
name: 'list_all_portals',
});
},
fetchPortalAndItsCategories() {
this.$emit('fetch-portal');
},
},
};
</script>

View file

@ -16,7 +16,7 @@
{{ $t('HELP_CENTER.PORTAL.ADD.LOGO.LABEL') }}
</label>
<div class="logo-container">
<thumbnail :username="name" size="56" variant="square" />
<thumbnail :username="name" size="56px" variant="square" />
<woot-button
v-if="false"
class="upload-button"

View file

@ -121,6 +121,7 @@ export default {
locale: code,
},
});
this.$emit('fetch-portal');
this.$emit('open-portal-page');
},
isLocaleActive(code, slug) {

View file

@ -33,7 +33,7 @@
</header>
<div class="category-list">
<category-list-item
:categories="categoryByLocaleCode"
:categories="categoriesByLocaleCode"
@delete="deleteCategory"
@edit="openEditCategoryModal"
/>
@ -91,7 +91,7 @@ export default {
currentPortalSlug() {
return this.$route.params.portalSlug;
},
categoryByLocaleCode() {
categoriesByLocaleCode() {
return this.$store.getters['categories/categoriesByLocaleCode'](
this.currentLocaleCode
);
@ -131,6 +131,12 @@ export default {
closeEditCategoryModal() {
this.showEditCategoryModal = false;
},
async fetchCategoriesByPortalSlugAndLocale(localeCode) {
await this.$store.dispatch('categories/index', {
portalSlug: this.currentPortalSlug,
locale: localeCode,
});
},
async deleteCategory(categoryId) {
try {
await this.$store.dispatch('categories/delete', {
@ -152,6 +158,7 @@ export default {
changeCurrentCategory(event) {
const localeCode = event.target.value;
this.currentLocaleCode = localeCode;
this.fetchCategoriesByPortalSlugAndLocale(localeCode);
},
},
};

View file

@ -48,6 +48,14 @@ export const getters = {
return currentAccount.availability;
},
getCurrentUserAutoOffline($state, $getters) {
const { accounts = [] } = $state.currentUser;
const [currentAccount = {}] = accounts.filter(
account => account.id === $getters.getCurrentAccountId
);
return currentAccount.auto_offline;
},
getCurrentAccountId(_, __, rootState) {
if (rootState.route.params && rootState.route.params.accountId) {
return Number(rootState.route.params.accountId);
@ -174,6 +182,15 @@ export const actions = {
}
},
updateAutoOffline: async ({ commit }, { accountId, autoOffline }) => {
try {
const response = await authAPI.updateAutoOffline(accountId, autoOffline);
commit(types.SET_CURRENT_USER, response.data);
} catch (error) {
// Ignore error
}
},
setCurrentUserAvailability({ commit, state: $state }, data) {
if (data[$state.currentUser.id]) {
commit(types.SET_CURRENT_USER_AVAILABILITY, data[$state.currentUser.id]);

View file

@ -41,11 +41,11 @@ export const actions = {
portalSlug,
articleObj,
});
const { id: articleId, portal } = payload;
const { id: articleId } = payload;
commit(types.ADD_ARTICLE, payload);
commit(types.ADD_ARTICLE_ID, articleId);
commit(types.ADD_ARTICLE_FLAG, articleId);
dispatch('portals/updatePortal', portal, { root: true });
dispatch('portals/updatePortal', portalSlug, { root: true });
return articleId;
} catch (error) {
return throwErrorMessage(error);

View file

@ -88,6 +88,38 @@ describe('#actions', () => {
});
});
describe('#updateAutoOffline', () => {
it('sends correct actions if API is success', async () => {
axios.post.mockResolvedValue({
data: {
id: 1,
name: 'John',
accounts: [
{
account_id: 1,
auto_offline: false,
},
],
},
headers: { expiry: 581842904 },
});
await actions.updateAutoOffline(
{ commit, dispatch },
{ autoOffline: false, accountId: 1 }
);
expect(commit.mock.calls).toEqual([
[
types.default.SET_CURRENT_USER,
{
id: 1,
name: 'John',
accounts: [{ account_id: 1, auto_offline: false }],
},
],
]);
});
});
describe('#updateUISettings', () => {
it('sends correct actions if API is success', async () => {
axios.put.mockResolvedValue({

View file

@ -1,6 +1,7 @@
<template>
<li class="dropdown-menu--header" :tabindex="null" :aria-disabled="true">
<span class="title">{{ title }}</span>
<slot />
</li>
</template>
<script>

View file

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

View file

@ -77,4 +77,3 @@ fullcontact:
settings_form_schema:
[{ 'label': 'API Key', 'type': 'text', 'name': 'api_key',"validation": "required", }]
visible_properties: ['api_key']

View file

@ -183,6 +183,7 @@ Rails.application.routes.draw do
delete :avatar, on: :collection
member do
post :availability
post :auto_offline
put :set_active_account
end
end

View file

@ -22,13 +22,23 @@ class Integrations::Slack::IncomingMessageBuilder
private
def valid_event?
supported_event_type? && supported_event?
supported_event_type? && supported_event? && should_process_event?
end
def supported_event_type?
SUPPORTED_EVENT_TYPES.include?(params[:type])
end
# Discard all the subtype of a message event
# We are only considering the actual message sent by a Slack user
# Any reactions or messages sent by the bot will be ignored.
# https://api.slack.com/events/message#subtypes
def should_process_event?
return true if params[:type] != 'event_callback'
params[:event][:user].present? && params[:event][:subtype].blank?
end
def supported_event?
hook_verification? || SUPPORTED_EVENTS.include?(params[:event][:type])
end

View file

@ -36,12 +36,13 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService
if conversation.identifier.present?
"#{private_indicator}#{message.content}"
else
"*Inbox: #{message.inbox.name} [#{message.inbox.inbox_type}]* \n\n #{message.content}"
"\n*Inbox:* #{message.inbox.name} (#{message.inbox.inbox_type})\n\n#{message.content}"
end
end
def avatar_url(sender)
sender.try(:avatar_url) || "#{ENV.fetch('FRONTEND_URL', nil)}/admin/avatar_square.png"
sender_type = sender.instance_of?(Contact) ? 'contact' : 'user'
"#{ENV.fetch('FRONTEND_URL', nil)}/integrations/slack/#{sender_type}.png"
end
def send_message
@ -86,7 +87,7 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService
end
def sender_name(sender)
sender.try(:name) ? "#{sender_type(sender)}: #{sender.try(:name)}" : sender_type(sender)
sender.try(:name) ? "#{sender.try(:name)} (#{sender_type(sender)})" : sender_type(sender)
end
def sender_type(sender)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -196,6 +196,30 @@ RSpec.describe 'Profile API', type: :request do
end
end
describe 'POST /api/v1/profile/auto_offline' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
post '/api/v1/profile/auto_offline'
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 auto offline status' do
post '/api/v1/profile/auto_offline',
params: { profile: { auto_offline: false, account_id: account.id } },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response['accounts'].first['auto_offline']).to be(false)
end
end
end
describe 'PUT /api/v1/profile/set_active_account' do
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do

View file

@ -12,6 +12,24 @@ describe Integrations::Slack::IncomingMessageBuilder do
event_time: 1_588_623_033
}
end
let(:sub_type_message) do
{
team_id: 'TLST3048H',
api_app_id: 'A012S5UETV4',
event: message_event.merge({ type: 'message', subtype: 'bot_message' }),
type: 'event_callback',
event_time: 1_588_623_033
}
end
let(:message_without_user) do
{
team_id: 'TLST3048H',
api_app_id: 'A012S5UETV4',
event: message_event.merge({ type: 'message', user: nil }),
type: 'event_callback',
event_time: 1_588_623_033
}
end
let(:message_with_attachments) { slack_attachment_stub }
let(:message_without_thread_ts) { slack_message_stub_without_thread_ts }
let(:verification_params) { slack_url_verification_stub }
@ -81,6 +99,20 @@ describe Integrations::Slack::IncomingMessageBuilder do
expect(conversation.messages.count).to eql(messages_count)
end
it 'does not create message for message sub type events' do
messages_count = conversation.messages.count
builder = described_class.new(sub_type_message)
builder.perform
expect(conversation.messages.count).to eql(messages_count)
end
it 'does not create message if user is missing' do
messages_count = conversation.messages.count
builder = described_class.new(message_without_user)
builder.perform
expect(conversation.messages.count).to eql(messages_count)
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

View file

@ -28,8 +28,8 @@ describe Integrations::Slack::SendOnSlackService do
expect(slack_client).to receive(:chat_postMessage).with(
channel: hook.reference_id,
text: "*Inbox: #{inbox.name} [#{inbox.inbox_type}]* \n\n #{message.content}",
username: "Contact: #{message.sender.name}",
text: "\n*Inbox:* #{inbox.name} (#{inbox.inbox_type})\n\n#{message.content}",
username: "#{message.sender.name} (Contact)",
thread_ts: nil,
icon_url: anything
).and_return(slack_message)
@ -49,7 +49,7 @@ describe Integrations::Slack::SendOnSlackService do
expect(slack_client).to receive(:chat_postMessage).with(
channel: hook.reference_id,
text: message.content,
username: "Contact: #{message.sender.name}",
username: "#{message.sender.name} (Contact)",
thread_ts: conversation.identifier,
icon_url: anything
).and_return(slack_message)
@ -63,7 +63,7 @@ describe Integrations::Slack::SendOnSlackService do
expect(slack_client).to receive(:chat_postMessage).with(
channel: hook.reference_id,
text: message.content,
username: "Contact: #{message.sender.name}",
username: "#{message.sender.name} (Contact)",
thread_ts: conversation.identifier,
icon_url: anything
).and_return(slack_message)
@ -93,7 +93,7 @@ describe Integrations::Slack::SendOnSlackService do
expect(slack_client).to receive(:chat_postMessage).with(
channel: hook.reference_id,
text: message.content,
username: "Contact: #{message.sender.name}",
username: "#{message.sender.name} (Contact)",
thread_ts: conversation.identifier,
icon_url: anything
).and_raise(Slack::Web::Api::Errors::AccountInactive.new('Account disconnected'))