コード例 #1
0
ファイル: Edit.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $slot_id = $input->getArgument('slot_id');
     $duration = $input->getArgument('duration');
     $this->repository->edit($slot_id, $duration);
     $output->writeln(sprintf('Updated slot %s to <info>%s h</info>', $slot_id, $duration));
 }
コード例 #2
0
ファイル: Status.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     // Allow special date param in the form of _1 for back one day, _2 for two
     // days etc.
     $date = $input->getArgument('date');
     if (preg_match('/_(\\d+)/', $date, $matches)) {
         $date = date('Y-m-d', time() - 86400 * $matches[1]);
     }
     $data = $this->repository->status($date);
     $table = new Table($output);
     $table->setHeaders(['Slot', 'JobId', 'Time', 'Title']);
     $rows = [];
     $total = 0;
     foreach ($data as $record) {
         $total += $record->duration;
         $details = $this->connector->ticketDetails($record->tid);
         if (!empty($record->active)) {
             $record->tid .= ' *';
         }
         $rows[] = [$record->id, $record->tid, Formatter::formatDuration($record->duration), $details->getTitle()];
     }
     $rows[] = new TableSeparator();
     $rows[] = ['', '<comment>Total</comment>', '<info>' . Formatter::formatDuration($total) . '</info>', ''];
     $table->setRows($rows);
     $table->render();
 }
コード例 #3
0
ファイル: Open.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if ($data = $this->repository->getActive()) {
         $details = $this->connector->ticketDetails($data->tid);
         $output->writeLn(sprintf('%s [<info>%d</info>] - <comment>%s</comment> [slot: <comment>%d</comment>]', $details->getTitle(), $data->tid, Formatter::formatDuration(time() - $data->start), $data->id));
         return;
     }
     $output->writeln('<error>No active slot</error>');
 }
コード例 #4
0
ファイル: MostFrequentlyUsed.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if ($entries = $this->repository->frequent()) {
         $table = new Table($output);
         $table->setHeaders(['JobId', 'Title']);
         $rows = [];
         foreach ($entries as $entry) {
             $details = $this->connector->ticketDetails($entry->tid);
             $rows[] = [$entry->tid, $details->getTitle()];
         }
         $table->setRows($rows);
         $table->render();
     }
 }
コード例 #5
0
ファイル: Visit.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if (!($issue_number = $input->getArgument('issue'))) {
         if ($data = $this->repository->getActive()) {
             $issue_number = $data->tid;
         }
     }
     if (!$issue_number) {
         $output->writeln('<error>No active ticket, please use tl visit {ticket_id} to specifiy a ticket.</error>');
         return;
     }
     $url = $this->connector->ticketUrl($issue_number);
     $this->open($url, $output);
 }
コード例 #6
0
ファイル: Continues.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if ($slot_id = $input->getArgument('slot_id')) {
         $slot = $this->repository->slot($slot_id);
     } else {
         $slot = $this->repository->latest();
     }
     if ($slot) {
         $details = $this->connector->ticketDetails($slot->tid);
         list($slot_id, $continued) = $this->repository->start($slot->tid, $slot->comment, $slot->id);
         $output->writeln(sprintf('<bg=blue;fg=white;options=bold>[%s]</> <comment>%s</comment> entry for <info>%d</info>: %s [slot:<comment>%d</comment>]', (new \DateTime())->format('h:i'), $continued ? 'Continued' : 'Started new', $slot->tid, $details->getTitle(), $slot_id));
         return;
     }
     $output->writeln('<error>Could not find the slot to continue</error>');
 }
コード例 #7
0
ファイル: Delete.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $slot_id = $input->getArgument('slot_id');
     $helper = $this->getHelper('question');
     $question = new ConfirmationQuestion('Are you sure?', false);
     $confirm = NULL;
     if (($slot = $this->repository->slot($slot_id)) && ($confirm = $helper->ask($input, $output, $question)) && $this->repository->delete($slot_id)) {
         $deleted = $this->connector->ticketDetails($slot->tid);
         $output->writeln(sprintf('Deleted slot <comment>%d</comment> against ticket <info>%d</info>: %s, duration <info>%s</info>', $slot->id, $slot->tid, $deleted->getTitle(), Formatter::formatDuration($slot->end - $slot->start)));
         return;
     }
     if ($confirm !== FALSE) {
         $output->writeln('<error>Cannot delete slot, either does not exist or has been sent to back end.</error>');
     }
 }
コード例 #8
0
ファイル: Stop.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if ($stop = $this->repository->stop()) {
         $stopped = $this->connector->ticketDetails($stop->tid);
         $output->writeln(sprintf('<bg=blue;fg=white;options=bold>[%s]</> Closed slot <comment>%d</comment> against ticket <info>%d</info>: %s, duration <info>%s</info>', (new \DateTime())->format('h:i'), $stop->id, $stop->tid, $stopped->getTitle(), Formatter::formatDuration($stop->duration)));
         if (($comment = $input->getOption('comment')) || $input->getOption('pause')) {
             if ($this->connector->pause($stop->tid, $comment)) {
                 $output->writeln(sprintf('Ticket <comment>%s</comment> set to paused.', $stop->tid));
             } else {
                 $output->writeln('<error>Could not update ticket status</error>');
             }
         }
         return;
     }
     $output->writeln('<error>No active slot</error>');
 }
コード例 #9
0
ファイル: Combine.php プロジェクト: nickschuch/tl
 protected function stopTicket($slot_id, OutputInterface $output)
 {
     if ($stop = $this->repository->stop($slot_id)) {
         $stopped = $this->connector->ticketDetails($stop->tid);
         $output->writeln(sprintf('Closed slot <comment>%d</comment> against ticket <info>%d</info>: %s, duration <info>%s</info>', $stop->id, $stop->tid, $stopped->getTitle(), Formatter::formatDuration($stop->duration)));
     }
 }
コード例 #10
0
ファイル: Reviewer.php プロジェクト: nickschuch/tl
 public function getSummary($date = 19780101, $check = FALSE, $exact = FALSE)
 {
     $data = $this->repository->review($date, $check);
     if (count($data) == 0 && !$check) {
         throw new \Exception('All entries stored in remote system');
     }
     $total = 0;
     $offline = FALSE;
     try {
         $categories = $this->connector->fetchCategories();
     } catch (ConnectException $e) {
         $offline = TRUE;
     }
     $exact_total = 0;
     foreach ($data as $record) {
         $total += $record->duration;
         $details = $this->connector->ticketDetails($record->tid);
         $category_id = str_pad($record->category, 3, 0, STR_PAD_LEFT);
         $category = '';
         if ($record->category) {
             if ($offline) {
                 $category = 'Offline';
             } elseif (isset($categories[$category_id])) {
                 $category = $categories[$category_id];
             } else {
                 $category = 'Unknown';
             }
         }
         $row = [$record->id, $record->tid, $record->duration];
         if ($exact) {
             $row[] = Formatter::formatDuration($record->end - $record->start);
             $exact_total += $record->end - $record->start;
         }
         $row = array_merge($row, [substr($details->getTitle(), 0, 25) . '...', $category, $record->comment]);
         $rows[] = $row;
     }
     $rows[] = new TableSeparator();
     if ($exact) {
         $rows[] = ['', '<comment>Total</comment>', '<info>' . $total . ' h</info>', '<info>' . Formatter::formatDuration($exact_total) . '</info>', '', '', ''];
     } else {
         $rows[] = ['', '<comment>Total</comment>', '<info>' . $total . ' h</info>', '', '', ''];
     }
     return $rows;
 }
コード例 #11
0
ファイル: Comment.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $entries = $this->repository->review(Review::ALL, TRUE);
     $helper = $this->getHelper('question');
     $last = FALSE;
     foreach ($entries as $entry) {
         if ($entry->comment && !$input->getOption('recomment')) {
             continue;
         }
         $title = $this->connector->ticketDetails($entry->tid);
         $question = new Question(sprintf('Enter comment for slot <comment>%d</comment> [<info>%d</info>]: %s [<info>%s h</info>] [%s]', $entry->id, $entry->tid, $title->getTitle(), $entry->duration, $last ?: static::DEFAULT_COMMENT), $last ?: static::DEFAULT_COMMENT);
         $comment = $helper->ask($input, $output, $question);
         $this->repository->comment($entry->id, $comment);
         $last = $comment;
     }
     if (!$last) {
         $output->writeln('<error>All items already commented, use --recomment to recomment</error>');
     }
 }
コード例 #12
0
ファイル: TagAll.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $entries = $this->repository->review(Review::ALL, TRUE);
     $helper = $this->getHelper('question');
     $last = FALSE;
     $categories = $this->connector->fetchCategories();
     $question = new ChoiceQuestion(sprintf('Select tag to use:[%s]', $last ?: Tag::DEFAULT_TAG), $categories, $last ?: Tag::DEFAULT_TAG);
     $tag_id = $helper->ask($input, $output, $question);
     $tag = $categories[$tag_id];
     list(, $tag) = explode(':', $tag);
     foreach ($entries as $entry) {
         if ($entry->category) {
             continue;
         }
         $this->repository->tag($tag, $entry->id);
         $last = $tag;
     }
     if (!$last) {
         $output->writeln('<error>All items already tagged, use --retag to retag</error>');
     }
 }
コード例 #13
0
ファイル: Start.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 public function completeArgumentValues($argumentName, CompletionContext $context)
 {
     $aliases = [];
     if ($argumentName === 'issue_number') {
         // Get all the aliases that are similar to our current search.
         $results = $this->repository->listAliases($context->getWordAtIndex(2));
         foreach ($results as $alias) {
             $aliases[] = $alias->alias;
         }
     }
     return $aliases;
 }
コード例 #14
0
ファイル: Alias.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     if ($input->getOption('list')) {
         $table = new Table($output);
         $table->setHeaders(['Alias', 'Issue number']);
         $list = $this->repository->listAliases();
         $rows = [];
         foreach ($list as $alias) {
             $rows[] = [$alias->alias, $alias->tid];
         }
         $table->setRows($rows);
         $table->render();
         return;
     }
     $alias = $input->getArgument('alias');
     $tid = $input->getArgument('ticket_id');
     if ($input->getOption('delete')) {
         if ($this->repository->removeAlias($tid, $alias)) {
             $output->writeln('Removed alias');
         } else {
             $output->writeln('<error>Unable to delete alias</error>');
         }
     } else {
         if (!$alias) {
             $output->writeln('<error>Missing alias</error>');
             return;
         }
         if (!$tid) {
             $output->writeln('<error>Missing ticket number</error>');
             return;
         }
         if ($this->repository->addAlias($tid, $alias)) {
             $output->writeln('Created new alias');
         } else {
             $output->writeln('<error>Unable to create alias</error>');
         }
     }
 }
コード例 #15
0
ファイル: Send.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     // Find any untagged items needing review, use an arbitrarily early date.
     $review = $this->reviewer->getSummary(static::ALL, TRUE);
     if (count($review) > 2) {
         // Incomplete records exist.
         $output->writeln('<error>Please tag/comment on the following entries:</error>');
         $table = new Table($output);
         $table->setHeaders(Reviewer::headers());
         $table->setRows($review);
         $table->render();
         return;
     }
     $entry_ids = $return = [];
     foreach ($this->repository->send() as $entry) {
         $entry_ids[$entry->tid] = $this->connector->sendEntry($entry);
         if ($entry_ids[$entry->tid]) {
             // A real entry, give some output.
             $output->writeln(sprintf('Stored entry for <info>%d</info>, remote id <comment>%d</comment>', $entry->tid, $entry_ids[$entry->tid]));
         }
     }
     $this->repository->store($entry_ids);
 }
コード例 #16
0
ファイル: Tag.php プロジェクト: nickschuch/tl
 /**
  * Tag single entry.
  *
  * @param \Symfony\Component\Console\Input\InputInterface $input
  * @param \Symfony\Component\Console\Output\OutputInterface $output
  * @param $slot_id
  */
 protected function tagOne(InputInterface $input, OutputInterface $output, $slot_id)
 {
     if ($entry = $this->repository->slot($slot_id)) {
         $helper = $this->getHelper('question');
         try {
             $title = $this->connector->ticketDetails($entry->tid);
             $categories = $this->connector->fetchCategories();
         } catch (ConnectException $e) {
             $output->writeln('<error>You are offline, please try again later.</error>');
             return;
         }
         $question = new ChoiceQuestion(sprintf('Enter tag for slot <comment>%d</comment> [<info>%d</info>]: %s [<info>%s h</info>] [%s]', $entry->id, $entry->tid, $title->getTitle(), Formatter::formatDuration($entry->end - $entry->start), static::DEFAULT_TAG), $categories, static::DEFAULT_TAG);
         $tag_id = $helper->ask($input, $output, $question);
         $tag = $categories[$tag_id];
         list(, $tag) = explode(':', $tag);
         $this->repository->tag($tag, $entry->id);
     } else {
         $output->writeln('<error>No such slot - please check your slot ID</error>');
     }
 }
コード例 #17
0
ファイル: Billable.php プロジェクト: nickschuch/tl
 /**
  * {@inheritdoc}
  */
 protected function execute(InputInterface $input, OutputInterface $output)
 {
     $period = $input->getArgument('period');
     $start = $input->getOption('start');
     $project = $input->getOption('project');
     $start = $start ? new \DateTime($start) : NULL;
     if (!in_array($period, [static::MONTH, static::DAY, static::WEEK, static::FORTNIGHT], TRUE)) {
         $output->writeln('<error>Period must be one of day|week|month|fortnight</error>');
         $output->writeln('E.g. <comment>tl billable week</comment>');
         return;
     }
     switch ($period) {
         case static::WEEK:
             $date = DateHelper::startOfWeek($start);
             $end = clone $date;
             $end->modify('+7 days');
             break;
         case static::MONTH:
             $date = DateHelper::startOfMonth($start);
             $end = clone $date;
             $end->modify('+1 month');
             $end = DateHelper::startOfMonth($end);
             $end->modify('-1 second');
             break;
         case static::FORTNIGHT:
             $date = DateHelper::startOfWeek($start);
             $date->modify('-7 days');
             $end = clone $date;
             $end->modify('+14 days');
             break;
         default:
             $date = DateHelper::startOfDay($start);
             $end = clone $date;
             $end->modify('+1 day');
     }
     $billable = 0;
     $non_billable = 0;
     $unknown = 0;
     $unknowns = [];
     $projects = [];
     $billable_projects = [];
     $non_billable_projects = [];
     foreach ($this->repository->totalByTicket($date->getTimestamp(), $end->getTimestamp()) as $tid => $duration) {
         $details = $this->connector->ticketDetails($tid);
         if ($details) {
             if (!isset($projects[$details->getProjectId()])) {
                 $projects[$details->getProjectId()] = 0;
             }
             if ($details->isBillable()) {
                 $billable += $duration;
                 $projects[$details->getProjectId()] += $duration;
                 $billable_projects[$details->getProjectId()] = $details->getProjectId();
             } else {
                 $non_billable += $duration;
                 $projects[$details->getProjectId()] += $duration;
                 $non_billable_projects[$details->getProjectId()] = $details->getProjectId();
             }
         } else {
             $unknown += $duration;
             $unknowns[] = $tid;
         }
     }
     $table = new Table($output);
     if (!$project) {
         $table->setHeaders(['Type', 'Hours', 'Percent']);
     } else {
         $table->setHeaders(['Type', 'Project', 'Hours', 'Percent']);
     }
     $total = $billable + $non_billable + $unknown;
     $tag = 'info';
     // @todo make this configurable.
     if ($billable / $total < 0.8) {
         $tag = 'error';
     }
     if ($project) {
         $project_names = $this->connector->projectNames();
         $rows[] = ['Billable', '', '', ''];
         foreach ($billable_projects as $project_id) {
             $project_name = isset($project_names[$project_id]) ? $project_names[$project_id] : "Project ID {$project_id}";
             $rows[] = ['', $project_name, Formatter::formatDuration($projects[$project_id]), ''];
         }
         $rows[] = new TableSeparator();
         $rows[] = ['Billable', '', Formatter::formatDuration($billable), "<{$tag}>" . round(100 * $billable / $total, 2) . "%</{$tag}>"];
         $rows[] = new TableSeparator();
         $rows[] = ['Non-Billable', '', '', ''];
         foreach ($non_billable_projects as $project_id) {
             $project_name = isset($project_names[$project_id]) ? $project_names[$project_id] : "Project ID {$project_id}";
             $rows[] = ['', $project_name, Formatter::formatDuration($projects[$project_id]), ''];
         }
         $rows[] = new TableSeparator();
         $rows[] = ['Non-billable', '', Formatter::formatDuration($non_billable), round(100 * $non_billable / $total, 2) . '%'];
         if ($unknown) {
             $rows[] = new TableSeparator();
             $rows[] = ['Unknown', '', '', ''];
             $rows[] = ['', 'Unknown<comment>*</comment>', Formatter::formatDuration($unknown), round(100 * $unknown / $total, 2) . '%'];
             $rows[] = ['', '<comment>* Deleted or access denied tickets:</comment> ' . implode(',', $unknowns), '', ''];
         }
         $rows[] = new TableSeparator();
         $rows[] = ['', 'Total', Formatter::formatDuration($total), '100%'];
     } else {
         $rows[] = ['Billable', Formatter::formatDuration($billable), "<{$tag}>" . round(100 * $billable / $total, 2) . "%</{$tag}>"];
         $rows[] = ['Non-billable', Formatter::formatDuration($non_billable), round(100 * $non_billable / $total, 2) . '%'];
         if ($unknown) {
             $rows[] = ['Unknown<comment>*</comment>', Formatter::formatDuration($unknown), round(100 * $unknown / $total, 2) . '%'];
             $rows[] = ['<comment>* Deleted or access denied tickets:</comment> ' . implode(',', $unknowns), '', ''];
         }
         $rows[] = new TableSeparator();
         $rows[] = ['Total', Formatter::formatDuration($total), '100%'];
     }
     $table->setRows($rows);
     $table->render();
 }