# Type: Input # Category: Task # Description: Used to put new task into Redmine # Dialog: -> Issue -> Activity # Fields: # Uses: YesNo # Copied: No # Author: Andriy Lesyuk use Orangutan::Task; use Orangutan::User; my @newtask = ( 'I guess this is the name of new task... Should I stop the current one and start new task?', 'Should I add this as a new task automatically stopping the current one?', 'Do you want me to stop the current task and start this one?', 'Is this a new task which should replace the current one?' ); my @newname = ( 'You want me to rename your current task with this name?', 'Is this new name for the current task?' ); my @neither = ( 'So what is this?!.. Ok. Do not answer! Just ignoring it.', 'So I guess this is some kind of "thoughts aloud"...', 'Then I did not understand what you meant...' ); new Orangutan::Context( response => sub { my ($context, $user, $message) = @_; my $regexp; my @result = ( ); # Remove ending $message =~ s/!*\.*$//; # Extract/remove time $regexp = '\b *(?:since|from|at) ([0-9]{1,2}(?:[:-][0-9]{2})? *(?:(?:a\.?|p\.?)m\.?)?)$'; if ($message =~ /$regexp/i) { my $date = new Orangutan::Date($1); if ($date) { unshift(@result, $date); $message =~ s/$regexp//i; } else { unshift(@result, undef); } } else { unshift(@result, undef); } if (!defined($result[0])) { $regexp = '^(?:(?:From|since|at) )?([0-9]{1,2}(?:[:-][0-9]{2})? *(?:(?:a\.?|p\.?)m\.?)?) *[:-]? *\b'; if ($message =~ /$regexp/i) { my $date = new Orangutan::Date($1); if ($date) { $result[0] = $date; $message =~ s/$regexp//i; } } } # Extract/remove activity $regexp = '\b *\(([^)]+)\)$'; if ($message =~ /$regexp/i) { my $activity = $main::query->ActivityExists($1); if (defined($activity) && ($activity ne '')) { unshift(@result, $1); $message =~ s/$regexp//i; } else { unshift(@result, undef); } } else { unshift(@result, undef); } # Extract/remove project $regexp = '(.*?)\b *(?:on|for|at) ["\']?(.+?)["\']?$'; if ($message =~ /$regexp/i) { my $project = $main::query->ProjectExists($2); if (defined($project) && (ref $project eq 'HASH')) { unshift(@result, $project); $message =~ s/$regexp/$1/i; } else { unshift(@result, undef); } } else { unshift(@result, undef); } # Extract/remove issue my $trackers = $main::config->Get('redmine', '_trackers_regexp'); if ($trackers) { $regexp = '\b *(?:(?:on|for|at|of) )?(?:the )?(?:(?:issue|'.$trackers.') )?(?:#|No\.? ?)([0-9]+)$'; } else { $regexp = '\b *(?:(?:on|for|at|of) )?(?:the )?(?:issue )?(?:#|No\.? ?)([0-9]+)$'; } if ($message =~ /$regexp/i) { unshift(@result, $1); $message =~ s/$regexp//i; } else { unshift(@result, undef); } if (!defined($result[0])) { if ($trackers) { $regexp = '^(?:the )?(?:(?:issue|'.$trackers.') )?(?:#|No\.? ?)([0-9]+) *[:-]? *\b'; } else { $regexp = '^(?:the )?(?:issue )?(?:#|No\.? ?)([0-9]+) *[:-]? *\b'; } if ($message =~ /$regexp/i) { $result[0] = $1; $message =~ s/$regexp//i; } } # Get task name if ($message =~ /^(?:I(?:\'m| (?:am|will(?: be)?|start(?:ed)?))?(?: do(?:ing)?)? )?(.*[a-z]+.*)$/i) { unshift(@result, $1); unshift(@result, 1); } else { @result = ( ); } return @result; }, handler => sub { my ($context, $user, $item, $name, $issueid, $project, $activity, $date) = @_; $context->UnsetField('started'); if ($item == -1) { my $taskfield = $user->UnsetField('newtask'); my $namefield = $user->UnsetField('newname'); if (defined($taskfield) && ($taskfield eq '?')) { if ($name eq 'Y') { my $curtask = $user->GetTask; if (defined($curtask)) { my $newdate = $user->GetField('tasktime'); if (!defined($newdate) || ($newdate->Date >= $curtask->GetStart->Date)) { if (!defined($newdate)) { $newdate = new Orangutan::Date; } if ($newdate->Date > $curtask->GetStart->Date) { if (($newdate->Date - $curtask->GetStart->Date) >= 86400) { $user->SendMessage([ "You previous task lasts for more than 24 hours! ". "I can't let you enter new task until you stop the current one...", "Please stop your current task first! I would do this by myself but I can't ". "because I don't know its end time and it lasts for more than 24 hours...", "Stop your current task first! Current time is not good for its end time ". "because it's over 24 hours from its start time!" ]); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } return; } else { $curtask->SetEnd($newdate); } } else { $curtask->SetEnd(new Orangutan::Date($curtask->GetStart->Date + 60)); } if (!defined($curtask->GetActivity('id'))) { my $default_activity = $main::query->GetDefaultActivity($curtask->GetProject('id')); if (defined($default_activity)) { $curtask->SetActivity( id => $default_activity->{'id'}, name => $default_activity->{'name'} ); my $format = Orangutan::Context::Random( 'Using activity %s (the default one)...', 'Setting activity to "%s"...' ); $user->SendMessage(sprintf($format, $default_activity->{'name'})); } else { $user->SendMessage([ 'Something is wrong with activities in the project of the previous task...', "Can't find a single activity for the project of the previous task. :S" ]); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } return; } } if ($main::query->GetEntries($user, $curtask->GetStart, $curtask->GetEnd) == 0) { if ($curtask->GetStart->WorkDayStart->Date != $curtask->GetEnd->WorkDayStart->Date) { my $copy = new Orangutan::Task($curtask); $curtask->SetEnd($curtask->GetStart->WorkDayEnd); $user->SubmitTask; $user->SetTask($copy); $copy->SetStart($copy->GetEnd->WorkDayStart); $user->SubmitTask; } else { $user->SubmitTask; } $user->FireEvent('task', 'end', $curtask); $name = $user->GetField('taskname'); $date = $user->GetField('tasktime'); $issueid = $user->GetField('taskissue'); $project = $user->GetField('taskproject'); $activity = $user->GetField('taskactivity'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } } else { $user->SendMessage([ 'Not so simple! The task you told me conflicts with some task in Redmine...', "Sorry, but I can't do this. There are conflicts between tasks...", 'Previous task conflicts with some tasks in Redmine.' ]); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } return; } } else { $user->SendMessage([ "The start time conflicts with the task you are working on. Sorry but I can't add this new task.", "Looks like your current task should be dropped... But I can't do this without your request!", "I'm not sure what to do with your previous task... So I can't do what you request.", 'Hey! Either change the start time or remove your current task!' ]); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } return; } } else { $user->SendMessage('I got tangled.'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } return; } } else { if (!defined($namefield)) { $user->SetField('newtask', $name); $user->SetField('newname', '?'); $user->SendMessage(\@newname); } else { $user->SendMessage(\@neither); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } } return; } } elsif (defined($namefield) && ($namefield eq '?')) { if ($name eq 'Y') { my $curtask = $user->GetTask; my $taskname = $user->GetField('taskname'); if (defined($curtask) && defined($taskname)) { $curtask->SetName($taskname); $main::config->Context('Ok')->Tell($user); my @args = ( ); if ($user->GetField('taskissue')) { push(@args, Orangutan::Context::Random('issue ID', 'issue', 'issue number')); } if ($user->GetField('taskproject')) { push(@args, Orangutan::Context::Random('project name', 'project', 'task project')); } if ($user->GetField('taskactivity')) { push(@args, Orangutan::Context::Random('activity name', 'activity', 'project activity')); } if ($user->GetField('tasktime')) { push(@args, Orangutan::Context::Random('start time', 'time')); } if (scalar @args > 0) { my $message = 'Ignoring '; if (scalar @args > 2) { $message .= join(', ', @args[0..($#args-1)]); } elsif (scalar @args == 2) { $message .= $args[0]; } if (scalar @args > 1) { $message .= ' and '; } $message .= $args[$#args]; $message .= '...'; $user->SendMessage($message); } } else { $user->SendMessage('Hm... I got tangled.'); } my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } } else { if (!defined($taskfield)) { $user->SetField('newname', $name); $user->SetField('newtask', '?'); $user->SendMessage(\@newtask); } else { $user->SendMessage(\@neither); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } } } return; } } else { # Check if the task is already specified my $curtask = $user->GetTask; if (defined($curtask)) { if ($curtask->GetName eq $name) { my $curstart = undef; if ($curtask->IsSet(TASK_TIMESET)) { $curstart = $curtask->GetStart; } if (!defined($date) || (defined($curstart) && ($curstart->Date == $date->Date))) { $user->SendMessage([ 'You have already told...', 'I already know...', 'I know...', 'You need to tell me only if you have started working on another task...' ]); } else { $user->SendMessage([ 'You want me to change start time for the task?.. If so just tell "Change start time to HH:MM"...', 'Too late to change time! ;) Kidding... Just tell "Change start time to HH:MM"...', 'Please use "Change start time to HH:MM" to change start time of current task!', 'I can\'t be so smart! I\'m a bot! Want to stop current task and begin another one? '. 'Send the same by mistake? If you want me to change time send "Change start time to HH:MM"!' ]); } } else { my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $user->SetField('taskname', $name); if (defined($issueid)) { $user->SetField('taskissue', $issueid); } else { $user->UnsetField('taskissue'); } if (defined($project)) { $user->SetField('taskproject', $project); } else { $user->UnsetField('taskproject'); } if (defined($activity)) { $user->SetField('taskactivity', $activity); } else { $user->UnsetField('taskactivity'); } if (defined($date)) { $user->SetField('tasktime', $date); my $curstart = $curtask->GetStart; if ($date->Date <= $curstart->Date) { $user->SetField('newname', '?'); $user->SendMessage(\@newname); $user->UnsetField('newtask'); } else { $user->SetField('newtask', '?'); $user->SendMessage(\@newtask); $user->UnsetField('newname'); } } else { $user->SetField('newtask', '?'); $user->SendMessage(\@newtask); $user->UnsetField('tasktime'); $user->UnsetField('newname'); } $yesno->InsertForeignHandler($context); } else { $user->SendMessage([ 'Oops! I forgot the language... Can not even ask you a question!..', 'Damn! Something is wrong with my vocabulary...' ]); } } return; } } if (defined($date) && ((time - $date->Date) > 72000)) { my $format = Orangutan::Context::Random( 'Treating specified time as %s, %s %d %02d:%02d...', 'Assuming that you meant %s, %s %d %02d:%02d...' ); my ($min, $hour, $mday, $mon, $year, $wday) = (localtime($date->Date))[1..6]; $user->SendMessage(sprintf($format, $Orangutan::Date::weekdays[$wday], $Orangutan::Date::months[$mon], $mday, $hour, $min)); } # Check if time does not overlap my $last = $user->GetLast; if (defined($last)) { if (defined($date)) { if ($last->GetEnd->Date > $date->Date) { $user->SendMessage([ 'You were still working on another task at that time!', 'Tasks time may not overlap! Sorry...', 'You have not finished your previous task at that time!' ]); return; } } else { my $time = new Orangutan::Date; if (($last->GetEnd->Date - $time->Date) > 86400) { $user->SendMessage([ "Oops! Something weird is happenning... Can't add the task!", 'I can start this task only in more than a day. Sorry!' ]); return; } } } # Warning if more than 9 hours if (defined($date)) { my $elapsed = int($date->Elapsed / 3600); if ($elapsed > 9) { $format = Orangutan::Context::Random( "Hey! It's %d hours...", "Wow!.. It's %d hours...", 'Cool!.. So you are working %d hours...' ); $user->SendMessage(sprintf($format, $elapsed)); } } # Fetch issue details my $issue = undef; if (defined($issueid)) { $issue = $main::query->GetIssue($issueid); if (defined($issue)) { utf8::decode($issue->{'project_name'}); if (!$main::query->Can('edit_time_entries', $user, $issue->{'project_id'})) { $format = Orangutan::Context::Random( 'You must be a member of %2$s project to be able to log your time for issue #%d!', 'Issue #%d is for %s project and you are not a member of this project!', "Can't add this task because you are not a member of project %2\$s...", 'To track time for issue #%d you must be a member of %s project!' ); $user->SendMessage(sprintf($format, $issueid, $issue->{'project_name'})); return; } if ($issue->{'is_closed'}) { $user->SendMessage([ 'Hm... I thought this issue had already been closed...', 'I hope you know that this issue is already closed...', 'Hm... This issue is marked to be closed...', 'Hm... Weird... This issue is closed...' ]); } elsif ($issue->{'done_ratio'} == 100) { $user->SendMessage([ 'Weird... Looks like someone believes that this issue was done...', 'Hm... I thought this issued had been completed...', 'Hm... This issue looks to be 100% done...' ]); } } else { $format = Orangutan::Context::Random( 'Sorry, but I cannot find issue #%d...', 'Issue #%d does not exist.', 'There is no issue #%d...' ); $user->SendMessage(sprintf($format, $issueid)); return; } } elsif ($name =~ /(?:#|No\.? ?)([0-9]+)/) { $issue = $main::query->GetIssue($1); if (defined($issue) && (!$main::query->Can('edit_time_entries', $user, $issue->{'project_id'}) || $issue->{'is_closed'} || ($issue->{'done_ratio'} == 100))) { $issue = undef; } } # Set task my %taskargs = ( ); $taskargs{'name'} = $name; if (defined($issue)) { $taskargs{'issue'} = $issue->{'id'}; if (defined($project)) { if ($project->{'id'} == $issue->{'project_id'}) { $user->SendMessage([ 'Project name is optional and usually ignored. Only issue is required!', 'No need to specify the project name if you specify the issue...', "You don't need to specify the project name..." ]); } else { $user->SendMessage([ 'Ignoring specified project - this issue does not belong to it...', 'This issue is in different project...', 'I guess you mixed up the project...' ]); } } $taskargs{'project'} = { id => $issue->{'project_id'}, name => $issue->{'project_name'} }; } elsif (defined($project)) { if ($main::query->Can('edit_time_entries', $user, $project->{'id'})) { $taskargs{'project'} = { id => $project->{'id'}, name => $project->{'name'} }; } else { $format = Orangutan::Context::Random( 'To be able to create tasks for %s you must be a member of this project.', 'You have no permissions to log for %s...', 'You must be a member of %s!..' ); $user->SendMessage(sprintf($format, $project->{'name'})); return; } } elsif ($user->GetProject) { $taskargs{'project'} = { id => $user->GetProjectID, name => $user->GetProject }; if ($user->GetIssue) { $issue = $main::query->GetIssue($user->GetIssue); if ($issue) { $taskargs{'issue'} = $issue->{'id'}; } } } else { $user->SendMessage([ 'You must specify the project name for this task! Cannot start the task without a project name...', "You specified neither task's project nor default project... Sorry but I won't create this task.", "We are going to have troubles with the task without a project... So I'm not creating this task." ]); return; } if (defined($taskargs{'project'})) { if (defined($activity)) { my $activityid = $main::query->ActivityExists($activity, $taskargs{'project'}->{'id'}); if ($activityid) { $taskargs{'activity'} = { id => $activityid, name => $activity }; } else { $user->SendMessage([ 'Ignoring specified activity - it does not exists for this project...', 'Cannot find such activity in this project...', 'This activity is not in this project...' ]); } } elsif ($user->GetActivity) { my $activityid = $main::query->ActivityExists($user->GetActivity, $taskargs{'project'}->{'id'}); if ($activityid) { $taskargs{'activity'} = { id => $activityid, name => $user->GetActivity }; } } if (!defined($taskargs{'activity'})) { my $default_activity = $main::query->GetDefaultActivity($taskargs{'project'}->{'id'}); if (defined($default_activity)) { $taskargs{'activity'} = { id => $default_activity->{'id'}, name => $default_activity->{'name'} }; } } } elsif (defined($activity)) { $user->SendMessage([ 'Ignored specified activity because neither project nor issue was specified...', "Can't use specified activity without knowing what is the project...", 'You specified activity name but forget about issue ID...' ]); } if (defined($date)) { $taskargs{'start'} = $date; } elsif (defined($last)) { my $enddate = new Orangutan::Date; if ($last->GetEnd->Date > $enddate->Date) { $enddate->Set($last->GetEnd->Date); $taskargs{'start'} = $enddate; } } my $task = $user->StartTask(%taskargs); $context->SetField('started', 1); $user->FireEvent('task', 'start', $task); # Write message my $message = Orangutan::Context::Random('Ok... ', 'Got it!.. ', 'I see... '); $message .= Orangutan::Context::Random( 'Getting this as your current task', 'Your current task is clear', "I hope this task is not very complex and you'll get it done easily" ); if (defined($date)) { $message .= sprintf(" (start time is %02d:%02d)", $date->GetTime); } $message .= '.'; if (defined($issue)) { utf8::decode($issue->{'subject'}); if (defined($issueid)) { $message .= " The issue is "; } else { $message .= " Assuming the issue is "; } $message .= $issue->{'tracker_name'}.' '; $message .= sprintf("#%d", $issue->{'id'}); $message .= ' "'.$issue->{'subject'}.'"'; my $projectname = $task->GetProject; $message .= " (project is \"$projectname\")"; my $activityname = $task->GetActivity; if (defined($activityname)) { if (defined($issueid) && !defined($activity)) { $message .= ". Assuming the activity is \"$activityname\""; } else { $message .= " and the activity is \"$activityname\""; } } else { my $activity = $user->GetResponse('Activity'); if ($activity) { $user->AddRequest($activity); } } $message .= '...'; } else { my $response = $user->GetResponse('Issue'); if ($response) { $user->AddRequest($response); } } $user->SendMessage($message); # Warn if description is going to be truncated my $max_length = $main::config->Get('redmine', 'description_max_length'); if (defined($max_length) && ($max_length > 0)) { if (length($name) > $max_length) { $user->SendMessage([ 'Be aware that the description you specified is too long and will be truncated when saved to Redmine...', 'Want to warn you that Redmine does not support such a long task description...', "I believe Redmine is going to truncate the task name beacuse it's too long..." ]); } } }, nomatch => sub { my ($context, $user) = @_; $user->SendMessage([ 'I need some short description of your work...', 'Not so simple! Add comments!..', 'You missed a description...' ]); }, undo => sub { my ($context, $user) = @_; my $task = $user->GetTask; if (defined($task) && $context->GetField('started')) { $user->RemoveTask; $user->FireEvent('task', 'cancel', $task); my $message = Orangutan::Context::Random('Ok.', 'Got it.', 'Okay.', 'Oops.'); $message .= ' Sorry... '; $message .= Orangutan::Context::Random('Removed.', 'Removing.', 'Canceled.', 'Canceling.'); $user->SendMessage($message); } }, help => { title => 'How can I start a task?', question => [ '^(?:I(?:\'d like| (?:want|would like)) to )?(?:start|create)(?: (?:a|another))?(?: new)? task!*\.*$', '^(?:How )?(?:(?:can|do) I |to )(?:start|add|create|put) '. '(?:(?:a|another|the) )?(?:new)?(?:current )?tasks?\?*!*\.*$', '^I have(?: (?:a|another))?(?: new)? task!*\.*$' ], answer => "Just tell Orangutan what are you working on! ". "The message (with some exceptions - see further) will be treated as a description of new task. ". "You optionally can specify the ID of the issue, the project name, the activity name and the start time. ". "If no issue, project and/or activity was specified the default ones will be used. ". "If no start time was specified the current one will be used.\n". "Format of the request:\n". " on # for () since