From 17fb6b8d550b0e3fccb8b6ec6bbd7595628b449c Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Mon, 18 Apr 2022 18:15:20 +0530 Subject: [PATCH] fix: Update business hour calculation (#4496) --- app/javascript/shared/helpers/DateHelper.js | 17 +++--- .../shared/helpers/specs/DateHelper.spec.js | 38 +++++------- .../widget/components/TeamAvailability.vue | 1 + app/javascript/widget/mixins/availability.js | 49 +++++++++------ .../mixins/specs/availabilityMixin.spec.js | 60 +++++++++++++++++++ package.json | 1 + yarn.lock | 5 ++ 7 files changed, 125 insertions(+), 46 deletions(-) create mode 100644 app/javascript/widget/mixins/specs/availabilityMixin.spec.js diff --git a/app/javascript/shared/helpers/DateHelper.js b/app/javascript/shared/helpers/DateHelper.js index 6adc36951..15638c5a8 100644 --- a/app/javascript/shared/helpers/DateHelper.js +++ b/app/javascript/shared/helpers/DateHelper.js @@ -2,7 +2,6 @@ import fromUnixTime from 'date-fns/fromUnixTime'; import format from 'date-fns/format'; import isToday from 'date-fns/isToday'; import isYesterday from 'date-fns/isYesterday'; -import parseISO from 'date-fns/parseISO'; export const formatUnixDate = (date, dateFormat = 'MMM dd, yyyy') => { const unixDate = fromUnixTime(date); @@ -20,10 +19,14 @@ export const formatDigitToString = val => { return val > 9 ? `${val}` : `0${val}`; }; -export const buildDateFromTime = (hr, min, utcOffset, date = new Date()) => { - const today = format(date, 'yyyy-MM-dd'); - const hour = formatDigitToString(hr); - const minute = formatDigitToString(min); - const timeString = `${today}T${hour}:${minute}:00${utcOffset}`; - return parseISO(timeString); +export const isTimeAfter = (h1, m1, h2, m2) => { + if (h1 < h2) { + return false; + } + + if (h1 === h2) { + return m1 >= m2; + } + + return true; }; diff --git a/app/javascript/shared/helpers/specs/DateHelper.spec.js b/app/javascript/shared/helpers/specs/DateHelper.spec.js index 283950ed1..c67c1a27b 100644 --- a/app/javascript/shared/helpers/specs/DateHelper.spec.js +++ b/app/javascript/shared/helpers/specs/DateHelper.spec.js @@ -1,8 +1,8 @@ import { formatDate, formatUnixDate, - buildDateFromTime, formatDigitToString, + isTimeAfter, } from '../DateHelper'; describe('#DateHelper', () => { @@ -44,27 +44,21 @@ describe('#DateHelper', () => { }) ).toEqual('Yesterday'); }); - - describe('#buildDate', () => { - it('returns correctly parsed date', () => { - const date = new Date(); - date.setFullYear(2021); - date.setMonth(2); - date.setDate(5); - - const result = buildDateFromTime(12, 15, '.465Z', date); - expect(result + '').toEqual( - 'Fri Mar 05 2021 12:15:00 GMT+0000 (Coordinated Universal Time)' - ); - }); +}); +describe('#formatDigitToString', () => { + it('returns date compatabile string from number is less than 9', () => { + expect(formatDigitToString(8)).toEqual('08'); }); - - describe('#formatDigitToString', () => { - it('returns date compatabile string from number is less than 9', () => { - expect(formatDigitToString(8)).toEqual('08'); - }); - it('returns date compatabile string from number is greater than 9', () => { - expect(formatDigitToString(11)).toEqual('11'); - }); + it('returns date compatabile string from number is greater than 9', () => { + expect(formatDigitToString(11)).toEqual('11'); + }); +}); + +describe('#isTimeAfter', () => { + it('return correct values', () => { + expect(isTimeAfter(5, 30, 9, 30)).toEqual(false); + expect(isTimeAfter(9, 30, 9, 30)).toEqual(true); + expect(isTimeAfter(9, 29, 9, 30)).toEqual(false); + expect(isTimeAfter(11, 59, 12, 0)).toEqual(false); }); }); diff --git a/app/javascript/widget/components/TeamAvailability.vue b/app/javascript/widget/components/TeamAvailability.vue index 35cb7eebc..1f4c5d046 100644 --- a/app/javascript/widget/components/TeamAvailability.vue +++ b/app/javascript/widget/components/TeamAvailability.vue @@ -58,6 +58,7 @@ export default { default: false, }, }, + computed: { ...mapGetters({ widgetColor: 'appConfig/getWidgetColor', diff --git a/app/javascript/widget/mixins/availability.js b/app/javascript/widget/mixins/availability.js index c797f79e4..1c5cbc37f 100644 --- a/app/javascript/widget/mixins/availability.js +++ b/app/javascript/widget/mixins/availability.js @@ -1,5 +1,5 @@ -import compareAsc from 'date-fns/compareAsc'; -import { buildDateFromTime } from 'shared/helpers/DateHelper'; +import { utcToZonedTime } from 'date-fns-tz'; +import { isTimeAfter } from 'shared/helpers/DateHelper'; export default { computed: { @@ -33,23 +33,32 @@ export default { closedAllDay, openAllDay, } = this.currentDayAvailability; + + if (openAllDay || closedAllDay) { + return true; + } + const { utcOffset } = this.channelConfig; - - if (openAllDay) return true; - - if (closedAllDay) return false; - - const startTime = buildDateFromTime(openHour, openMinute, utcOffset); - const endTime = buildDateFromTime(closeHour, closeMinute, utcOffset); - const isBetween = - compareAsc(new Date(), startTime) === 1 && - compareAsc(endTime, new Date()) === 1; - - if (isBetween) return true; - return false; + const today = this.getDateWithOffset(utcOffset); + const currentHours = today.getHours(); + const currentMinutes = today.getMinutes(); + const isAfterStartTime = isTimeAfter( + currentHours, + currentMinutes, + openHour, + openMinute + ); + const isBeforeEndTime = isTimeAfter( + closeHour, + closeMinute, + currentHours, + currentMinutes + ); + return isAfterStartTime && isBeforeEndTime; }, currentDayAvailability() { - const dayOfTheWeek = new Date().getDay(); + const { utcOffset } = this.channelConfig; + const dayOfTheWeek = this.getDateWithOffset(utcOffset).getDay(); const [workingHourConfig = {}] = this.channelConfig.workingHours.filter( workingHour => workingHour.day_of_week === dayOfTheWeek ); @@ -63,8 +72,14 @@ export default { }; }, isInBusinessHours() { - const { workingHoursEnabled } = window.chatwootWebChannel; + const { workingHoursEnabled } = this.channelConfig; return workingHoursEnabled ? this.isInBetweenTheWorkingHours : true; }, }, + + methods: { + getDateWithOffset(utcOffset) { + return utcToZonedTime(new Date().toISOString(), utcOffset); + }, + }, }; diff --git a/app/javascript/widget/mixins/specs/availabilityMixin.spec.js b/app/javascript/widget/mixins/specs/availabilityMixin.spec.js new file mode 100644 index 000000000..5b9e28caa --- /dev/null +++ b/app/javascript/widget/mixins/specs/availabilityMixin.spec.js @@ -0,0 +1,60 @@ +import { createWrapper } from '@vue/test-utils'; +import availabilityMixin from '../availability'; +import Vue from 'vue'; + +global.chatwootWebChannel = { + workingHoursEnabled: true, + workingHours: [ + { + day_of_week: 3, + closed_all_day: false, + open_hour: 8, + open_minutes: 30, + close_hour: 17, + close_minutes: 35, + open_all_day: false, + }, + { + day_of_week: 4, + closed_all_day: false, + open_hour: 8, + open_minutes: 30, + close_hour: 17, + close_minutes: 30, + open_all_day: false, + }, + ], + utcOffset: '-07:00', +}; + +describe('availabilityMixin', () => { + it('returns valid isInBetweenWorkingHours if in different timezone', () => { + const Component = { + render() {}, + mixins: [availabilityMixin], + }; + jest + .useFakeTimers('modern') + .setSystemTime(new Date('Thu Apr 14 2022 06:04:46 GMT+0530')); + const Constructor = Vue.extend(Component); + const vm = new Constructor().$mount(); + const wrapper = createWrapper(vm); + expect(wrapper.vm.isInBetweenTheWorkingHours).toBe(true); + jest.useRealTimers(); + }); + + it('returns valid isInBetweenWorkingHours if in same timezone', () => { + global.chatwootWebChannel.utcOffset = '+05:30'; + const Component = { + render() {}, + mixins: [availabilityMixin], + }; + jest + .useFakeTimers('modern') + .setSystemTime(new Date('Thu Apr 14 2022 09:01:46 GMT+0530')); + const Constructor = Vue.extend(Component); + const wrapper = createWrapper(new Constructor().$mount()); + expect(wrapper.vm.isInBetweenTheWorkingHours).toBe(true); + jest.useRealTimers(); + }); +}); diff --git a/package.json b/package.json index 1b55668b3..b88346415 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "core-js": "3.11.0", "country-code-emoji": "^1.0.0", "date-fns": "2.21.1", + "date-fns-tz": "^1.3.3", "dompurify": "2.2.7", "dotenv": "^8.0.0", "foundation-sites": "~6.5.3", diff --git a/yarn.lock b/yarn.lock index a95d08798..8f0bd9067 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5454,6 +5454,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns-tz@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.3.tgz#7884a4b3ed6cd95bfd81831d608e5ef8be500c86" + integrity sha512-Gks46gwbSauBQnV3Oofluj1wTm8J0tM7sbSJ9P+cJq/ZnTCpMohTKmmO5Tn+jQ7dyn0+b8G7cY4O2DZ5P/LXcA== + date-fns@2.21.1: version "2.21.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.21.1.tgz#679a4ccaa584c0706ea70b3fa92262ac3009d2b0"