diff --git a/addons/mail/static/src/models/activity_view/activity_view.js b/addons/mail/static/src/models/activity_view/activity_view.js index 794e79a8c37..110325ce217 100644 --- a/addons/mail/static/src/models/activity_view/activity_view.js +++ b/addons/mail/static/src/models/activity_view/activity_view.js @@ -74,7 +74,7 @@ registerModel({ if (!this.activity.dateDeadline) { return clear(); } - const today = moment(this.messaging.time.currentDateEveryMinute.getTime()).startOf('day'); + const today = moment(this.clockWatcher.clock.date.getTime()).startOf('day'); const momentDeadlineDate = moment(auto_str_to_date(this.activity.dateDeadline)); // true means no rounding const diff = momentDeadlineDate.diff(today, 'days', true); @@ -167,6 +167,15 @@ registerModel({ assignedUserText: attr({ compute: '_computeAssignedUserText', }), + clockWatcher: one('ClockWatcher', { + default: insertAndReplace({ + clock: insertAndReplace({ + frequency: 60 * 1000, + }), + }), + inverse: 'activityViewOwner', + isCausal: true, + }), /** * Compute the label for "when" the activity is due. */ diff --git a/addons/mail/static/src/models/clock/clock.js b/addons/mail/static/src/models/clock/clock.js new file mode 100644 index 00000000000..e4a1a6720e1 --- /dev/null +++ b/addons/mail/static/src/models/clock/clock.js @@ -0,0 +1,75 @@ +/** @odoo-module **/ + +import { registerModel } from '@mail/model/model_core'; +import { attr, many } from '@mail/model/model_field'; +import { OnChange } from '@mail/model/model_onchange'; + +/** + * Models a record that provides the current date, updated at a given frequency. + */ +registerModel({ + name: 'Clock', + identifyingFields: ['frequency'], + lifecycleHooks: { + _willDelete() { + this.messaging.browser.clearInterval(this.tickInterval); + }, + }, + recordMethods: { + /** + * @private + * @returns {integer} + */ + _computeTickInterval() { + return this.messaging.browser.setInterval(this._onInterval, this.frequency); + }, + /** + * @private + */ + _onChangeWatchers() { + if (this.watchers.length === 0) { + this.delete(); + } + }, + /** + * @private + */ + _onInterval() { + this.update({ date: new Date() }); + }, + }, + fields: { + /** + * A Date object set at the current date, updated at every tick. + */ + date: attr({ + default: new Date(), + }), + /** + * An integer representing the frequency in milliseconds at which `date` + * must be recomputed. + */ + frequency: attr({ + readonly: true, + required: true, + }), + tickInterval: attr({ + compute: '_computeTickInterval', + }), + /** + * The records that are making use of this clock. + * + * The clock self-destructs when there are no more watchers. + */ + watchers: many('ClockWatcher', { + inverse: 'clock', + isCausal: true, + }), + }, + onChanges: [ + new OnChange({ + dependencies: ['watchers'], + methodName: '_onChangeWatchers', + }), + ], +}); diff --git a/addons/mail/static/src/models/clock_watcher/clock_watcher.js b/addons/mail/static/src/models/clock_watcher/clock_watcher.js new file mode 100644 index 00000000000..ca9a82a98e2 --- /dev/null +++ b/addons/mail/static/src/models/clock_watcher/clock_watcher.js @@ -0,0 +1,22 @@ +/** @odoo-module **/ + +import { registerModel } from '@mail/model/model_core'; +import { one } from '@mail/model/model_field'; + +/** + * Models a record that makes use of a clock. + */ +registerModel({ + name: 'ClockWatcher', + identifyingFields: [['activityViewOwner']], + fields: { + activityViewOwner: one('ActivityView', { + inverse: 'clockWatcher', + readonly: true, + }), + clock: one('Clock', { + inverse: 'watchers', + required: true, + }), + }, +}); diff --git a/addons/mail/static/src/models/messaging/messaging.js b/addons/mail/static/src/models/messaging/messaging.js index f1d2f7f2e37..9d0a0dca498 100644 --- a/addons/mail/static/src/models/messaging/messaging.js +++ b/addons/mail/static/src/models/messaging/messaging.js @@ -375,12 +375,6 @@ registerModel({ * Mailbox Starred. */ starred: one('Thread'), - time: one('Time', { - default: insertAndReplace(), - isCausal: true, - readonly: true, - required: true, - }), userSetting: one('UserSetting', { isCausal: true, }), diff --git a/addons/mail/static/src/models/time/time.js b/addons/mail/static/src/models/time/time.js deleted file mode 100644 index 8ed52f4d7f6..00000000000 --- a/addons/mail/static/src/models/time/time.js +++ /dev/null @@ -1,41 +0,0 @@ -/** @odoo-module **/ - -import { registerModel } from '@mail/model/model_core'; -import { attr } from '@mail/model/model_field'; - -registerModel({ - name: 'Time', - identifyingFields: ['messaging'], - lifecycleHooks: { - _willDelete() { - clearInterval(this.everyMinuteIntervalId); - }, - }, - recordMethods: { - /** - * @private - * @returns {number} - */ - _computeEveryMinuteIntervalId() { - return setInterval(this._onEveryMinuteTimeout, 60 * 1000); - }, - /** - * @private - */ - _onEveryMinuteTimeout() { - this.update({ currentDateEveryMinute: new Date() }); - }, - }, - fields: { - /** - * Contains a Date object that is set to the current time, and is - * (re-)computed every minute. - */ - currentDateEveryMinute: attr({ - default: new Date(), - }), - everyMinuteIntervalId: attr({ - compute: '_computeEveryMinuteIntervalId', - }), - }, -}); diff --git a/addons/mail/static/tests/clock_model_tests.js b/addons/mail/static/tests/clock_model_tests.js new file mode 100644 index 00000000000..04acd34791b --- /dev/null +++ b/addons/mail/static/tests/clock_model_tests.js @@ -0,0 +1,42 @@ +/** @odoo-module **/ + +import { insertAndReplace } from '@mail/model/model_field_command'; +import { afterEach, beforeEach, start } from '@mail/utils/test_utils'; + +QUnit.module('mail', {}, function () { +QUnit.module('clock_model_tests', { + async beforeEach() { + await beforeEach(this); + + this.start = async params => { + const res = await start({ ...params, data: this.data }); + const { apps, env, widget } = res; + this.apps = apps; + this.env = env; + this.widget = widget; + return res; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('Deleting all the watchers of a clock should result in the deletion of the clock itself.', async function (assert) { + assert.expect(1); + + await this.start(); + const watcher = this.messaging.models['ClockWatcher'].insert({ + clock: insertAndReplace({ frequency: 180 * 1000 }), + qunitTestOwner: insertAndReplace(), + }); + const { clock } = watcher; + + watcher.delete(); + assert.notOk( + clock.exists(), + "deleting all the watchers of a clock should result in the deletion of the clock itself." + ); +}); + +}); diff --git a/addons/mail/static/tests/clock_watcher_qunit_tests.js b/addons/mail/static/tests/clock_watcher_qunit_tests.js new file mode 100644 index 00000000000..2676d8dba9e --- /dev/null +++ b/addons/mail/static/tests/clock_watcher_qunit_tests.js @@ -0,0 +1,17 @@ +/** @odoo-module **/ + +import { addFields, patchIdentifyingFields } from '@mail/model/model_core'; +import { one } from '@mail/model/model_field'; +// ensure that the model definition is loaded before the patch +import '@mail/models/clock_watcher/clock_watcher'; + +patchIdentifyingFields('ClockWatcher', identifyingFields => { + identifyingFields[0].push('qunitTestOwner'); +}); + +addFields('ClockWatcher', { + qunitTestOwner: one('QUnitTest', { + inverse: 'clockWatcher', + readonly: true, + }), +}); diff --git a/addons/mail/static/tests/qunit_test.js b/addons/mail/static/tests/qunit_test.js index 1eccbc3e532..ce7a688e40c 100644 --- a/addons/mail/static/tests/qunit_test.js +++ b/addons/mail/static/tests/qunit_test.js @@ -7,6 +7,10 @@ registerModel({ name: 'QUnitTest', identifyingFields: [], // singleton acceptable (only one test at a time) fields: { + clockWatcher: one('ClockWatcher', { + inverse: 'qunitTestOwner', + isCausal: true, + }), composer: one('Composer', { isCausal: true, }),