diff --git a/temba/triggers/views.py b/temba/triggers/views.py index 2bd73ef83ce..28c2ea501d5 100644 --- a/temba/triggers/views.py +++ b/temba/triggers/views.py @@ -358,6 +358,7 @@ class BaseLargeSendForm(forms.ModelForm): (45, _("45 minutes")), (60, _("1 hour")), ) + CHUNK_MAX_LIMIT = 500 batch_interval = forms.ChoiceField( choices=BATCH_INTERVAL, @@ -366,12 +367,14 @@ class BaseLargeSendForm(forms.ModelForm): required=True, widget=SelectWidget(), ) + flow = TembaChoiceField( Flow.objects.none(), label=_("Flow"), required=True, widget=SelectWidget(attrs={"placeholder": _("Select a flow"), "searchable": True}), ) + groups = TembaMultipleChoiceField( queryset=ContactGroup.user_groups.none(), label=_("Groups"), @@ -381,15 +384,18 @@ class BaseLargeSendForm(forms.ModelForm): attrs={"icons": True, "placeholder": _("Select contact groups"), "searchable": True} ), ) + start_time = forms.DateTimeField( required=True, label=_("Start Time for Send"), widget=InputWidget(attrs={"datetimepicker": True, "placeholder": _("Select a date and time")}), ) + chunk_size = forms.IntegerField( label=_("Chunks"), - help_text=_("I want to split the message to this many pieces"), - widget=InputWidget(attrs={"type": "number", "placeholder": _("Enter chunk size")}), + max_value=CHUNK_MAX_LIMIT, + help_text=_("I want to split the message to this many pieces. Max: 500"), + widget=InputWidget(attrs={"type": "number", "max": CHUNK_MAX_LIMIT, "placeholder": _("Enter chunk size")}), ) limit_time = forms.BooleanField(required=False, label=_("Limit to business hours"), help_text=_("9 AM to 5 PM")) @@ -414,9 +420,14 @@ class LargeSendMixin: @classmethod def get_new_time(cls, input_time, hour, add_to_day=0): - input_time = datetime.datetime( - year=input_time.year, month=input_time.month, day=input_time.day + add_to_day, hour=hour - ) + try: + input_time = datetime.datetime( + year=input_time.year, month=input_time.month, day=input_time.day + add_to_day, hour=hour + ) + except ValueError: + input_time = datetime.datetime( + year=input_time.year, month=input_time.month, day=input_time.day, hour=hour + ) + timedelta(days=add_to_day) return timezone.make_aware(input_time) def derive_start_time(self, start_time, limit_time): diff --git a/templates/triggers/trigger_create_large_send.haml b/templates/triggers/trigger_create_large_send.haml index be13dda8ab9..80258cf9d50 100644 --- a/templates/triggers/trigger_create_large_send.haml +++ b/templates/triggers/trigger_create_large_send.haml @@ -91,6 +91,12 @@ groupFields.forEach(elem => groupList.push(Number(elem.value))); if (startTime && groupList.length > 0 && intervalValue && chunkSizeValue) { + if (chunkSizeValue > 500) { + showAlertMessage("Chunks should be max of 500", "red"); + recreatePrimaryButton(true); + return + } + const formData = new FormData(); formData.append('groups', groupList); formData.append('start_time', startTime); @@ -100,24 +106,54 @@ const formOption = { method: 'POST', body: formData, + signal: AbortSignal.timeout(30000), headers: { 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken') } } + showAlertMessage("Calculating groups...", "black"); + recreatePrimaryButton(true); fetch('{% url "triggers.trigger_large_send_schedule_summary" %}', formOption) .then((response) => response.json()) .then((response) => { toggleScheduleList(response); + recreatePrimaryButton(false); + }).catch((error) => { + console.log("[trigger_large_send_schedule_summary]", error) + showAlertMessage("Unable to process the summary, please try again.", "red") }); } - }, 100); + }, 500); + } + + function recreatePrimaryButton(disable) { + const { dialogFooter } = getFormElements(); + const currentSubmit = dialogFooter.querySelector('temba-button'); + let options = { name: 'Create', onclick: showConfirmation, primary: true }; + if (disable) { + options['disabled'] = true; + } + const submitButton = createButton(options); + try { + if (currentSubmit) { + dialogFooter.removeChild(currentSubmit); + dialogFooter.prepend(submitButton); + } + } catch (e) {} } function chunkBy(number, n) { return new Array(Math.floor(number / n)).fill(n).concat(number % n) } + function showAlertMessage(msg, color) { + const { modaxBody } = getFormElements(); + const scheduleListPreview = modaxBody.querySelector('.schedule-list-preview'); + scheduleListPreview.innerHTML = `
${msg}
`; + scheduleListPreview.classList.remove('hidden'); + } + function toggleScheduleList(response) { const { modaxBody } = getFormElements(); const scheduleListPreview = modaxBody.querySelector('.schedule-list-preview'); @@ -152,6 +188,7 @@ event.preventDefault(); const scheduleList = modaxBody.querySelector('#schedule-list'); const confirmationContent = confirmationDialog.querySelector('.p-6'); + confirmationContent.innerHTML = "Please confirm you want to send on the following schedule:" if (scheduleList) confirmationContent.appendChild(scheduleList); confirmationDialog.open = true; } @@ -186,9 +223,11 @@ hasError = true; } }); - event.target.disabled = false; - if (hasError) confirmationDialog.open = false; - else window.location.href = '{% url "triggers.trigger_list" %}'; + if (hasError) { + confirmationDialog.open = false; + } else { + window.location.href = '{% url "triggers.trigger_list" %}'; + } }); } @@ -224,7 +263,7 @@ initCalFields(form); onValueChange(); const currentSubmit = dialogFooter.querySelector('temba-button[primary]'); - const submitButton = createButton({ name: 'Create', onclick: showConfirmation, primary: true }); + const submitButton = createButton({ name: 'Create', onclick: showConfirmation, primary: true, disabled: true }); try { if (currentSubmit) { dialogFooter.removeChild(currentSubmit);