feat: Display "Snoozed Until" time on conversation header (#3028)

This commit is contained in:
Pranav Raj S 2021-09-29 19:33:51 +05:30 committed by GitHub
parent 49ac4a4400
commit 57abdc4d5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 217 additions and 172 deletions

View file

@ -14,6 +14,7 @@
@import 'helper-classes'; @import 'helper-classes';
@import 'formulate'; @import 'formulate';
@import 'date-picker'; @import 'date-picker';
@import 'utility-helpers';
@import 'foundation-sites/scss/foundation'; @import 'foundation-sites/scss/foundation';
@import '~bourbon/core/bourbon'; @import '~bourbon/core/bourbon';

View file

@ -42,14 +42,6 @@ $resolve-button-width: 13.2rem;
margin-right: var(--space-normal); margin-right: var(--space-normal);
min-width: 0; min-width: 0;
.user--name {
@include margin(0);
display: inline-block;
font-size: $font-size-medium;
line-height: 1.3;
text-transform: capitalize;
width: 100%;
}
.user--profile__meta { .user--profile__meta {
align-items: flex-start; align-items: flex-start;
@ -59,12 +51,6 @@ $resolve-button-width: 13.2rem;
margin-left: $space-slab; margin-left: $space-slab;
min-width: 0; min-width: 0;
} }
.user--profile__button {
font-size: $font-size-mini;
margin-top: $space-micro;
padding: 0;
}
} }
} }

View file

@ -0,0 +1,35 @@
<template>
<span class="inbox--name">
<i :class="computedInboxClass" />
{{ inbox.name }}
</span>
</template>
<script>
import { getInboxClassByType } from 'dashboard/helper/inbox';
export default {
props: {
inbox: {
type: Object,
default: () => {},
},
},
computed: {
computedInboxClass() {
const { phone_number: phoneNumber, channel_type: type } = this.inbox;
const classByType = getInboxClassByType(type, phoneNumber);
return classByType;
},
},
};
</script>
<style scoped>
.inbox--name {
padding: var(--space-micro) 0;
line-height: var(--space-slab);
font-weight: var(--font-weight-medium);
background: none;
color: var(--s-500);
font-size: var(--font-size-mini);
}
</style>

View file

@ -19,11 +19,7 @@
/> />
<div class="conversation--details columns"> <div class="conversation--details columns">
<div class="conversation--metadata"> <div class="conversation--metadata">
<span v-if="showInboxName" class="label"> <inbox-name v-if="showInboxName" :inbox="inbox" />
<i :class="computedInboxClass" />
{{ inboxName }}
</span>
<span <span
v-if="showAssignee && assignee" v-if="showAssignee && assignee"
class="label assignee-label text-truncate" class="label assignee-label text-truncate"
@ -72,16 +68,17 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { MESSAGE_TYPE } from 'widget/helpers/constants'; import { MESSAGE_TYPE } from 'widget/helpers/constants';
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin'; import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
import { getInboxClassByType } from 'dashboard/helper/inbox';
import Thumbnail from '../Thumbnail'; import Thumbnail from '../Thumbnail';
import conversationMixin from '../../../mixins/conversations'; import conversationMixin from '../../../mixins/conversations';
import timeMixin from '../../../mixins/time'; import timeMixin from '../../../mixins/time';
import router from '../../../routes'; import router from '../../../routes';
import { frontendURL, conversationUrl } from '../../../helper/URLHelper'; import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
import InboxName from '../InboxName';
import inboxMixin from 'shared/mixins/inboxMixin'; import inboxMixin from 'shared/mixins/inboxMixin';
export default { export default {
components: { components: {
InboxName,
Thumbnail, Thumbnail,
}, },
@ -192,12 +189,6 @@ export default {
return stateInbox; return stateInbox;
}, },
computedInboxClass() {
const { phone_number: phoneNumber, channel_type: type } = this.inbox;
const classByType = getInboxClassByType(type, phoneNumber);
return classByType;
},
showInboxName() { showInboxName() {
return ( return (
!this.hideInboxName && !this.hideInboxName &&
@ -244,15 +235,6 @@ export default {
} }
} }
.conversation--details .label {
padding: var(--space-micro) 0 var(--space-micro) 0;
line-height: var(--space-slab);
font-weight: var(--font-weight-medium);
background: none;
color: var(--s-500);
font-size: var(--font-size-mini);
}
.conversation--details { .conversation--details {
.conversation--user { .conversation--user {
padding-top: var(--space-micro); padding-top: var(--space-micro);
@ -276,6 +258,15 @@ export default {
justify-content: space-between; justify-content: space-between;
padding-right: var(--space-normal); padding-right: var(--space-normal);
.label {
padding: var(--space-micro) 0 var(--space-micro) 0;
line-height: var(--space-slab);
font-weight: var(--font-weight-medium);
background: none;
color: var(--s-500);
font-size: var(--font-size-mini);
}
.assignee-label { .assignee-label {
max-width: 50%; max-width: 50%;
} }

View file

@ -12,22 +12,25 @@
<h3 class="user--name text-truncate"> <h3 class="user--name text-truncate">
{{ currentContact.name }} {{ currentContact.name }}
</h3> </h3>
<div class="conversation--header--actions">
<inbox-name :inbox="inbox" class="margin-right-small" />
<span
v-if="isSnoozed"
class="snoozed--display-text margin-right-small"
>
{{ snoozedDisplayText }}
</span>
<woot-button <woot-button
class="user--profile__button" class="user--profile__button margin-right-small"
size="small" size="small"
variant="link" variant="link"
@click="$emit('contact-panel-toggle')" @click="$emit('contact-panel-toggle')"
> >
{{ {{ contactPanelToggleText }}
`${
isContactPanelOpen
? $t('CONVERSATION.HEADER.CLOSE')
: $t('CONVERSATION.HEADER.OPEN')
} ${$t('CONVERSATION.HEADER.DETAILS')}`
}}
</woot-button> </woot-button>
</div> </div>
</div> </div>
</div>
<div <div
class="header-actions-wrap" class="header-actions-wrap"
:class="{ 'has-open-sidebar': isContactPanelOpen }" :class="{ 'has-open-sidebar': isContactPanelOpen }"
@ -44,9 +47,13 @@ import agentMixin from '../../../mixins/agentMixin.js';
import eventListenerMixins from 'shared/mixins/eventListenerMixins'; import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import inboxMixin from 'shared/mixins/inboxMixin'; import inboxMixin from 'shared/mixins/inboxMixin';
import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers'; import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
import wootConstants from '../../../constants';
import differenceInHours from 'date-fns/differenceInHours';
import InboxName from '../InboxName';
export default { export default {
components: { components: {
InboxName,
MoreActions, MoreActions,
Thumbnail, Thumbnail,
}, },
@ -61,39 +68,50 @@ export default {
default: false, default: false,
}, },
}, },
data() {
return {
currentChatAssignee: null,
inboxId: null,
};
},
computed: { computed: {
...mapGetters({ ...mapGetters({
uiFlags: 'inboxAssignableAgents/getUIFlags', uiFlags: 'inboxAssignableAgents/getUIFlags',
currentChat: 'getSelectedChat', currentChat: 'getSelectedChat',
}), }),
chatMetadata() { chatMetadata() {
return this.chat.meta; return this.chat.meta;
}, },
inbox() {
const { inbox_id: inboxId } = this.chat;
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
return stateInbox;
},
currentContact() { currentContact() {
return this.$store.getters['contacts/getContact']( return this.$store.getters['contacts/getContact'](
this.chat.meta.sender.id this.chat.meta.sender.id
); );
}, },
isSnoozed() {
return this.currentChat.status === wootConstants.STATUS_TYPE.SNOOZED;
}, },
mounted() { snoozedDisplayText() {
const { snoozed_until: snoozedUntil } = this.currentChat;
if (snoozedUntil) {
// When the snooze is applied, it schedules the unsnooze event to next day/week 9AM.
// By that logic if the time difference is less than or equal to 24 + 9 hours we can consider it tomorrow.
const MAX_TIME_DIFFERENCE = 33;
const isSnoozedUntilTomorrow =
differenceInHours(new Date(snoozedUntil), new Date()) <=
MAX_TIME_DIFFERENCE;
return this.$t(
isSnoozedUntilTomorrow
? 'CONVERSATION.HEADER.SNOOZED_UNTIL_TOMORROW'
: 'CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_WEEK'
);
}
return this.$t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY');
},
contactPanelToggleText() {
return `${
this.isContactPanelOpen
? this.$t('CONVERSATION.HEADER.CLOSE')
: this.$t('CONVERSATION.HEADER.OPEN')
} ${this.$t('CONVERSATION.HEADER.DETAILS')}`;
},
inbox() {
const { inbox_id: inboxId } = this.chat; const { inbox_id: inboxId } = this.chat;
this.inboxId = inboxId; return this.$store.getters['inboxes/getInbox'](inboxId);
},
}, },
methods: { methods: {
@ -129,4 +147,28 @@ export default {
flex-shrink: 0; flex-shrink: 0;
} }
} }
.user--name {
display: inline-block;
font-size: var(--font-size-medium);
line-height: 1.3;
margin: 0;
text-transform: capitalize;
width: 100%;
}
.conversation--header--actions {
align-items: center;
display: flex;
font-size: var(--font-size-mini);
.user--profile__button {
padding: 0;
}
.snoozed--display-text {
font-weight: var(--font-weight-medium);
color: var(--y-900);
}
}
</style> </style>

View file

@ -1,41 +1,29 @@
<template> <template>
<div class="flex-container actions--container"> <div class="flex-container actions--container">
<woot-button
v-if="!currentChat.muted"
v-tooltip="$t('CONTACT_PANEL.MUTE_CONTACT')"
class="hollow secondary actions--button"
icon="ion-volume-mute"
@click="mute"
/>
<woot-button
v-else
v-tooltip.left="$t('CONTACT_PANEL.UNMUTE_CONTACT')"
class="hollow secondary actions--button"
icon="ion-volume-medium"
@click="unmute"
/>
<woot-button
v-tooltip="$t('CONTACT_PANEL.SEND_TRANSCRIPT')"
class="hollow secondary actions--button"
icon="ion-share"
@click="toggleEmailActionsModal"
/>
<resolve-action <resolve-action
:conversation-id="currentChat.id" :conversation-id="currentChat.id"
:status="currentChat.status" :status="currentChat.status"
/> />
<woot-button
class="more--button"
variant="clear"
size="large"
color-scheme="secondary"
icon="ion-android-more-vertical"
@click="toggleConversationActions"
/>
<div
v-if="showConversationActions"
v-on-clickaway="hideConversationActions"
class="dropdown-pane dropdowm--bottom"
:class="{ 'dropdown-pane--open': showConversationActions }"
>
<woot-dropdown-menu>
<woot-dropdown-item v-if="!currentChat.muted">
<button class="button clear alert " @click="mute">
<span>{{ $t('CONTACT_PANEL.MUTE_CONTACT') }}</span>
</button>
</woot-dropdown-item>
<woot-dropdown-item v-else>
<button class="button clear alert" @click="unmute">
<span>{{ $t('CONTACT_PANEL.UNMUTE_CONTACT') }}</span>
</button>
</woot-dropdown-item>
<woot-dropdown-item>
<button class="button clear" @click="toggleEmailActionsModal">
{{ $t('CONTACT_PANEL.SEND_TRANSCRIPT') }}
</button>
</woot-dropdown-item>
</woot-dropdown-menu>
</div>
<email-transcript-modal <email-transcript-modal
v-if="showEmailActionsModal" v-if="showEmailActionsModal"
:show="showEmailActionsModal" :show="showEmailActionsModal"
@ -50,13 +38,9 @@ import { mixin as clickaway } from 'vue-clickaway';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import EmailTranscriptModal from './EmailTranscriptModal'; import EmailTranscriptModal from './EmailTranscriptModal';
import ResolveAction from '../../buttons/ResolveAction'; import ResolveAction from '../../buttons/ResolveAction';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
export default { export default {
components: { components: {
WootDropdownMenu,
WootDropdownItem,
EmailTranscriptModal, EmailTranscriptModal,
ResolveAction, ResolveAction,
}, },
@ -97,7 +81,16 @@ export default {
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '~dashboard/assets/scss/mixins'; .actions--container {
align-items: center;
.button {
font-size: var(--font-size-large);
margin-right: var(--space-small);
border-color: var(--color-border);
color: var(--s-400);
}
}
.more--button { .more--button {
align-items: center; align-items: center;

View file

@ -1,6 +1,7 @@
import { createLocalVue, mount } from '@vue/test-utils'; import { createLocalVue, mount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import VueI18n from 'vue-i18n'; import VueI18n from 'vue-i18n';
import VTooltip from 'v-tooltip';
import Button from 'dashboard/components/buttons/Button'; import Button from 'dashboard/components/buttons/Button';
import i18n from 'dashboard/i18n'; import i18n from 'dashboard/i18n';
@ -10,6 +11,7 @@ import MoreActions from '../MoreActions';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
localVue.use(VueI18n); localVue.use(VueI18n);
localVue.use(VTooltip);
localVue.component('woot-button', Button); localVue.component('woot-button', Button);
@ -63,21 +65,9 @@ describe('MoveActions', () => {
moreActions = mount(MoreActions, { store, localVue, i18n: i18nConfig }); moreActions = mount(MoreActions, { store, localVue, i18n: i18nConfig });
}); });
it('opens the menu when user clicks "more"', async () => {
expect(moreActions.find('.dropdown-pane').exists()).toBe(false);
await moreActions.find('.more--button').trigger('click');
expect(moreActions.find('.dropdown-pane').exists()).toBe(true);
});
describe('muting discussion', () => { describe('muting discussion', () => {
it('triggers "muteConversation"', async () => { it('triggers "muteConversation"', async () => {
await moreActions.find('.more--button').trigger('click'); await moreActions.find('button:first-child').trigger('click');
await moreActions
.find('.dropdown-pane button:first-child')
.trigger('click');
expect(muteConversation).toBeCalledWith( expect(muteConversation).toBeCalledWith(
expect.any(Object), expect.any(Object),
@ -87,11 +77,7 @@ describe('MoveActions', () => {
}); });
it('shows alert', async () => { it('shows alert', async () => {
await moreActions.find('.more--button').trigger('click'); await moreActions.find('button:first-child').trigger('click');
await moreActions
.find('.dropdown-pane button:first-child')
.trigger('click');
expect(window.bus.$emit).toBeCalledWith( expect(window.bus.$emit).toBeCalledWith(
'newToastMessage', 'newToastMessage',
@ -106,11 +92,7 @@ describe('MoveActions', () => {
}); });
it('triggers "unmuteConversation"', async () => { it('triggers "unmuteConversation"', async () => {
await moreActions.find('.more--button').trigger('click'); await moreActions.find('button:first-child').trigger('click');
await moreActions
.find('.dropdown-pane button:first-child')
.trigger('click');
expect(unmuteConversation).toBeCalledWith( expect(unmuteConversation).toBeCalledWith(
expect.any(Object), expect.any(Object),
@ -120,11 +102,7 @@ describe('MoveActions', () => {
}); });
it('shows alert', async () => { it('shows alert', async () => {
await moreActions.find('.more--button').trigger('click'); await moreActions.find('button:first-child').trigger('click');
await moreActions
.find('.dropdown-pane button:first-child')
.trigger('click');
expect(window.bus.$emit).toBeCalledWith( expect(window.bus.$emit).toBeCalledWith(
'newToastMessage', 'newToastMessage',

View file

@ -39,7 +39,10 @@
"OPEN_ACTION": "Open", "OPEN_ACTION": "Open",
"OPEN": "More", "OPEN": "More",
"CLOSE": "Close", "CLOSE": "Close",
"DETAILS": "details" "DETAILS": "details",
"SNOOZED_UNTIL_TOMORROW": "Snoozed until tomorrow",
"SNOOZED_UNTIL_NEXT_WEEK": "Snoozed until next week",
"SNOOZED_UNTIL_NEXT_REPLY": "Snoozed until next reply"
}, },
"RESOLVE_DROPDOWN": { "RESOLVE_DROPDOWN": {
"MARK_PENDING": "Mark as pending", "MARK_PENDING": "Mark as pending",

View file

@ -91,7 +91,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~dashboard/assets/scss/_utility-helpers.scss';
.page-title { .page-title {
margin: 0; margin: 0;
} }

View file

@ -24,8 +24,9 @@ const actions = {
commit(types.default.SET_LIST_LOADING_STATUS); commit(types.default.SET_LIST_LOADING_STATUS);
try { try {
const response = await ConversationApi.get(params); const response = await ConversationApi.get(params);
const { data } = response.data; const {
const { payload: chatList, meta: metaData } = data; data: { payload: chatList, meta: metaData },
} = response.data;
commit(types.default.SET_ALL_CONVERSATION, chatList); commit(types.default.SET_ALL_CONVERSATION, chatList);
dispatch('conversationStats/set', metaData); dispatch('conversationStats/set', metaData);
dispatch('conversationLabels/setBulkConversationLabels', chatList); dispatch('conversationLabels/setBulkConversationLabels', chatList);
@ -36,10 +37,7 @@ const actions = {
); );
dispatch( dispatch(
'conversationPage/setCurrentPage', 'conversationPage/setCurrentPage',
{ { filter: params.assigneeType, page: params.page },
filter: params.assigneeType,
page: params.page,
},
{ root: true } { root: true }
); );
if (!chatList.length) { if (!chatList.length) {
@ -69,10 +67,7 @@ const actions = {
} = await MessageApi.getPreviousMessages(data); } = await MessageApi.getPreviousMessages(data);
commit( commit(
`conversationMetadata/${types.default.SET_CONVERSATION_METADATA}`, `conversationMetadata/${types.default.SET_CONVERSATION_METADATA}`,
{ { id: data.conversationId, data: meta }
id: data.conversationId,
data: meta,
}
); );
commit(types.default.SET_PREVIOUS_CONVERSATIONS, { commit(types.default.SET_PREVIOUS_CONVERSATIONS, {
id: data.conversationId, id: data.conversationId,
@ -140,14 +135,22 @@ const actions = {
{ conversationId, status, snoozedUntil = null } { conversationId, status, snoozedUntil = null }
) => { ) => {
try { try {
const response = await ConversationApi.toggleStatus({ const {
data: {
payload: {
current_status: updatedStatus,
snoozed_until: updatedSnoozedUntil,
} = {},
} = {},
} = await ConversationApi.toggleStatus({
conversationId, conversationId,
status, status,
snoozedUntil, snoozedUntil,
}); });
commit(types.default.RESOLVE_CONVERSATION, { commit(types.default.CHANGE_CONVERSATION_STATUS, {
conversationId, conversationId,
status: response.data.payload.current_status, status: updatedStatus,
snoozedUntil: updatedSnoozedUntil,
}); });
} catch (error) { } catch (error) {
// Handle error // Handle error
@ -223,11 +226,7 @@ const actions = {
data: { id, agent_last_seen_at: lastSeen }, data: { id, agent_last_seen_at: lastSeen },
} = await ConversationApi.markMessageRead(data); } = await ConversationApi.markMessageRead(data);
setTimeout( setTimeout(
() => () => commit(types.default.MARK_MESSAGE_READ, { id, lastSeen }),
commit(types.default.MARK_MESSAGE_READ, {
id,
lastSeen,
}),
4000 4000
); );
} catch (error) { } catch (error) {

View file

@ -69,9 +69,13 @@ export const mutations = {
Vue.set(chat.meta, 'team', team); Vue.set(chat.meta, 'team', team);
}, },
[types.default.RESOLVE_CONVERSATION](_state, { conversationId, status }) { [types.default.CHANGE_CONVERSATION_STATUS](
_state,
{ conversationId, status, snoozedUntil }
) {
const conversation = const conversation =
getters.getConversationById(_state)(conversationId) || {}; getters.getConversationById(_state)(conversationId) || {};
Vue.set(conversation, 'snoozed_until', snoozedUntil);
Vue.set(conversation, 'status', status); Vue.set(conversation, 'status', status);
}, },

View file

@ -217,15 +217,24 @@ describe('#actions', () => {
describe('#toggleStatus', () => { describe('#toggleStatus', () => {
it('sends correct mutations if toggle status is successful', async () => { it('sends correct mutations if toggle status is successful', async () => {
axios.post.mockResolvedValue({ axios.post.mockResolvedValue({
data: { payload: { conversation_id: 1, current_status: 'resolved' } }, data: {
payload: {
conversation_id: 1,
current_status: 'snoozed',
snoozed_until: null,
},
},
}); });
await actions.toggleStatus( await actions.toggleStatus(
{ commit }, { commit },
{ conversationId: 1, status: 'resolved' } { conversationId: 1, status: 'snoozed' }
); );
expect(commit).toHaveBeenCalledTimes(1); expect(commit).toHaveBeenCalledTimes(1);
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([
['RESOLVE_CONVERSATION', { conversationId: 1, status: 'resolved' }], [
'CHANGE_CONVERSATION_STATUS',
{ conversationId: 1, status: 'snoozed', snoozedUntil: null },
],
]); ]);
}); });
}); });

View file

@ -161,7 +161,7 @@ describe('#mutations', () => {
}); });
}); });
describe('#RESOLVE_CONVERSATION', () => { describe('#CHANGE_CONVERSATION_STATUS', () => {
it('updates the conversation status correctly', () => { it('updates the conversation status correctly', () => {
const state = { const state = {
allConversations: [ allConversations: [
@ -173,7 +173,7 @@ describe('#mutations', () => {
], ],
}; };
mutations[types.RESOLVE_CONVERSATION](state, { mutations[types.CHANGE_CONVERSATION_STATUS](state, {
conversationId: '1', conversationId: '1',
status: 'resolved', status: 'resolved',
}); });

View file

@ -23,7 +23,7 @@ export default {
SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW', SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW', CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
CLEAR_ALL_MESSAGES: 'CLEAR_ALL_MESSAGES', CLEAR_ALL_MESSAGES: 'CLEAR_ALL_MESSAGES',
RESOLVE_CONVERSATION: 'RESOLVE_CONVERSATION', CHANGE_CONVERSATION_STATUS: 'CHANGE_CONVERSATION_STATUS',
ADD_CONVERSATION: 'ADD_CONVERSATION', ADD_CONVERSATION: 'ADD_CONVERSATION',
UPDATE_CONVERSATION: 'UPDATE_CONVERSATION', UPDATE_CONVERSATION: 'UPDATE_CONVERSATION',
MUTE_CONVERSATION: 'MUTE_CONVERSATION', MUTE_CONVERSATION: 'MUTE_CONVERSATION',

View file

@ -4,12 +4,13 @@ class Conversations::EventDataPresenter < SimpleDelegator
additional_attributes: additional_attributes, additional_attributes: additional_attributes,
can_reply: can_reply?, can_reply: can_reply?,
channel: inbox.try(:channel_type), channel: inbox.try(:channel_type),
contact_inbox: contact_inbox,
id: display_id, id: display_id,
inbox_id: inbox_id, inbox_id: inbox_id,
contact_inbox: contact_inbox,
messages: push_messages, messages: push_messages,
meta: push_meta, meta: push_meta,
status: status, status: status,
snoozed_until: snoozed_until,
unread_count: unread_incoming_messages.count, unread_count: unread_incoming_messages.count,
**push_timestamps **push_timestamps
} }

View file

@ -3,6 +3,7 @@ end
json.payload do json.payload do
json.success @status json.success @status
json.current_status @conversation.status
json.conversation_id @conversation.display_id json.conversation_id @conversation.display_id
json.current_status @conversation.status
json.snoozed_until @conversation.snoozed_until
end end

View file

@ -24,16 +24,17 @@ else
json.messages conversation.unread_messages.includes([:user, { attachments: [{ file_attachment: [:blob] }] }]).last(10).map(&:push_event_data) json.messages conversation.unread_messages.includes([:user, { attachments: [{ file_attachment: [:blob] }] }]).last(10).map(&:push_event_data)
end end
json.inbox_id conversation.inbox_id json.account_id conversation.account_id
json.status conversation.status json.additional_attributes conversation.additional_attributes
json.muted conversation.muted?
json.can_reply conversation.can_reply?
json.timestamp conversation.last_activity_at.to_i
json.contact_last_seen_at conversation.contact_last_seen_at.to_i
json.agent_last_seen_at conversation.agent_last_seen_at.to_i json.agent_last_seen_at conversation.agent_last_seen_at.to_i
json.assignee_last_seen_at conversation.assignee_last_seen_at.to_i json.assignee_last_seen_at conversation.assignee_last_seen_at.to_i
json.unread_count conversation.unread_incoming_messages.count json.can_reply conversation.can_reply?
json.additional_attributes conversation.additional_attributes json.contact_last_seen_at conversation.contact_last_seen_at.to_i
json.custom_attributes conversation.custom_attributes json.custom_attributes conversation.custom_attributes
json.account_id conversation.account_id json.inbox_id conversation.inbox_id
json.labels conversation.label_list json.labels conversation.label_list
json.muted conversation.muted?
json.snoozed_until conversation.snoozed_until
json.status conversation.status
json.timestamp conversation.last_activity_at.to_i
json.unread_count conversation.unread_incoming_messages.count

View file

@ -354,6 +354,7 @@ RSpec.describe Conversation, type: :model do
timestamp: conversation.last_activity_at.to_i, timestamp: conversation.last_activity_at.to_i,
can_reply: true, can_reply: true,
channel: 'Channel::WebWidget', channel: 'Channel::WebWidget',
snoozed_until: conversation.snoozed_until,
contact_last_seen_at: conversation.contact_last_seen_at.to_i, contact_last_seen_at: conversation.contact_last_seen_at.to_i,
agent_last_seen_at: conversation.agent_last_seen_at.to_i, agent_last_seen_at: conversation.agent_last_seen_at.to_i,
unread_count: 0 unread_count: 0

View file

@ -22,6 +22,7 @@ RSpec.describe Conversations::EventDataPresenter do
can_reply: conversation.can_reply?, can_reply: conversation.can_reply?,
channel: conversation.inbox.channel_type, channel: conversation.inbox.channel_type,
timestamp: conversation.last_activity_at.to_i, timestamp: conversation.last_activity_at.to_i,
snoozed_until: conversation.snoozed_until,
contact_last_seen_at: conversation.contact_last_seen_at.to_i, contact_last_seen_at: conversation.contact_last_seen_at.to_i,
agent_last_seen_at: conversation.agent_last_seen_at.to_i, agent_last_seen_at: conversation.agent_last_seen_at.to_i,
unread_count: 0 unread_count: 0