Skip to content

Commit c554730

Browse files
authored
Merge pull request #13158 from ozer550/create-task-polling-composable
Implement task polling composable
2 parents 81f3f58 + fae40dd commit c554730

File tree

3 files changed

+117
-80
lines changed

3 files changed

+117
-80
lines changed

packages/kolibri-common/components/SyncSchedule/EditDeviceSyncSchedule.vue

+59-62
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
:style="selectorStyle"
3232
:options="selectArray"
3333
:label="$tr('frequency')"
34+
@select="handleUserInput"
3435
/>
3536
</KGridItem>
3637
</KGrid>
@@ -43,6 +44,7 @@
4344
:style="selectorStyle"
4445
:options="getDays"
4546
:label="$tr('day')"
47+
@select="handleUserInput"
4648
/>
4749
</KGridItem>
4850
</KGrid>
@@ -55,6 +57,7 @@
5557
:style="selectorStyle"
5658
:options="SyncTime"
5759
:label="$tr('time')"
60+
@select="handleUserInput"
5861
/>
5962
</KGridItem>
6063
</KGrid>
@@ -78,7 +81,7 @@
7881
<KCheckbox
7982
:checked="retryFlag"
8083
:disabled="currentTaskRunning"
81-
@change="retryFlag = !retryFlag"
84+
@change="handleRetryCheckboxChange"
8285
>
8386
{{ $tr('checkboxLabel') }}
8487
</KCheckbox>
@@ -156,6 +159,7 @@
156159
import { now } from 'kolibri/utils/serverClock';
157160
import commonCoreStrings from 'kolibri/uiText/commonCoreStrings';
158161
import { TaskStatuses, TaskTypes } from 'kolibri-common/utils/syncTaskUtils';
162+
import useTaskPolling from '../../composables/useTaskPolling';
159163
import { KDP_ID, oneHour, oneDay, oneWeek, twoWeeks, oneMonth } from './constants';
160164
import { kdpNameTranslator } from './i18n';
161165
@@ -192,6 +196,10 @@
192196
BottomAppBar,
193197
},
194198
mixins: [commonCoreStrings],
199+
setup() {
200+
const { tasks } = useTaskPolling('facility_task');
201+
return { tasks };
202+
},
195203
props: {
196204
icon: {
197205
type: String,
@@ -217,9 +225,9 @@
217225
device: null,
218226
now: null,
219227
selectedItem: {},
220-
tasks: [],
221228
selectedDay: {},
222229
selectedTime: {},
230+
userHasEdited: false,
223231
};
224232
},
225233
computed: {
@@ -266,16 +274,26 @@
266274
};
267275
});
268276
},
277+
filteredTasks() {
278+
return this.tasks.filter(
279+
task =>
280+
(this.isKdp || task.extra_metadata.device_id === this.device?.id) &&
281+
task.facility_id === this.facilityId &&
282+
task.type === this.taskType &&
283+
// Only show tasks that are repeating indefinitely
284+
task.repeat === null,
285+
);
286+
},
269287
deviceName() {
270288
return this.device && this.device.nickname && this.device.nickname.length
271289
? this.device.nickname
272290
: this.device.device_name;
273291
},
274292
currentTask() {
275-
return this.tasks && this.tasks.length ? this.tasks[0] : null;
293+
return this.filteredTasks.length ? this.filteredTasks[0] : null;
276294
},
277295
currentTaskRunning() {
278-
return this.currentTask && this.currentTask.status === TaskStatuses.RUNNING;
296+
return this.currentTask?.status === TaskStatuses.RUNNING;
279297
},
280298
timeRequired() {
281299
return this.selectedItem.value > oneHour;
@@ -304,6 +322,36 @@
304322
);
305323
},
306324
},
325+
watch: {
326+
currentTask() {
327+
if (this.currentTask && !this.userHasEdited) {
328+
const enqueueAt = new Date(Date.parse(this.currentTask.scheduled_datetime));
329+
const day = enqueueAt.getDay();
330+
const hours = enqueueAt.getHours();
331+
const minutes = enqueueAt.getMinutes();
332+
this.selectedItem =
333+
this.selectArray.find(item => item.value === this.currentTask.repeat_interval) || {};
334+
this.selectedDay = this.getDays.find(item => item.value === day) || {};
335+
for (const time of this.SyncTime) {
336+
// Because there can be some drift in the task scheduling process,
337+
// we round the 'scheduled' time to the nearest 30 minutes
338+
if (
339+
time.minutes === 0 &&
340+
((time.hours === hours && minutes < 15) ||
341+
(time.hours === hours + 1 && minutes >= 45))
342+
) {
343+
this.selectedTime = time;
344+
break;
345+
}
346+
if (time.minutes === 30 && time.hours === hours && minutes >= 15 && minutes < 45) {
347+
this.selectedTime = time;
348+
break;
349+
}
350+
}
351+
this.retryFlag = Boolean(this.currentTask.retry_interval);
352+
}
353+
},
354+
},
307355
created() {
308356
this.fetchDevice();
309357
this.now = now();
@@ -393,67 +441,11 @@
393441
})
394442
.catch(() => {
395443
this.createTaskFailedSnackbar();
396-
if (this.currentTask) {
397-
this.fetchSyncTasks();
398-
}
399444
});
400445
},
401-
402446
goBack() {
403447
this.$router.push(this.goBackRoute);
404448
},
405-
pollFetchSyncTasks() {
406-
this.pollInterval = setInterval(() => {
407-
this.fetchSyncTasks();
408-
}, 10000);
409-
},
410-
fetchSyncTasks() {
411-
TaskResource.list({ queue: 'facility_task' }).then(tasks => {
412-
this.tasks = tasks.filter(
413-
task =>
414-
(this.isKdp || task.extra_metadata.device_id === this.device.id) &&
415-
task.facility_id === this.facilityId &&
416-
task.type === this.taskType &&
417-
// Only show tasks that are repeating indefinitely
418-
task.repeat === null,
419-
);
420-
this.$nextTick(() => {
421-
if (this.currentTask) {
422-
const enqueueAt = new Date(Date.parse(this.currentTask.scheduled_datetime));
423-
const day = enqueueAt.getDay();
424-
const hours = enqueueAt.getHours();
425-
const minutes = enqueueAt.getMinutes();
426-
this.selectedItem =
427-
this.selectArray.find(item => item.value === this.currentTask.repeat_interval) ||
428-
{};
429-
this.selectedDay = this.getDays.find(item => item.value === day) || {};
430-
for (const time of this.SyncTime) {
431-
// Because there can be some drift in the task scheduling process,
432-
// we round the 'scheduled' time to the nearest 30 minutes
433-
if (
434-
time.minutes === 0 &&
435-
((time.hours === hours && minutes < 15) ||
436-
(time.hours === hours + 1 && minutes >= 45))
437-
) {
438-
this.selectedTime = time;
439-
break;
440-
}
441-
if (time.minutes === 30 && time.hours === hours && minutes >= 15 && minutes < 45) {
442-
this.selectedTime = time;
443-
break;
444-
}
445-
}
446-
this.retryFlag = Boolean(this.currentTask.retry_interval);
447-
if (this.currentTaskRunning) {
448-
this.pollFetchSyncTasks();
449-
} else {
450-
clearInterval(this.pollInterval);
451-
this.pollInterval = null;
452-
}
453-
}
454-
});
455-
});
456-
},
457449
fetchDevice() {
458450
if (this.isKdp) {
459451
this.device = {
@@ -462,14 +454,19 @@
462454
device_name: kdpNameTranslator.$tr('syncToKDP'),
463455
base_url: '',
464456
};
465-
this.fetchSyncTasks();
466457
return;
467458
}
468459
NetworkLocationResource.fetchModel({ id: this.deviceId }).then(device => {
469460
this.device = device;
470-
this.fetchSyncTasks();
471461
});
472462
},
463+
handleUserInput() {
464+
this.userHasEdited = true;
465+
},
466+
handleRetryCheckboxChange() {
467+
this.retryFlag = !this.retryFlag;
468+
this.handleUserInput();
469+
},
473470
},
474471
$trs: {
475472
editSyncScheduleTitle: {

packages/kolibri-common/components/SyncSchedule/ManageSyncSchedule.vue

+11-18
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@
117117
import { computed } from 'vue';
118118
import ImmersivePage from 'kolibri/components/pages/ImmersivePage';
119119
import CoreTable from 'kolibri/components/CoreTable';
120-
import TaskResource from 'kolibri/apiResources/TaskResource';
121120
import FacilityResource from 'kolibri-common/apiResources/FacilityResource';
122121
import commonCoreStrings from 'kolibri/uiText/commonCoreStrings';
123122
import commonSyncElements from 'kolibri-common/mixins/commonSyncElements';
@@ -127,6 +126,7 @@
127126
useDevicesWithFilter,
128127
} from 'kolibri-common/components/syncComponentSet/SelectDeviceModalGroup/useDevices';
129128
import { TaskTypes } from 'kolibri-common/utils/syncTaskUtils';
129+
import useTaskPolling from '../../composables/useTaskPolling';
130130
import { KDP_ID, oneHour, oneDay, oneWeek, twoWeeks, oneMonth } from './constants';
131131
import { kdpNameTranslator } from './i18n';
132132
@@ -141,6 +141,7 @@
141141
mixins: [commonCoreStrings, commonSyncElements],
142142
setup(props) {
143143
const deviceFilter = useDeviceFacilityFilter({ id: props.facilityId });
144+
const { tasks } = useTaskPolling('facility_task');
144145
const { devices } = useDevicesWithFilter(
145146
{
146147
subset_of_users_device: false,
@@ -165,6 +166,7 @@
165166
});
166167
return {
167168
devicesById,
169+
tasks,
168170
};
169171
},
170172
props: {
@@ -185,10 +187,17 @@
185187
return {
186188
deviceModal: false,
187189
facility: null,
188-
facilitySyncTasks: [],
189190
};
190191
},
191192
computed: {
193+
facilitySyncTasks() {
194+
return this.tasks.filter(
195+
t =>
196+
t.facility_id === this.facilityId &&
197+
t.repeat === null &&
198+
(t.type === TaskTypes.SYNCDATAPORTAL || t.type === TaskTypes.SYNCPEERFULL),
199+
);
200+
},
192201
scheduledTasks() {
193202
return this.facilitySyncTasks.map(task => {
194203
const deviceName = this.devicesById[this.getDeviceId(task)]
@@ -208,7 +217,6 @@
208217
},
209218
},
210219
beforeMount() {
211-
this.pollFacilityTasks();
212220
this.fetchFacility();
213221
},
214222
methods: {
@@ -217,21 +225,6 @@
217225
this.facility = { ...facility };
218226
});
219227
},
220-
pollFacilityTasks() {
221-
TaskResource.list({ queue: 'facility_task' }).then(tasks => {
222-
this.facilitySyncTasks = tasks.filter(
223-
t =>
224-
t.facility_id === this.facilityId &&
225-
t.repeat === null &&
226-
(t.type === TaskTypes.SYNCDATAPORTAL || t.type === TaskTypes.SYNCPEERFULL),
227-
);
228-
if (this.isPolling) {
229-
setTimeout(() => {
230-
return this.pollFacilityTasks();
231-
}, 2000);
232-
}
233-
});
234-
},
235228
closeModal() {
236229
this.deviceModal = false;
237230
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import logger from 'kolibri-logging';
2+
import { ref, onMounted, onUnmounted } from 'vue';
3+
import { useTimeoutPoll } from '@vueuse/core';
4+
import TaskResource from 'kolibri/apiResources/TaskResource';
5+
6+
const taskPollers = new Map();
7+
8+
const logging = logger.getLogger(__filename);
9+
10+
export default function useTaskPolling(queueName) {
11+
if (!taskPollers.has(queueName)) {
12+
const consumers = ref(0);
13+
const tasks = ref([]);
14+
const { pause, resume, isActive } = useTimeoutPoll(
15+
async () => {
16+
try {
17+
tasks.value = await TaskResource.list({ queue: queueName });
18+
} catch (e) {
19+
logging.error('Error while fetching tasks', e);
20+
}
21+
},
22+
5000,
23+
{ immediate: true },
24+
);
25+
26+
taskPollers.set(queueName, { consumers, tasks, pause, resume, isActive });
27+
}
28+
29+
const poller = taskPollers.get(queueName);
30+
31+
onMounted(() => {
32+
poller.consumers.value++;
33+
if (!poller.isActive.value) {
34+
poller.resume();
35+
}
36+
});
37+
38+
onUnmounted(() => {
39+
poller.consumers.value--;
40+
if (poller.consumers.value === 0) {
41+
poller.pause();
42+
taskPollers.delete(queueName);
43+
}
44+
});
45+
46+
return { tasks: poller.tasks };
47+
}

0 commit comments

Comments
 (0)