# Type: Input # Category: Issue # Description: Changes done ratio of an issue # Author: Andriy Lesyuk # FIXME: Sl@vko: not yet sub Orangutan::Query::GetDoneRatioChanges($$;$) { my ($self, $issueid, $calcspent) = @_; my $rows = undef; if ($self->Connect) { my $spenttime = ''; if ($calcspent) { $spenttime .= qq{ , }; $spenttime .= qq{ ( SELECT SUM(ROUND(hours * 3600)) FROM time_entries WHERE issue_id = $issueid AND ((end_time IS NOT NULL AND end_time < DATE_ADD(journals.created_on, INTERVAL 6 HOUR)) OR (end_time IS NULL AND spent_on < journals.created_on)) ) AS spent_time }; } if ($self->Query(qq{ SELECT value, UNIX_TIMESTAMP(created_on) AS created_on $spenttime FROM journals JOIN journal_details ON journal_id = journals.id AND property = 'attr' AND prop_key = 'done_ratio' WHERE journalized_type = 'Issue' AND journalized_id = $issueid ORDER BY created_on DESC })) { $rows = $self->Rows; } $self->Disconnect; } return $rows; } new Orangutan::Context( request => sub { my ($context, $user) = @_; my $message = undef; my $suggestions = $context->GetField('suggestions'); if ($suggestions && (scalar @{$suggestions} > 0)) { my $format; my $lasttime = $context->GetField('lasttime'); if (!defined($lasttime) || ((time - $lasttime) > 3600)) { $format = Orangutan::Context::Random( # FIXME: utf8 for subject 'It looks like it\'s time to change the done ratio of %s #%s "%s" to %d%% (current value is %d%%). Should I change it?', 'I believe it\'s time to change the done ratio of %s #%s "%s" to %d%% (it is %d%%). Can I change it?', 'I suggest changing the done ratio of %s #%s "%s" to %d%% (from %d%%). Do you accept?', 'The done ratio of %s #%s "%s" should be about %d%% (it is %d%%). Should I update it?', 'Should I change the done ratio of %s #%s "%s" to %d%% (from %d%%)?' ); } else { $format = Orangutan::Context::Random( 'I would also change the done ratio of %s #%s "%s" to %d%% (current value is %d%%)... Do you agree?', 'Now I suggest changing the done ratio of %s #%s "%s" to %d%% (it is %d%%). Can I do this?', 'The done ratio of %s #%s "%s" should be about %d%% but it is %d%%... Should I fix it?', 'What about %s #%s "%s"? Should I change the done ratio to %d%% (from %d%%)?' ); } $message = sprintf($format, $suggestions->[0][0]->{'tracker_name'}, $suggestions->[0][0]->{'id'}, $suggestions->[0][0]->{'subject'}, $suggestions->[0][1], $suggestions->[0][0]->{'done_ratio'}); $context->SetField('lasttime', time); $context->SetField('asked', 1); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->InsertForeignHandler($context); } my $number = $user->GetResponse('Number'); if ($number) { $number->InsertForeignHandler($context); } } return $message; }, response => sub { my ($context, $user, $message) = @_; my @result = ( ); my $regexp = 'issue'; my $trackers = $main::config->Get('redmine', '_trackers_regexp'); if ($trackers) { $regexp = "(?:issue|$trackers)"; } my @regexps = ( '([0-9]+)%? (?:(?:is|has been) )?(?:done|completed|finished) (?:on|for|in) (?:(?:the )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+)!*\.*', #0: Y% done on #X '(?:Change|set|update) (?:the )?(% )?done(?: ratio)? to ([0-9]+)%? (?:for|in) (?:(?:the )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+)!*\.*', #1: Set done to Y% for #X '(?:Change|set|update) (?:the )?(% )?done(?: ratio)? (?:of|for) (?:(?:the )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+) to ([0-9]+)%?!*\.*', #2: Set done of #X to Y% 'I(?: have)? (?:did|done|completed|finished) (?:(?:the )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+) at ([0-9]+)%?!*\.*', #3: I did #X at Y% '(?:(?:The )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+) (?:is|has been) (?:done|completed|finished) at ([0-9]+)%?!*\.*', #4: #X is done at Y% '(?:(?:The )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+) (?:is )?([0-9]+)%? (?:done|completed|finished)!*\.*', #5: #X is Y% done '(?:(?:The )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+) ?[:-]? ([0-9]+)%', #6: #X - Y% '(?:I )?(?:have )?(?:did|done|completed|finished) (?:(?:the )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+)!*\.*', #7: I completed #X '(?:(?:The )?'.$regexp.' )?(?:#|No\.? ?)?([0-9]+) (?:is|has been) (?:done|completed|finished)!*\.*' #8: #X is done ); for (my $i = 0; $i < scalar @regexps; $i++) { if ($message =~ /^$regexps[$i]$/i) { push(@result, 1); if ($i < 2) { push(@result, $1); push(@result, $2); } elsif ($i > 6) { push(@result, 100); push(@result, $1); } else { push(@result, $2); push(@result, $1); } last; } } if ((scalar @result == 0) && $context->GetField('asked')) { @regexps = ( '(?:(?:(?:This|the) )?issue|it)(?:\'s| (?:is|was|ha(?:s|ve) been)) (?:done|completed|finished) at ([0-9]+)%?!*\.*', '(?:Change|set)(?: (?:it|(?:the )?done ratio))? to ([0-9]+)%!*\.*', '(?:Use )?([0-9]+)%!*\.*' ); foreach my $regexp (@regexps) { if ($message =~ /^$regexp$/i) { push(@result, 2); push(@result, $1); last; } } } return @result; }, handler => sub { my ($context, $user, $item, $ratio, $issueid) = @_; my $suggestions = $context->GetField('suggestions'); my $issue; if ($item == 1) { $issue = $main::query->GetIssue($issueid); if ($issue) { if (!$main::query->Can('edit_issues', $user, $issue->{'project_id'})) { $user->SendMessage([ 'You need to be able to edit this issue to modify the done ratio!', 'You have no permissions to modify it!', 'You cannot modify this issue!..' ]); $context->UnsetField('asked'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } my $number = $user->GetResponse('Number'); if ($number) { $number->RemoveForeignHandler($context); } $context->SetTimeout; return 1; } } else { $user->SendMessage([ 'I guess you have made a mistake in issue ID...', 'Cannot find issue with such ID!', 'There is no such issue!' ]); return 1; } } else { if ($suggestions && (scalar @{$suggestions} > 0)) { $issue = $suggestions->[0][0]; } else { $user->SendMessage([ 'Oops... I forgot what I asked...', 'The issue disappeared... :S' ]); $context->UnsetField('asked'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } my $number = $user->GetResponse('Number'); if ($number) { $number->RemoveForeignHandler($context); } $context->SetTimeout; return 1; } } if ($item == -1) { if ($ratio eq 'Y') { if ($suggestions && (scalar @{$suggestions} > 0)) { $ratio = $suggestions->[0][1]; } else { $user->SendMessage([ "Hm... Can't remember what I asked... :(", "Oops! I don't remember the issue..." ]); $context->UnsetField('asked'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } my $number = $user->GetResponse('Number'); if ($number) { $number->RemoveForeignHandler($context); } $context->SetTimeout; return 1; } } else { if ($suggestions && (scalar @{$suggestions} > 0)) { my $rejects = $user->ReadData('DoneRatio'); if (!defined($rejects)) { $rejects = { }; } $rejects->{$suggestions->[0][0]->{'id'}} = time; $user->WriteData('DoneRatio', $rejects); shift(@{$suggestions}); if (scalar @{$suggestions} > 0) { my $doneratio = $user->GetResponse($context->GetID); if ($doneratio) { $user->AddRequest($doneratio); } } else { $context->UnsetField('suggestions'); } } $user->SendMessage([ "I see... Don't forget to update it!", "Got it. I know you'll do this!", 'Ok... Sorry.', 'I see...' ]); $context->UnsetField('asked'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } my $number = $user->GetResponse('Number'); if ($number) { $number->RemoveForeignHandler($context); } $context->SetTimeout; return 1; } } if ($ratio > 100) { my $format = Orangutan::Context::Random( "I'm glad you think you did it at %d%%... But I can't accept this!", 'The value must be less than or equal to 100%%!', 'This is incorrect value!' ); $user->SendMessage(sprintf($format, $ratio)); return 1; } elsif (($ratio % 10) > 0) { $user->SendMessage([ "Can't accept this value. It should be multiple of 10!", 'Use values like 10%, 20%, 30% ... 100%...', 'The value must be multiple of 10...' ]); return 1; } elsif ($ratio == $issue->{'done_ratio'}) { $user->SendMessage([ "That's the current value of the done ratio for this issue!", "Nothing to change... That's the current ratio!", 'Already set to this value!' ]); return 1; } my ($journal, @details) = $main::query->UpdateIssue($user, $issue->{'id'}, done_ratio => $ratio); if (defined($journal) && (scalar @details > 0)) { my $message; if ($ratio > $issue->{'done_ratio'}) { $message = Orangutan::Context::Random('Ok.', 'Got it!', 'Cool!'); $message .= ' '; $message .= Orangutan::Context::Random( 'I knew you would do this! ;)', "That's good!..", 'Congrats!' ); } else { $message = Orangutan::Context::Random('I see...', 'Changed.', 'Ok...'); $message .= ' '; $message .= Orangutan::Context::Random( "Hope you'll overtake it soon!", 'Wish you to make it up.', 'This is sad... :(' ); } my %watchers = ( ); $watchers{$issue->{'author_name'}}++; if ($issue->{'assigned_to_name'}) { $watchers{$issue->{'assigned_to_name'}}++; } if ($main::query->GetWatchers($issue->{'id'})) { while (defined(my $watcher = $main::query->Next)) { $watchers{$watcher->{'login'}}++; } } $issue->{'done_ratio'} = $ratio; foreach my $username (keys %watchers) { my $watcher = $main::users->GetUserLogin($username); if (defined($watcher)) { $watcher->FireEvent('issue', 'change', 'add', $issue, $journal, @details); } } $user->SendMessage($message); } else { $user->SendMessage([ "You know? I'm not sure if I changed it... :S", 'I guess I failed to change it...', 'Some troubles over here...' ]); } if ($suggestions && (scalar @{$suggestions} > 0)) { my $i; for ($i = 0; $i < scalar @{$suggestions}; $i++) { if ($issue->{'id'} == $suggestions->[$i][0]->{'id'}) { last; } } if ($i < scalar @{$suggestions}) { splice(@{$suggestions}, $i, 1); if (scalar @{$suggestions} > 0) { my $doneratio = $user->GetResponse($context->GetID); if ($doneratio) { $user->AddRequest($doneratio); } } else { $context->UnsetField('suggestions'); } } } $context->UnsetField('asked'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } my $number = $user->GetResponse('Number'); if ($number) { $number->RemoveForeignHandler($context); } $context->SetTimeout; return 1; }, ontimeout => sub { my ($context, $user) = @_; my $suggestions = $context->GetField('suggestions'); my $rejects = $user->ReadData('DoneRatio'); if (!defined($rejects)) { $rejects = { }; } $rejects->{$suggestions->[0][0]->{'id'}} = time; $user->WriteData('DoneRatio', $rejects); my $message = Orangutan::Context::Random('Well...', 'I see...', 'Ok...'); $message .= ' '; $message .= Orangutan::Context::Random( "Let's keep it as it is for a while...", "Let's leave it untouched...", 'Will get back to it later.', 'Forget about this issue.', 'Not this time...' ); $user->SendMessage($message); shift(@{$suggestions}); if (scalar @{$suggestions} > 0) { my $doneratio = $user->GetResponse($context->GetID); if ($doneratio) { $user->AddRequest($doneratio); } } else { $context->UnsetField('suggestions'); } $context->UnsetField('asked'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($context); } my $number = $user->GetResponse('Number'); if ($number) { $number->RemoveForeignHandler($context); } $context->SetTimeout; return 1; }, init => sub { my ($context) = @_; foreach my $username ($main::users->GetUsers) { my $user = $main::users->GetUser($username); my $doneratio = $user->GetResponse($context->GetID); if ($doneratio) { $user->LoadData('DoneRatio'); } } }, onstatus => sub { my ($context, $user, $type, $status) = @_; my $date = new Orangutan::Date; my $doneratio = $user->GetResponse($context->GetID); if ($doneratio && $user->IsWorkDay($date->WorkDayStart) && !$doneratio->GetField('computed')) { my $previous = $user->GetStatus; if (!defined($previous) || (defined($status) && ($previous eq 'away') && ($status eq 'online'))) { if ($main::query->GetChangedIssues($user->GetUserID, 'done_ratio')) { my @issues = ( ); while (defined(my $next = $main::query->Next)) { push(@issues, $next->{'issue_id'}); } my $changed = 0; my $rejects = $user->ReadData('DoneRatio'); my $suggestions = $doneratio->GetField('suggestions'); if (!defined($suggestions)) { $suggestions = [ ]; } foreach my $issueid (@issues) { if (!defined($rejects) || !defined($rejects->{$issueid})) { my $issue = $main::query->GetIssueEx($issueid); if ($issue && $main::query->Can('edit_issues', $user, $issue->{'project_id'})) { utf8::decode($issue->{'subject'}); if (!$issue->{'spent_time'} || (!$issue->{'estimated_hours'} && !$issue->{'done_ratio'})) { my $ratio = undef; if ($issue->{'start_date'} && $issue->{'due_date'}) { my $journal = undef; if ($main::query->GetDoneRatioChanges($issueid) > 0) { $journal = $main::query->Next; } my $last = new Orangutan::Date($journal->{'created_on'}); if ($journal && ($last->DayStart->Date > $issue->{'start_date'})) { my $today = new Orangutan::Date; if ($today->WorkDayStart->Date > $last->WorkDayStart->Date) { my $start = new Orangutan::Date($issue->{'start_date'}); my $ldays = scalar $user->GetWorkDays($start->WorkDayStart, $last->WorkDayStart); my $tdays = scalar $user->GetWorkDays($start->WorkDayStart, $today->WorkDayStart); if ($tdays > $ldays) { my $suggestion = int((($journal->{'value'} * $tdays) / $ldays) / 10) * 10; if ($suggestion > $issue->{'done_ratio'}) { $ratio = ($suggestion > 100) ? 100 : $suggestion; } } } } else { my $today = new Orangutan::Date; my $end = new Orangutan::Date($issue->{'due_date'}); if ($today->WorkDayStart->Date > $end->WorkDayStart->Date) { $ratio = 100; } else { my $start = new Orangutan::Date($issue->{'start_date'}); my $days = scalar $user->GetWorkDays($start->WorkDayStart, $end->WorkDayStart); my $tdays = scalar $user->GetWorkDays($start->WorkDayStart, $today->WorkDayStart); my $suggestion = int((($tdays * 100) / $days) / 10) * 10; $ratio = ($suggestion > 100) ? 100 : $suggestion; } } } elsif ($issue->{'start_date'}) { my $journal = undef; if ($main::query->GetDoneRatioChanges($issueid) > 0) { $journal = $main::query->Next; } my $last = new Orangutan::Date($journal->{'created_on'}); if ($journal && ($last->DayStart->Date > $issue->{'start_date'})) { my $today = new Orangutan::Date; if ($today->WorkDayStart->Date > $last->WorkDayStart->Date) { my $start = new Orangutan::Date($issue->{'start_date'}); my $ldays = scalar $user->GetWorkDays($start->WorkDayStart, $last->WorkDayStart); my $tdays = scalar $user->GetWorkDays($start->WorkDayStart, $today->WorkDayStart); if ($tdays > $ldays) { my $suggestion = int((($journal->{'value'} * $tdays) / $ldays) / 10) * 10; if ($suggestion > $issue->{'done_ratio'}) { $ratio = ($suggestion > 100) ? 100 : $suggestion; } } } } } else { my $count = $main::query->GetDoneRatioChanges($issueid); if ($count > 1) { my $last = $main::query->First; my $first = $main::query->Last; my $ldate = new Orangutan::Date($last->{'created_on'}); my $fdate = new Orangutan::Date($first->{'created_on'}); if (($ldate->DayStart->Date > $fdate->DayStart->Date) && ($last->{'value'} > $first->{'value'})) { my $today = new Orangutan::Date; if ($today->WorkDayStart->Date > $ldate->WorkDayStart->Date) { my $ldays = scalar $user->GetWorkDays($fdate->WorkDayStart, $ldate->WorkDayStart); my $tdays = scalar $user->GetWorkDays($fdate->WorkDayStart, $today->WorkDayStart); if ($tdays > $ldays) { my $suggestion = int(((($last->{'value'} - $first->{'value'}) * $tdays) / $ldays) / 10) * 10 + $first->{'value'}; if ($suggestion > $issue->{'done_ratio'}) { $ratio = ($suggestion > 100) ? 100 : $suggestion; } } } } } } if (defined($ratio) && !grep(($issue->{'id'} <=> $_[0]->{'id'}), @{$suggestions})) { push(@{$suggestions}, [ $issue, $ratio ]); $changed++; } } } } } if ($changed) { $doneratio->SetField('suggestions', $suggestions); if (!$user->GetRequest($doneratio->GetID)) { $user->AddRequest($doneratio); } } $doneratio->SetField('computed', 1); } } } }, ontask => sub { my ($context, $user, $type, $status, $task) = @_; if (($status eq 'end') || ($status eq 'add') || ($status eq 'change') || ($status eq 'break')) { my $doneratio = $user->GetResponse($context->GetID); if ($doneratio && $task->GetIssue) { my $rejects = $user->ReadData('DoneRatio'); if (!defined($rejects) || !defined($rejects->{$task->GetIssue})) { my $issue = $main::query->GetIssueEx($task->GetIssue); if ($issue && ($issue->{'done_ratio'} < 100) && $main::query->Can('edit_issues', $user, $issue->{'project_id'})) { my $journal = undef; if ($main::query->GetDoneRatioChanges($task->GetIssue, 1) > 0) { $journal = $main::query->Next; my $created = new Orangutan::Date($journal->{'created_on'}); if ($task->GetDate->Date < $created->DayStart->Date) { return 1; } } my $suggestions = $doneratio->GetField('suggestions'); if (!defined($suggestions)) { $suggestions = [ ]; } my $ratio = undef; if ($issue->{'estimated_hours'}) { if ($journal && $journal->{'value'} && $journal->{'spent_time'}) { my $factor = ($journal->{'spent_time'} * 100) / $journal->{'value'} / $issue->{'estimated_hours'}; my $suggestion = int((($issue->{'spent_time'} * 100) / ($issue->{'estimated_hours'} * $factor)) / 10) * 10; if ($suggestion > $issue->{'done_ratio'}) { $ratio = ($suggestion > 100) ? 100 : $suggestion; } } else { my $suggestion = int((($issue->{'spent_time'} * 100) / $issue->{'estimated_hours'}) / 10) * 10; if ($suggestion > $issue->{'done_ratio'}) { $ratio = ($suggestion > 100) ? 100 : $suggestion; } } } else { if ($journal && $journal->{'value'} && $journal->{'spent_time'}) { my $suggestion = int((($issue->{'spent_time'} * $journal->{'value'}) / $journal->{'spent_time'}) / 10) * 10; if ($suggestion > $issue->{'done_ratio'}) { $ratio = ($suggestion > 100) ? 100 : $suggestion; } } } if (defined($ratio) && !grep(($issue->{'id'} <=> $_[0]->{'id'}), @{$suggestions})) { push(@{$suggestions}, [ $issue, $ratio ]); $doneratio->SetField('suggestions', $suggestions); if (!$user->GetRequest($doneratio->GetID)) { $user->AddRequest($doneratio); } } } } } } }, onissue => sub { my ($context, $user, $type, $status, $operation, $issue, $journal, @details) = @_; if (($status eq 'change') && ($operation ne 'add')) { my $clean = 0; foreach my $detail (@details) { if ($detail->{'property'} eq 'attr') { if ($detail->{'prop_key'} eq 'done_ratio') { $clean = 1; last; } elsif ($detail->{'prop_key'} eq 'status_id') { if ($issue->{'is_closed'}) { $clean = 1; last; } } } } if ($clean) { my $rejects = $user->ReadData('DoneRatio'); if (defined($rejects) && defined($rejects->{$issue->{'id'}})) { delete($rejects->{$issue->{'id'}}); $user->WriteData('DoneRatio', $rejects); } my $doneratio = $user->GetResponse($context->GetID); if ($doneratio) { my $suggestions = $doneratio->GetField('suggestions'); if (defined($suggestions)) { my $i; for ($i = 0; $i < scalar @{$suggestions}; $i++) { if ($issue->{'id'} == $suggestions->[$i][0]->{'id'}) { last; } } if ($i < scalar @{$suggestions}) { splice(@{$suggestions}, $i, 1); if (scalar @{$suggestions} == 0) { $doneratio->UnsetField('suggestions'); } if (($i == 0) && $doneratio->GetField('asked')) { my $message; if ($journal->{'user_id'} == $user->GetUserID) { $message = Orangutan::Context::Random('I see...', 'Oh...', 'Ok...'); $message .= ' '; $message .= Orangutan::Context::Random( 'You could tell me the done ratio...', 'I could help you setting it...', "That's good.", 'Never mind.' ); } else { $message = Orangutan::Context::Random('Wait.', 'Oh...', 'Hm...'); $message .= ' '; $message .= Orangutan::Context::Random( 'Another user specified the done ration.', 'Another user just set it.', 'Too late! :)' ); } $user->SendMessage($message); if (scalar @{$suggestions} > 0) { $user->AddRequest($doneratio); } $doneratio->UnsetField('asked'); my $yesno = $user->GetResponse('YesNo'); if ($yesno) { $yesno->RemoveForeignHandler($doneratio); } my $number = $user->GetResponse('Number'); if ($number) { $number->RemoveForeignHandler($doneratio); } $doneratio->SetTimeout; } } } } } } }, schedule => sub { my ($context, $scheduler, $arg) = @_; if ($arg eq 'clean') { foreach my $username ($main::users->GetUsers) { my $user = $main::users->GetUser($username); my $ucontext = $user->GetResponse($context->GetID); if (defined($ucontext)) { $ucontext->UnsetField('suggestions'); $ucontext->UnsetField('computed'); } my $rejects = $user->ReadData('DoneRatio'); if (defined($rejects)) { my $update = 0; foreach my $issueid (keys %{$rejects}) { if ((time - $rejects->{$issueid}) > 2592000) { delete($rejects->{$issueid}); $update++; } } if ($update) { $user->WriteData('DoneRatio', $rejects); } if (scalar keys %{$rejects} > 1000) { $main::logger->Log('too many rejects for user %s', $user->GetJabberUserID); } } } } my ($hour, $day, $month, $year) = (localtime(time))[2..5]; if ($hour >= 6) { ($day, $month, $year) = (localtime(time + 86400))[3..5]; } my $time = POSIX::mktime(0, 0, 6, $day, $month, $year); $scheduler->Schedule($time, $context, 'clean'); }, help => { title => 'How can I change done ratio of an issue?', question => '^(?:How )?(?:(?:can|do) I |to )(?:change|set|modify) (?:(?:(?:the|a) )?value of )?'. '(?:(?:the|a) )?(?:done|completeness) ratio(?: (?:of|for) (?:(?:the|an?) )?issues?)?\?*!*\.*$', answer => "Issue completeness in Redmine is represented with the done ratio. ". "You can change this ratio using Orangutan.\n". "Check out samples:\n". " o Change done ratio of #171 to 20%\n". " o 50% has been completed for #171\n". " o I have completed #171 at 30%\n". " o Change done to 20% for #171\n". " o #171 is done at 80%\n". " o 10% done on #171\n". " o #512 60% done\n". " o #512: 70%\n". "The value of done ratio must be in range from 0% to 100% and be multiple of 10...\n". "The following queries are also supported and set done ratio to 100%:\n". " o #171 is finished\n". " o I completed #171", weight => 1050 }, timeout => 1800, weight => [ undef, 0, undef ] ); # kate: syntax perl