diff --git a/packages/kolibri-common/components/SyncSchedule/EditDeviceSyncSchedule.vue b/packages/kolibri-common/components/SyncSchedule/EditDeviceSyncSchedule.vue index 68cabe3ca65..adcb67fa3f2 100644 --- a/packages/kolibri-common/components/SyncSchedule/EditDeviceSyncSchedule.vue +++ b/packages/kolibri-common/components/SyncSchedule/EditDeviceSyncSchedule.vue @@ -31,6 +31,7 @@ :style="selectorStyle" :options="selectArray" :label="$tr('frequency')" + @select="handleUserInput" /> @@ -43,6 +44,7 @@ :style="selectorStyle" :options="getDays" :label="$tr('day')" + @select="handleUserInput" /> @@ -55,6 +57,7 @@ :style="selectorStyle" :options="SyncTime" :label="$tr('time')" + @select="handleUserInput" /> @@ -78,7 +81,7 @@ {{ $tr('checkboxLabel') }} @@ -156,6 +159,7 @@ import { now } from 'kolibri/utils/serverClock'; import commonCoreStrings from 'kolibri/uiText/commonCoreStrings'; import { TaskStatuses, TaskTypes } from 'kolibri-common/utils/syncTaskUtils'; + import useTaskPolling from '../../composables/useTaskPolling'; import { KDP_ID, oneHour, oneDay, oneWeek, twoWeeks, oneMonth } from './constants'; import { kdpNameTranslator } from './i18n'; @@ -192,6 +196,10 @@ BottomAppBar, }, mixins: [commonCoreStrings], + setup() { + const { tasks } = useTaskPolling('facility_task'); + return { tasks }; + }, props: { icon: { type: String, @@ -217,9 +225,9 @@ device: null, now: null, selectedItem: {}, - tasks: [], selectedDay: {}, selectedTime: {}, + userHasEdited: false, }; }, computed: { @@ -266,16 +274,26 @@ }; }); }, + filteredTasks() { + return this.tasks.filter( + task => + (this.isKdp || task.extra_metadata.device_id === this.device?.id) && + task.facility_id === this.facilityId && + task.type === this.taskType && + // Only show tasks that are repeating indefinitely + task.repeat === null, + ); + }, deviceName() { return this.device && this.device.nickname && this.device.nickname.length ? this.device.nickname : this.device.device_name; }, currentTask() { - return this.tasks && this.tasks.length ? this.tasks[0] : null; + return this.filteredTasks.length ? this.filteredTasks[0] : null; }, currentTaskRunning() { - return this.currentTask && this.currentTask.status === TaskStatuses.RUNNING; + return this.currentTask?.status === TaskStatuses.RUNNING; }, timeRequired() { return this.selectedItem.value > oneHour; @@ -304,6 +322,36 @@ ); }, }, + watch: { + currentTask() { + if (this.currentTask && !this.userHasEdited) { + const enqueueAt = new Date(Date.parse(this.currentTask.scheduled_datetime)); + const day = enqueueAt.getDay(); + const hours = enqueueAt.getHours(); + const minutes = enqueueAt.getMinutes(); + this.selectedItem = + this.selectArray.find(item => item.value === this.currentTask.repeat_interval) || {}; + this.selectedDay = this.getDays.find(item => item.value === day) || {}; + for (const time of this.SyncTime) { + // Because there can be some drift in the task scheduling process, + // we round the 'scheduled' time to the nearest 30 minutes + if ( + time.minutes === 0 && + ((time.hours === hours && minutes < 15) || + (time.hours === hours + 1 && minutes >= 45)) + ) { + this.selectedTime = time; + break; + } + if (time.minutes === 30 && time.hours === hours && minutes >= 15 && minutes < 45) { + this.selectedTime = time; + break; + } + } + this.retryFlag = Boolean(this.currentTask.retry_interval); + } + }, + }, created() { this.fetchDevice(); this.now = now(); @@ -393,67 +441,11 @@ }) .catch(() => { this.createTaskFailedSnackbar(); - if (this.currentTask) { - this.fetchSyncTasks(); - } }); }, - goBack() { this.$router.push(this.goBackRoute); }, - pollFetchSyncTasks() { - this.pollInterval = setInterval(() => { - this.fetchSyncTasks(); - }, 10000); - }, - fetchSyncTasks() { - TaskResource.list({ queue: 'facility_task' }).then(tasks => { - this.tasks = tasks.filter( - task => - (this.isKdp || task.extra_metadata.device_id === this.device.id) && - task.facility_id === this.facilityId && - task.type === this.taskType && - // Only show tasks that are repeating indefinitely - task.repeat === null, - ); - this.$nextTick(() => { - if (this.currentTask) { - const enqueueAt = new Date(Date.parse(this.currentTask.scheduled_datetime)); - const day = enqueueAt.getDay(); - const hours = enqueueAt.getHours(); - const minutes = enqueueAt.getMinutes(); - this.selectedItem = - this.selectArray.find(item => item.value === this.currentTask.repeat_interval) || - {}; - this.selectedDay = this.getDays.find(item => item.value === day) || {}; - for (const time of this.SyncTime) { - // Because there can be some drift in the task scheduling process, - // we round the 'scheduled' time to the nearest 30 minutes - if ( - time.minutes === 0 && - ((time.hours === hours && minutes < 15) || - (time.hours === hours + 1 && minutes >= 45)) - ) { - this.selectedTime = time; - break; - } - if (time.minutes === 30 && time.hours === hours && minutes >= 15 && minutes < 45) { - this.selectedTime = time; - break; - } - } - this.retryFlag = Boolean(this.currentTask.retry_interval); - if (this.currentTaskRunning) { - this.pollFetchSyncTasks(); - } else { - clearInterval(this.pollInterval); - this.pollInterval = null; - } - } - }); - }); - }, fetchDevice() { if (this.isKdp) { this.device = { @@ -462,14 +454,19 @@ device_name: kdpNameTranslator.$tr('syncToKDP'), base_url: '', }; - this.fetchSyncTasks(); return; } NetworkLocationResource.fetchModel({ id: this.deviceId }).then(device => { this.device = device; - this.fetchSyncTasks(); }); }, + handleUserInput() { + this.userHasEdited = true; + }, + handleRetryCheckboxChange() { + this.retryFlag = !this.retryFlag; + this.handleUserInput(); + }, }, $trs: { editSyncScheduleTitle: { diff --git a/packages/kolibri-common/components/SyncSchedule/ManageSyncSchedule.vue b/packages/kolibri-common/components/SyncSchedule/ManageSyncSchedule.vue index 21170c0d450..fa80f2cfeee 100644 --- a/packages/kolibri-common/components/SyncSchedule/ManageSyncSchedule.vue +++ b/packages/kolibri-common/components/SyncSchedule/ManageSyncSchedule.vue @@ -117,7 +117,6 @@ import { computed } from 'vue'; import ImmersivePage from 'kolibri/components/pages/ImmersivePage'; import CoreTable from 'kolibri/components/CoreTable'; - import TaskResource from 'kolibri/apiResources/TaskResource'; import FacilityResource from 'kolibri-common/apiResources/FacilityResource'; import commonCoreStrings from 'kolibri/uiText/commonCoreStrings'; import commonSyncElements from 'kolibri-common/mixins/commonSyncElements'; @@ -127,6 +126,7 @@ useDevicesWithFilter, } from 'kolibri-common/components/syncComponentSet/SelectDeviceModalGroup/useDevices'; import { TaskTypes } from 'kolibri-common/utils/syncTaskUtils'; + import useTaskPolling from '../../composables/useTaskPolling'; import { KDP_ID, oneHour, oneDay, oneWeek, twoWeeks, oneMonth } from './constants'; import { kdpNameTranslator } from './i18n'; @@ -141,6 +141,7 @@ mixins: [commonCoreStrings, commonSyncElements], setup(props) { const deviceFilter = useDeviceFacilityFilter({ id: props.facilityId }); + const { tasks } = useTaskPolling('facility_task'); const { devices } = useDevicesWithFilter( { subset_of_users_device: false, @@ -165,6 +166,7 @@ }); return { devicesById, + tasks, }; }, props: { @@ -185,10 +187,17 @@ return { deviceModal: false, facility: null, - facilitySyncTasks: [], }; }, computed: { + facilitySyncTasks() { + return this.tasks.filter( + t => + t.facility_id === this.facilityId && + t.repeat === null && + (t.type === TaskTypes.SYNCDATAPORTAL || t.type === TaskTypes.SYNCPEERFULL), + ); + }, scheduledTasks() { return this.facilitySyncTasks.map(task => { const deviceName = this.devicesById[this.getDeviceId(task)] @@ -208,7 +217,6 @@ }, }, beforeMount() { - this.pollFacilityTasks(); this.fetchFacility(); }, methods: { @@ -217,21 +225,6 @@ this.facility = { ...facility }; }); }, - pollFacilityTasks() { - TaskResource.list({ queue: 'facility_task' }).then(tasks => { - this.facilitySyncTasks = tasks.filter( - t => - t.facility_id === this.facilityId && - t.repeat === null && - (t.type === TaskTypes.SYNCDATAPORTAL || t.type === TaskTypes.SYNCPEERFULL), - ); - if (this.isPolling) { - setTimeout(() => { - return this.pollFacilityTasks(); - }, 2000); - } - }); - }, closeModal() { this.deviceModal = false; }, diff --git a/packages/kolibri-common/composables/useTaskPolling.js b/packages/kolibri-common/composables/useTaskPolling.js new file mode 100644 index 00000000000..c645c07e4e1 --- /dev/null +++ b/packages/kolibri-common/composables/useTaskPolling.js @@ -0,0 +1,47 @@ +import logger from 'kolibri-logging'; +import { ref, onMounted, onUnmounted } from 'vue'; +import { useTimeoutPoll } from '@vueuse/core'; +import TaskResource from 'kolibri/apiResources/TaskResource'; + +const taskPollers = new Map(); + +const logging = logger.getLogger(__filename); + +export default function useTaskPolling(queueName) { + if (!taskPollers.has(queueName)) { + const consumers = ref(0); + const tasks = ref([]); + const { pause, resume, isActive } = useTimeoutPoll( + async () => { + try { + tasks.value = await TaskResource.list({ queue: queueName }); + } catch (e) { + logging.error('Error while fetching tasks', e); + } + }, + 5000, + { immediate: true }, + ); + + taskPollers.set(queueName, { consumers, tasks, pause, resume, isActive }); + } + + const poller = taskPollers.get(queueName); + + onMounted(() => { + poller.consumers.value++; + if (!poller.isActive.value) { + poller.resume(); + } + }); + + onUnmounted(() => { + poller.consumers.value--; + if (poller.consumers.value === 0) { + poller.pause(); + taskPollers.delete(queueName); + } + }); + + return { tasks: poller.tasks }; +}