Skip to content

Commit

Permalink
feat: Add more snooze options (#7344)
Browse files Browse the repository at this point in the history
  • Loading branch information
muhsin-k committed Jun 19, 2023
1 parent 5c5381c commit 9bed57c
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 30 deletions.
Expand Up @@ -86,6 +86,7 @@ import {
hasPressedAltAndMKey,
} from 'shared/helpers/KeyboardHelpers';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
Expand Down Expand Up @@ -181,7 +182,7 @@ export default {
onCmdSnoozeConversation(snoozeType) {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
this.snoozeTimes[snoozeType] || null
findSnoozeTime(snoozeType) || null
);
},
onCmdOpenConversation() {
Expand Down
Expand Up @@ -57,14 +57,15 @@ import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
import { mapGetters } from 'vuex';
import agentMixin from '../../../mixins/agentMixin.js';
import BackButton from '../BackButton';
import differenceInHours from 'date-fns/differenceInHours';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import inboxMixin from 'shared/mixins/inboxMixin';
import InboxName from '../InboxName';
import MoreActions from './MoreActions';
import Thumbnail from '../Thumbnail';
import wootConstants from 'dashboard/constants/globals';
import { conversationListPageURL } from 'dashboard/helper/URLHelper';
import { conversationReopenTime } from 'dashboard/helper/snoozeHelpers';
export default {
components: {
BackButton,
Expand Down Expand Up @@ -125,17 +126,9 @@ export default {
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'
)} ${conversationReopenTime(snoozedUntil)}`;
}
return this.$t('CONVERSATION.HEADER.SNOOZED_UNTIL_NEXT_REPLY');
},
Expand Down
7 changes: 7 additions & 0 deletions app/javascript/dashboard/constants/globals.js
Expand Up @@ -30,5 +30,12 @@ export default {
TESTIMONIAL_URL: 'https://testimonials.cdn.chatwoot.com/content.json',
SMALL_SCREEN_BREAKPOINT: 1024,
AVAILABILITY_STATUS_KEYS: ['online', 'busy', 'offline'],
SNOOZE_OPTIONS: {
UNTIL_NEXT_REPLY: 'until_next_reply',
AN_HOUR_FROM_NOW: 'an_hour_from_now',
UNTIL_TOMORROW: 'until_tomorrow',
UNTIL_NEXT_WEEK: 'until_next_week',
UNTIL_NEXT_MONTH: 'until_next_month',
},
};
export const DEFAULT_REDIRECT_URL = '/app/';
66 changes: 66 additions & 0 deletions app/javascript/dashboard/helper/snoozeHelpers.js
@@ -0,0 +1,66 @@
import {
getUnixTime,
format,
add,
startOfWeek,
addWeeks,
startOfMonth,
isMonday,
isToday,
setHours,
} from 'date-fns';
import wootConstants from 'dashboard/constants/globals';

const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;

export const findStartOfNextWeek = currentDate => {
const startOfNextWeek = startOfWeek(addWeeks(currentDate, 1));
return isMonday(startOfNextWeek)
? startOfNextWeek
: add(startOfNextWeek, {
days: (8 - startOfNextWeek.getDay()) % 7,
});
};

export const findStartOfNextMonth = currentDate => {
const startOfNextMonth = startOfMonth(add(currentDate, { months: 1 }));
return isMonday(startOfNextMonth)
? startOfNextMonth
: add(startOfNextMonth, {
days: (8 - startOfNextMonth.getDay()) % 7,
});
};

export const findNextDay = currentDate => {
return add(currentDate, { days: 1 });
};

export const setHoursToNine = date => {
return setHours(date, 9, 0, 0);
};

export const findSnoozeTime = (snoozeType, currentDate = new Date()) => {
let parsedDate = null;
if (snoozeType === SNOOZE_OPTIONS.AN_HOUR_FROM_NOW) {
parsedDate = add(currentDate, { hours: 1 });
} else if (snoozeType === SNOOZE_OPTIONS.UNTIL_TOMORROW) {
parsedDate = setHoursToNine(findNextDay(currentDate));
} else if (snoozeType === SNOOZE_OPTIONS.UNTIL_NEXT_WEEK) {
parsedDate = setHoursToNine(findStartOfNextWeek(currentDate));
} else if (snoozeType === SNOOZE_OPTIONS.UNTIL_NEXT_MONTH) {
parsedDate = setHoursToNine(findStartOfNextMonth(currentDate));
}

return parsedDate ? getUnixTime(parsedDate) : null;
};
export const conversationReopenTime = snoozedUntil => {
if (!snoozedUntil) {
return null;
}
const date = new Date(snoozedUntil);

if (isToday(date)) {
return format(date, 'h.mmaaa');
}
return snoozedUntil ? format(date, 'd MMM, h.mmaaa') : null;
};
105 changes: 105 additions & 0 deletions app/javascript/dashboard/helper/specs/snoozeHelpers.spec.js
@@ -0,0 +1,105 @@
import {
findSnoozeTime,
conversationReopenTime,
findStartOfNextWeek,
findStartOfNextMonth,
findNextDay,
setHoursToNine,
} from '../snoozeHelpers';

describe('#Snooze Helpers', () => {
describe('findStartOfNextWeek', () => {
it('should return first working day of next week if a date is passed', () => {
const today = new Date('06/16/2023');
const startOfNextWeek = new Date('06/19/2023');
expect(findStartOfNextWeek(today)).toEqual(startOfNextWeek);
});
it('should return first working day of next week if a date is passed', () => {
const today = new Date('06/03/2023');
const startOfNextWeek = new Date('06/05/2023');
expect(findStartOfNextWeek(today)).toEqual(startOfNextWeek);
});
});

describe('findStartOfNextMonth', () => {
it('should return first working day of next month if a valid date is passed', () => {
const today = new Date('06/21/2023');
const startOfNextMonth = new Date('07/03/2023');
expect(findStartOfNextMonth(today)).toEqual(startOfNextMonth);
});
it('should return first working day of next month if a valid date is passed', () => {
const today = new Date('02/28/2023');
const startOfNextMonth = new Date('03/06/2023');
expect(findStartOfNextMonth(today)).toEqual(startOfNextMonth);
});
});

describe('setHoursToNine', () => {
it('should return date with 9.00AM time', () => {
const nextDay = new Date('06/17/2023');
nextDay.setHours(9, 0, 0, 0);
expect(setHoursToNine(nextDay)).toEqual(nextDay);
});
});

describe('findSnoozeTime', () => {
it('should return nil if until_next_reply is passed', () => {
expect(findSnoozeTime('until_next_reply')).toEqual(null);
});

it('should return next hour time stamp if an_hour_from_now is passed', () => {
const nextHour = new Date();
nextHour.setHours(nextHour.getHours() + 1);
expect(findSnoozeTime('an_hour_from_now')).toBeCloseTo(
Math.floor(nextHour.getTime() / 1000)
);
});

it('should return next day 9.00AM time stamp until_tomorrow is passed', () => {
const today = new Date('06/16/2023');
const nextDay = new Date('06/17/2023');
nextDay.setHours(9, 0, 0, 0);
expect(findSnoozeTime('until_tomorrow', today)).toBeCloseTo(
nextDay.getTime() / 1000
);
});

it('should return next week monday 9.00AM time stamp if until_next_week is passed', () => {
const today = new Date('06/16/2023');
const startOfNextWeek = new Date('06/19/2023');
startOfNextWeek.setHours(9, 0, 0, 0);
expect(findSnoozeTime('until_next_week', today)).toBeCloseTo(
startOfNextWeek.getTime() / 1000
);
});

it('should return next month 9.00AM time stamp if until_next_month is passed', () => {
const today = new Date('06/21/2023');
const startOfNextMonth = new Date('07/03/2023');
startOfNextMonth.setHours(9, 0, 0, 0);
expect(findSnoozeTime('until_next_month', today)).toBeCloseTo(
startOfNextMonth.getTime() / 1000
);
});
});

describe('conversationReopenTime', () => {
it('should return nil if snoozedUntil is nil', () => {
expect(conversationReopenTime(null)).toEqual(null);
});

it('should return formatted date if snoozedUntil is not nil', () => {
expect(conversationReopenTime('2023-06-07T09:00:00.000Z')).toEqual(
'7 Jun, 9.00am'
);
});
});

describe('findNextDay', () => {
it('should return next day', () => {
const today = new Date('06/16/2023');
const nextDay = new Date('06/17/2023');
expect(findNextDay(today)).toEqual(nextDay);
});
});
});
6 changes: 5 additions & 1 deletion app/javascript/dashboard/i18n/locale/en/generalSettings.json
Expand Up @@ -106,6 +106,7 @@
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
"SNOOZE_CONVERSATION": "Snooze Conversation",
"ADD_LABEL": "Add label to the conversation",
"REMOVE_LABEL": "Remove label from the conversation",
"SETTINGS": "Settings"
Expand Down Expand Up @@ -141,7 +142,10 @@
"SNOOZE_CONVERSATION": "Snooze Conversation",
"UNTIL_NEXT_REPLY": "Until next reply",
"UNTIL_NEXT_WEEK": "Until next week",
"UNTIL_TOMORROW": "Until tomorrow"
"UNTIL_TOMORROW": "Until tomorrow",
"UNTIL_NEXT_MONTH": "Until next month",
"AN_HOUR_FROM_NOW": "Until an hour from now",
"CUSTOM": "Custom..."
}
},
"DASHBOARD_APPS": {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -20,9 +20,6 @@ import {
ICON_RESOLVE_CONVERSATION,
ICON_SEND_TRANSCRIPT,
ICON_SNOOZE_CONVERSATION,
ICON_SNOOZE_UNTIL_NEXT_REPLY,
ICON_SNOOZE_UNTIL_NEXT_WEEK,
ICON_SNOOZE_UNTIL_TOMORRROW,
ICON_UNMUTE_CONVERSATION,
ICON_PRIORITY_URGENT,
ICON_PRIORITY_HIGH,
Expand All @@ -31,6 +28,8 @@ import {
ICON_PRIORITY_NONE,
} from './CommandBarIcons';

const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;

const OPEN_CONVERSATION_ACTIONS = [
{
id: 'resolve_conversation',
Expand All @@ -39,32 +38,60 @@ const OPEN_CONVERSATION_ACTIONS = [
icon: ICON_RESOLVE_CONVERSATION,
handler: () => bus.$emit(CMD_RESOLVE_CONVERSATION),
},
];

const SNOOZE_CONVERSATION_ACTIONS = [
{
id: 'snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
icon: ICON_SNOOZE_CONVERSATION,
children: ['until_next_reply', 'until_tomorrow', 'until_next_week'],
children: Object.values(SNOOZE_OPTIONS),
},

{
id: 'until_next_reply',
id: SNOOZE_OPTIONS.UNTIL_NEXT_REPLY,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_REPLY',
parent: 'snooze_conversation',
icon: ICON_SNOOZE_UNTIL_NEXT_REPLY,
handler: () => bus.$emit(CMD_SNOOZE_CONVERSATION, 'nextReply'),
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_REPLY),
},
{
id: SNOOZE_OPTIONS.AN_HOUR_FROM_NOW,
title: 'COMMAND_BAR.COMMANDS.AN_HOUR_FROM_NOW',
parent: 'snooze_conversation',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
},
{
id: 'until_tomorrow',
id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
title: 'COMMAND_BAR.COMMANDS.UNTIL_TOMORROW',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
icon: ICON_SNOOZE_UNTIL_TOMORRROW,
handler: () => bus.$emit(CMD_SNOOZE_CONVERSATION, 'tomorrow'),
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_TOMORROW),
},
{
id: 'until_next_week',
id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_WEEK',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
icon: ICON_SNOOZE_UNTIL_NEXT_WEEK,
handler: () => bus.$emit(CMD_SNOOZE_CONVERSATION, 'nextWeek'),
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
title: 'COMMAND_BAR.COMMANDS.UNTIL_NEXT_MONTH',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_CONVERSATION',
parent: 'snooze_conversation',
icon: ICON_SNOOZE_CONVERSATION,
handler: () =>
bus.$emit(CMD_SNOOZE_CONVERSATION, SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
},
];

Expand Down Expand Up @@ -135,6 +162,7 @@ export default {
conversationId() {
return this.currentChat?.id;
},

statusActions() {
const isOpen =
this.currentChat?.status === wootConstants.STATUS_TYPE.OPEN;
Expand All @@ -145,7 +173,10 @@ export default {

let actions = [];
if (isOpen) {
actions = OPEN_CONVERSATION_ACTIONS;
actions = [
...OPEN_CONVERSATION_ACTIONS,
...SNOOZE_CONVERSATION_ACTIONS,
];
} else if (isResolved || isSnoozed) {
actions = RESOLVED_CONVERSATION_ACTIONS;
}
Expand Down Expand Up @@ -296,6 +327,7 @@ export default {
SEND_TRANSCRIPT_ACTION,
]);
},

conversationHotKeys() {
if (isAConversationRoute(this.$route.name)) {
return [
Expand Down

0 comments on commit 9bed57c

Please sign in to comment.