Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PRO] Make it possible to 'snooze' tasks. #219

Open
emihir0 opened this issue Jun 26, 2018 · 5 comments
Open

[PRO] Make it possible to 'snooze' tasks. #219

emihir0 opened this issue Jun 26, 2018 · 5 comments

Comments

@emihir0
Copy link

emihir0 commented Jun 26, 2018

Very often tasks can take a long time - for example a simple flow can be:

  1. Send draft order to customer
  2. Confirm order
  3. ...

It's pointless for confirm_order task to take up space in inbox (hence clutter it and more pressing tasks are not so visible) when we know the customer can take up to XX days until he responds. My suggestion is to let us be able to snooze task so it does not show up in inbox by default, but it can still be filtered to be shown there. Basically by default the snoozed tasks are filtered out.

Hence, ideally, it should be possible to:

  1. Snooze task as User until later datetime.
  2. Snooze task at flow definition (e.g. retry type of tasks) in code - similar to how Assign can be done directly in code.

The only tricky part about this is that there must be some background process that switches the tasks from snoozed to non-snoozed when the datetime is reached.

Edit: There is no need for background process. There would only have to be a snoozed_until datetime field and if the current time is lower than snoozed_until, then the task is snoozed - simple as that.

@codingjoe
Copy link

@emihir0 we already implemented this feature. I'll ask someone in my team to share the code here.
It would be a cool feature though to have it in the core library.

@emihir0
Copy link
Author

emihir0 commented Jun 27, 2018

@codingjoe Thanks, that would be much appreciated. Indeed this would be a nice feature to have in core.

Are there any other 'cool' functionalities your team has implemented that you think should be in core?

@codingjoe
Copy link

So essentially we have our custom Task model as well as a View mixin. Note that the view mixin requires the view to be saveable, see also http://viewflow-extensions.readthedocs.io/en/latest/views.html

class SnoozeableViewActivationMixin:

    _snooze = None

    def get_form_class(self):
        """Field validation in Django 1.10 blocks the functionality of snoozing."""
        form_class = super().get_form_class()
        form_class.use_required_attribute = False
        return form_class

    def activation_done(self, *args, **kwargs):
        if not self._snooze:
            super().activation_done(*args, **kwargs)

    def message_snooze(self, snooze_date):
        activation = self.request.activation
        hyperlink = activation.flow_task.get_task_url(activation.task)
        msg = _('Task {hyperlink} has been snoozed until {snooze_date}.').format(
            hyperlink=hyperlink, snooze_date=snooze_date.strftime("%d.%m.%Y %H:%M"))
        messages.success(self.request, mark_safe(msg), fail_silently=True)  # nosec

    def post(self, request, *args, **kwargs):
        self._snooze = request.POST.get('_snooze')
        if self._snooze:
            max_days = settings.VIEWFLOW_TASK_SNOOZE_MAX_DAYS
            now = local_now()
            snooze_date = self.compute_snooze_date(start_date=now, snooze=self._snooze)

            if snooze_date - now > datetime.timedelta(days=max_days):
                msg = _('You cannot postpone a task for more than {} days.').format(max_days)
                return self.snooze_fail(msg)

            if snooze_date < now:
                return self.snooze_fail(_('The requested postpone date is in the past.'))

            self.request.activation.task.snooze = snooze_date
            self.save_task()
            self.message_snooze(snooze_date)
            return redirect(self.get_success_url())

        return super().post(request, *args, *kwargs)

    def snooze_fail(self, msg):
        self.save_task()
        messages.error(self.request, msg, fail_silently=True)
        return redirect(self.request.get_full_path())

    def get_success_url(self):
        if self._snooze:
            process = self.request.activation.task.process
            return reverse('{}:detail'.format(process.flow_class.__name__), args=[process.pk])

        return super().get_success_url()

    @staticmethod
    def compute_snooze_date(start_date, snooze):
        day_start = dict(hour=7, minute=0, second=0)

        hours = re.match(r'(\d+)h', snooze)
        if hours:
            return start_date + datetime.timedelta(hours=int(hours.group(1)))

        if snooze == 'tomorrow':
            return (start_date + datetime.timedelta(days=1)).replace(**day_start)

        if snooze == 'day_after_tomorrow':
            return (start_date + datetime.timedelta(days=2)).replace(**day_start)

        if snooze == 'next_week':
            days_ahead = 7 - start_date.weekday()
            return (start_date + datetime.timedelta(days=days_ahead)).replace(**day_start)

        if snooze == 'after_lunch':
            after_lunch = start_date.replace(hour=13, minute=0, second=0)
            if start_date < after_lunch:
                return after_lunch
            else:
                return after_lunch + datetime.timedelta(days=1)

        try:
            days_ahead = int(snooze)
            return (start_date + datetime.timedelta(days=days_ahead)).replace(**day_start)

        except ValueError:
            # custom date
            return parser.parse(snooze).replace(**day_start, tzinfo=get_current_timezone())
class SnoozebleTask(AbstractTast):
    """
    Like :class:`.viewflow.models.Task` but with an additional due date.

    The default due date will be the time of instance creation but can
    be changed to any future or past time.
    """

    snooze = models.DateTimeField(_('snooze'), blank=True, null=True)

We have a viewflow-extension package, where we usually keep all our custom features to share them across apps. Its open source, feel free to use it. In this particular case though, we haven't put the feature in there yet. But we should really do that 😉

@emihir0
Copy link
Author

emihir0 commented Jul 1, 2018

Thanks! That's really useful.

@kmmbvnr
Copy link
Contributor

kmmbvnr commented Jul 2, 2018

The thing that you're asking is called "Manual Event" in the BPMN systems.

@viewflow viewflow locked and limited conversation to collaborators Jul 2, 2018
@viewflow viewflow unlocked this conversation Apr 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants