/** * Method to insert data for an issue from GitHub * * @return void * * @since 1.0 */ protected function insertIssue() { // Try to render the description with GitHub markdown $parsedText = $this->parseText($this->hookData->issue->body); // Prepare the dates for insertion to the database $dateFormat = $this->db->getDateFormat(); $opened = new Date($this->hookData->issue->created_at); $modified = new Date($this->hookData->issue->updated_at); $data = array(); $data['issue_number'] = $this->hookData->issue->number; $data['title'] = $this->hookData->issue->title; $data['description'] = $parsedText; $data['description_raw'] = $this->hookData->issue->body; $data['status'] = $this->hookData->issue->state == 'open' ? 1 : 10; $data['opened_date'] = $opened->format($dateFormat); $data['opened_by'] = $this->hookData->issue->user->login; $data['modified_date'] = $modified->format($dateFormat); $data['project_id'] = $this->project->project_id; $data['build'] = $this->hookData->repository->default_branch; // Add the closed date if the status is closed if ($this->hookData->issue->closed_at) { $closed = new Date($this->hookData->issue->closed_at); $data['closed_date'] = $closed->format($dateFormat); $data['closed_by'] = $this->hookData->sender->login; } // If the title has a [# in it, assume it's a JoomlaCode Tracker ID if (preg_match('/\\[#([0-9]+)\\]/', $this->hookData->issue->title, $matches)) { $data['foreign_number'] = $matches[1]; } elseif (preg_match('/tracker_item_id=([0-9]+)/', $this->hookData->issue->body, $matches)) { $data['foreign_number'] = $matches[1]; } // Process labels for the item $data['labels'] = $this->processLabels($this->hookData->issue->number); try { $table = new IssuesTable($this->db); $table->save($data); } catch (\Exception $e) { $this->logger->error(sprintf('Error adding GitHub issue %s/%s #%d to the tracker: %s', $this->project->gh_user, $this->project->gh_project, $this->hookData->issue->number, $e->getMessage())); $this->getContainer()->get('app')->close(); } $this->triggerEvent('onCommentAfterCreateIssue', $table); // Pull the user's avatar if it does not exist $this->pullUserAvatar($this->hookData->issue->user->login); // Add a close record to the activity table if the status is closed if ($this->hookData->issue->closed_at) { $this->addActivityEvent('close', $data['closed_date'], $this->hookData->sender->login, $this->project->project_id, $this->hookData->issue->number); } // Store was successful, update status $this->logger->info(sprintf('Added GitHub issue %s/%s #%d to the tracker.', $this->project->gh_user, $this->project->gh_project, $this->hookData->issue->number)); }
/** * Method to process the list of issues and inject into the database as needed * * @return $this * * @since 1.0 * @throws \RuntimeException */ protected function processData() { $ghIssues = $this->issues; $dbIssues = $this->getDbIssues(); if (!$ghIssues) { throw new \UnexpectedValueException('No issues received...'); } $added = 0; $updated = 0; $milestones = $this->getMilestones(); $this->out(g11n3t('Adding issues to the database...'), false); $progressBar = $this->getProgressBar(count($ghIssues)); $this->usePBar ? $this->out() : null; // Start processing the pulls now foreach ($ghIssues as $count => $ghIssue) { $this->usePBar ? $progressBar->update($count + 1) : $this->out($ghIssue->number . '...', false); if (!$this->checkInRange($ghIssue->number)) { // Not in range $this->usePBar ? null : $this->out('NiR ', false); continue; } $id = 0; foreach ($dbIssues as $dbIssue) { if ($ghIssue->number == $dbIssue->issue_number) { if ($this->force) { // Force update $this->usePBar ? null : $this->out('F ', false); $id = $dbIssue->id; break; } $d1 = new Date($ghIssue->updated_at); $d2 = new Date($dbIssue->modified_date); if ($d1 == $d2) { // No update required $this->usePBar ? null : $this->out('- ', false); continue 2; } $id = $dbIssue->id; break; } } // Store the item in the database $table = new IssuesTable($this->getContainer()->get('db')); if ($id) { $table->load($id); } $table->issue_number = $ghIssue->number; $table->title = $ghIssue->title; if ($table->description_raw != $ghIssue->body) { $table->description = $this->github->markdown->render($ghIssue->body, 'gfm', $this->project->gh_user . '/' . $this->project->gh_project); $this->checkGitHubRateLimit($this->github->markdown->getRateLimitRemaining()); $table->description_raw = $ghIssue->body; } $statusTable = new StatusTable($this->getContainer()->get('db')); // Get the list of status IDs based on the GitHub issue state $state = $ghIssue->state == 'open' ? false : true; $stateIds = $statusTable->getStateStatusIds($state); // Check if the issue status is in the array; if it is, then the item didn't change open state and we don't need to change the status if (!in_array($table->status, $stateIds)) { $table->status = $state ? 10 : 1; } $table->opened_date = (new Date($ghIssue->created_at))->format('Y-m-d H:i:s'); $table->opened_by = $ghIssue->user->login; $table->modified_date = (new Date($ghIssue->updated_at))->format('Y-m-d H:i:s'); $table->modified_by = $ghIssue->user->login; $table->project_id = $this->project->project_id; $table->milestone_id = $ghIssue->milestone && isset($milestones[$ghIssue->milestone->number]) ? $milestones[$ghIssue->milestone->number] : null; // If the issue has a diff URL, it is a pull request. if (isset($ghIssue->pull_request->diff_url)) { $table->has_code = 1; // Get the pull request corresponding to an issue. $this->debugOut('Get PR for the issue'); $pullRequest = $this->github->pulls->get($this->project->gh_user, $this->project->gh_project, $ghIssue->number); $table->pr_head_ref = $pullRequest->head->ref; $status = $this->GetMergeStatus($pullRequest); if (!$status->state) { // No status found. Let's create one! $status->state = 'pending'; $status->targetUrl = 'http://issues.joomla.org/gagaga'; $status->description = 'JTracker Bug Squad working on it...'; $status->context = 'jtracker'; // @todo Project based status messages // @$this->createStatus($ghIssue, 'pending', 'http://issues.joomla.org/gagaga', 'JTracker Bug Squad working on it...', 'CI/JTracker'); } else { // Save the merge status to database $table->merge_state = $status->state; $table->gh_merge_status = json_encode($status); } // Get commits $table->commits = json_encode($this->getCommits($pullRequest)); } // Add the closed date if the status is closed if ($ghIssue->closed_at) { $table->closed_date = (new Date($ghIssue->closed_at))->format('Y-m-d H:i:s'); } // If the title has a [# in it, assume it's a JoomlaCode Tracker ID if (preg_match('/\\[#([0-9]+)\\]/', $ghIssue->title, $matches)) { $table->foreign_number = $matches[1]; } elseif (preg_match('/tracker_item_id=([0-9]+)/', $ghIssue->body, $matches)) { $table->foreign_number = $matches[1]; } $table->labels = implode(',', $this->getLabelIds($ghIssue->labels)); $table->store(true); if (!$table->id) { // Bad coder :( - @todo when does this happen ?? throw new \RuntimeException(sprintf('Invalid issue id for issue: %1$d in project id %2$s', $ghIssue->number, $this->project->project_id)); } /* @todo see issue #194 Add an open record to the activity table $activity = new ActivitiesTable($db); $activity->project_id = $this->project->project_id; $activity->issue_number = (int) $table->issue_number; $activity->user = $issue->user->login; $activity->event = 'open'; $activity->created_date = $table->opened_date; $activity->store(); / Add a close record to the activity table if the status is closed if ($issue->closed_at) { $activity = new ActivitiesTable($db); $activity->project_id = $this->project->project_id; $activity->issue_number = (int) $table->issue_number; $activity->event = 'close'; $activity->created_date = $issue->closed_at; $activity->store(); } */ // Store was successful, update status if ($id) { ++$updated; } else { ++$added; } $this->changedIssueNumbers[] = $ghIssue->number; } // Output the final result $this->out()->logOut(sprintf(g11n3t('<ok>%1$d added, %2$d updated.</ok>'), $added, $updated)); return $this; }
/** * Save the item. * * @param array $src The source. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function save(array $src) { $filter = new InputFilter(); $data = array(); $data['id'] = $filter->clean($src['id'], 'int'); $data['status'] = $filter->clean($src['status'], 'int'); $data['priority'] = $filter->clean($src['priority'], 'int'); $data['title'] = $filter->clean($src['title'], 'string'); $data['build'] = $filter->clean($src['build'], 'string'); $data['description'] = $filter->clean($src['description'], 'raw'); $data['description_raw'] = $filter->clean($src['description_raw'], 'raw'); $data['rel_number'] = $filter->clean($src['rel_number'], 'int'); $data['rel_type'] = $filter->clean($src['rel_type'], 'int'); $data['commits'] = isset($src['commits']) ? $src['commits'] : ''; $data['pr_head_sha'] = isset($src['pr_head_sha']) ? $src['pr_head_sha'] : ''; if (isset($src['easy'])) { $data['easy'] = $filter->clean($src['easy'], 'int'); } if (isset($src['modified_date'])) { $data['modified_date'] = $filter->clean($src['modified_date'], 'string'); } $data['modified_by'] = $filter->clean($src['modified_by'], 'string'); $data['milestone_id'] = isset($src['milestone_id']) ? $filter->clean($src['milestone_id'], 'int') : null; $state = $src['new_state']; $changedState = $src['old_state'] != $src['new_state']; // If the item has moved from open to closed, add the close data if ($state == 'closed' && $changedState) { $data['closed_date'] = (new Date())->format($this->getDb()->getDateFormat()); $data['closed_by'] = $data['modified_by']; } // If the item has moved from closed to open, remove the close data if ($state == 'open' && $changedState) { $data['closed_date'] = null; $data['closed_by'] = null; } $data['labels'] = null; if (!empty($src['labels'])) { $labels = []; foreach ($src['labels'] as $labelId) { $labels[] = (int) $labelId; } $data['labels'] = implode(',', $labels); } if (!$data['id']) { throw new \RuntimeException('Missing ID'); } $table = new IssuesTable($this->db); $table->load($data['id'])->bind($data)->check()->store(true); return $this; }
/** * Updates a pull request title to include the JoomlaCode ID if it exists * * @param object $hookData Hook data payload * @param Github $github Github object * @param Logger $logger Logger object * @param object $project Object containing project data * @param IssuesTable $table Table object * * @return void * * @since 1.0 */ protected function updatePullTitle($hookData, Github $github, Logger $logger, $project, IssuesTable $table) { // If the title already has the ID in it, then no need to do anything here if (preg_match('/\\[#([0-9]+)\\]/', $hookData->pull_request->title, $matches)) { return; } // If we don't have a foreign ID, we can't do anything here if (is_null($table->foreign_number)) { return; } // Define the new title $title = '[#' . $table->foreign_number . '] ' . $table->title; try { $github->pulls->edit($project->gh_user, $project->gh_project, $hookData->pull_request->number, $title); // Post the new label on the object $logger->info(sprintf('Updated the title for GitHub pull request %s/%s #%d', $project->gh_user, $project->gh_project, $hookData->pull_request->number)); } catch (\DomainException $e) { $logger->error(sprintf('Error updating the title for GitHub pull request %s/%s #%d - %s', $project->gh_user, $project->gh_project, $hookData->pull_request->number, $e->getMessage())); // Don't change the title locally return; } // Update the local title now try { $data = ['title' => $title]; $table->save($data); } catch (\Exception $e) { $logger->error(sprintf('Error updating the title for issue %s/%s #%d on the tracker: %s', $project->gh_user, $project->gh_project, $hookData->issue->number, $e->getMessage())); } }
/** * Method to update data for an issue from GitHub * * @param integer $id The comment ID * * @return boolean True on success * * @since 1.0 */ protected function updateComment($id) { // Try to render the comment with GitHub markdown $parsedText = $this->parseText($this->hookData->comment->body); // Only update fields that may have changed, there's no API endpoint to show that so make some guesses $data = array(); $data['activities_id'] = $id; $data['text'] = $parsedText; $data['text_raw'] = $this->hookData->comment->body; try { $table = new ActivitiesTable($this->db); $table->load(array('activities_id' => $id)); $table->save($data); } catch (\Exception $e) { $this->logger->error('Error updating the database for comment ' . $id . ':' . $e->getMessage()); $this->getContainer()->get('app')->close(); } try { $issueTable = new IssuesTable($this->db); $issueTable->load(array('issue_number' => $this->hookData->issue->number, 'project_id' => $this->project->project_id)); $this->triggerEvent('onCommentAfterUpdate', $issueTable); } catch (\Exception $e) { $this->logger->error('Error loading the database for comment ' . $this->hookData->issue->number . ':' . $e->getMessage()); } // Store was successful, update status $this->logger->info(sprintf('Updated comment %s/%s #%d to the tracker.', $this->project->gh_user, $this->project->gh_project, $id)); return true; }
/** * Method to update data for an issue from GitHub * * @return boolean True on success * * @since 1.0 */ protected function updateData() { $table = new IssuesTable($this->db); try { $table->load(array('issue_number' => $this->hookData->issue->number, 'project_id' => $this->project->project_id)); } catch (\Exception $e) { $this->logger->error(sprintf('Error loading GitHub issue %s/%s #%d in the tracker: %s', $this->project->gh_user, $this->project->gh_project, $this->hookData->issue->number, $e->getMessage())); $this->getContainer()->get('app')->close(); } // Figure out the state based on the action $action = $this->hookData->action; $status = $this->processStatus($action, $table->status); // Try to render the description with GitHub markdown $parsedText = $this->parseText($this->hookData->issue->body); // Prepare the dates for insertion to the database $dateFormat = $this->db->getDateFormat(); $modified = new Date($this->hookData->issue->updated_at); // Only update fields that may have changed, there's no API endpoint to show that so make some guesses $data = array(); $data['id'] = $table->id; $data['title'] = $this->hookData->issue->title; $data['description'] = $parsedText; $data['description_raw'] = $this->hookData->issue->body; $data['status'] = is_null($status) ? $table->status : $status; $data['modified_date'] = $modified->format($dateFormat); $data['modified_by'] = $this->hookData->sender->login; // Add the closed date if the status is closed if ($this->hookData->issue->closed_at) { $closed = new Date($this->hookData->issue->closed_at); $data['closed_date'] = $closed->format($dateFormat); } // Process labels for the item $data['labels'] = $this->processLabels($this->hookData->issue->number); // Grab some data based on the existing record $data['priority'] = $table->priority; $data['build'] = $table->build; $data['rel_number'] = $table->rel_number; $data['rel_type'] = $table->rel_type; $data['milestone_id'] = $table->milestone_id; if (empty($data['build'])) { $data['build'] = $this->hookData->repository->default_branch; } $model = (new IssueModel($this->db))->setProject(new TrackerProject($this->db, $this->project)); // Check if the state has changed (e.g. open/closed) $oldState = $model->getOpenClosed($table->status); $state = is_null($status) ? $oldState : $model->getOpenClosed($data['status']); $data['old_state'] = $oldState; $data['new_state'] = $state; try { $model->save($data); } catch (\Exception $e) { $this->logger->error(sprintf('Error updating GitHub issue %s/%s #%d (Database ID #%d) in the tracker: %s', $this->project->gh_user, $this->project->gh_project, $this->hookData->issue->number, $table->id, $e->getMessage())); $this->getContainer()->get('app')->close(); } // Refresh the table object for the listeners $table->load($data['id']); $this->triggerEvent('onIssueAfterUpdate', $table); // Add a reopen record to the activity table if the status is closed if ($action == 'reopened') { $this->addActivityEvent('reopen', $this->hookData->issue->updated_at, $this->hookData->sender->login, $this->project->project_id, $this->hookData->issue->number); } // Add a close record to the activity table if the status is closed if ($this->hookData->issue->closed_at) { $this->addActivityEvent('close', $this->hookData->issue->closed_at, $this->hookData->sender->login, $this->project->project_id, $this->hookData->issue->number); } // Store was successful, update status $this->logger->info(sprintf('Updated GitHub issue %s/%s #%d (Database ID #%d) to the tracker.', $this->project->gh_user, $this->project->gh_project, $this->hookData->issue->number, $table->id)); return true; }
/** * Method to update data for an issue from GitHub * * @return boolean True on success * * @since 1.0 */ protected function updateData() { // Figure out the state based on the action $action = $this->hookData->action; $status = $this->processStatus($action); // Try to render the description with GitHub markdown $parsedText = $this->parseText($this->hookData->issue->body); // Prepare the dates for insertion to the database $dateFormat = $this->db->getDateFormat(); $modified = new Date($this->hookData->issue->updated_at); // Only update fields that may have changed, there's no API endpoint to show that so make some guesses $data = array(); $data['title'] = $this->hookData->issue->title; $data['description'] = $parsedText; $data['description_raw'] = $this->hookData->issue->body; if (!is_null($status)) { $data['status'] = $status; } $data['modified_date'] = $modified->format($dateFormat); $data['modified_by'] = $this->hookData->sender->login; // Add the closed date if the status is closed if ($this->hookData->issue->closed_at) { $closed = new Date($this->hookData->issue->closed_at); $data['closed_date'] = $closed->format($dateFormat); } // Process labels for the item $data['labels'] = $this->processLabels($this->hookData->issue->number); try { $table = new IssuesTable($this->db); $table->load(array('issue_number' => $this->hookData->issue->number, 'project_id' => $this->project->project_id)); $table->save($data); } catch (\Exception $e) { $this->logger->error(sprintf('Error updating GitHub issue %s/%s #%d in the tracker: %s', $this->project->gh_user, $this->project->gh_project, $this->hookData->issue->number, $e->getMessage())); $this->getContainer()->get('app')->close(); } $this->triggerEvent('onIssueAfterUpdate', $table); // Add a reopen record to the activity table if the status is closed if ($action == 'reopened') { $this->addActivityEvent('reopen', $this->hookData->issue->updated_at, $this->hookData->sender->login, $this->project->project_id, $this->hookData->issue->number); } // Add a close record to the activity table if the status is closed if ($this->hookData->issue->closed_at) { $this->addActivityEvent('close', $this->hookData->issue->closed_at, $this->hookData->sender->login, $this->project->project_id, $this->hookData->issue->number); } // Store was successful, update status $this->logger->info(sprintf('Updated GitHub issue %s/%s #%d to the tracker.', $this->project->gh_user, $this->project->gh_project, $this->hookData->issue->number)); return true; }
/** * Save the item. * * @param array $src The source. * * @return $this * * @since 1.0 * @throws \RuntimeException */ public function save(array $src) { $filter = new InputFilter(); $data = array(); $data['id'] = $filter->clean($src['id'], 'int'); $data['status'] = $filter->clean($src['status'], 'int'); $data['priority'] = $filter->clean($src['priority'], 'int'); $data['title'] = $filter->clean($src['title'], 'string'); $data['build'] = $filter->clean($src['build'], 'string'); $data['description'] = $filter->clean($src['description'], 'raw'); $data['description_raw'] = $filter->clean($src['description_raw'], 'raw'); $data['rel_number'] = $filter->clean($src['rel_number'], 'int'); $data['rel_type'] = $filter->clean($src['rel_type'], 'int'); $data['easy'] = $filter->clean($src['easy'], 'int'); $data['modified_by'] = $filter->clean($src['modified_by'], 'string'); if (!$data['id']) { throw new \RuntimeException('Missing ID'); } $table = new IssuesTable($this->db); $table->load($data['id'])->bind($data)->check()->store(true); return $this; }
/** * Method to update data for an issue from GitHub * * @return boolean True on success * * @since 1.0 */ protected function updateData() { $table = new IssuesTable($this->db); try { $table->load(array('issue_number' => $this->data->number, 'project_id' => $this->project->project_id)); } catch (\Exception $e) { $this->setStatusCode($e->getCode()); $logMessage = sprintf('Error loading GitHub issue %s/%s #%d in the tracker: %s', $this->project->gh_user, $this->project->gh_project, $this->data->number, $e->getMessage()); $this->response->error = $logMessage; $this->logger->error($logMessage); return false; } // Figure out the state based on the action $action = $this->hookData->action; $status = $this->processStatus($action, $table->status); // Try to render the description with GitHub markdown $parsedText = $this->parseText($this->data->body); // Prepare the dates for insertion to the database $dateFormat = $this->db->getDateFormat(); $modified = new Date($this->data->updated_at); // Only update fields that may have changed, there's no API endpoint to show that so make some guesses $data = array(); $data['id'] = $table->id; $data['title'] = $this->data->title; $data['description'] = $parsedText; $data['description_raw'] = $this->data->body; $data['status'] = is_null($status) ? $table->status : $status; $data['modified_date'] = $modified->format($dateFormat); $data['modified_by'] = $this->hookData->sender->login; // Add the closed date if the status is closed if ($this->data->closed_at) { $closed = new Date($this->data->closed_at); $data['closed_date'] = $closed->format($dateFormat); } // Process labels for the item $data['labels'] = $this->processLabels($this->data->number); // Grab some data based on the existing record $data['priority'] = $table->priority; $data['build'] = $table->build; $data['rel_number'] = $table->rel_number; $data['rel_type'] = $table->rel_type; $data['milestone_id'] = $table->milestone_id; if (empty($data['build'])) { $data['build'] = $this->hookData->repository->default_branch; } $model = (new IssueModel($this->db))->setProject(new TrackerProject($this->db, $this->project)); // Check if the state has changed (e.g. open/closed) $oldState = $model->getOpenClosed($table->status); $state = is_null($status) ? $oldState : $model->getOpenClosed($data['status']); $data['old_state'] = $oldState; $data['new_state'] = $state; $gitHubBot = null; if ($this->project->gh_editbot_user && $this->project->gh_editbot_pass) { $gitHubBot = new GitHubHelper(GithubFactory::getInstance($this->getContainer()->get('app'), true, $this->project->gh_editbot_user, $this->project->gh_editbot_pass)); } if ($gitHubBot && $table->pr_head_sha && $table->pr_head_sha != $this->data->head->sha) { // The PR has been updated. $testers = (new IssueModel($this->getContainer()->get('db')))->getAllTests($table->id); if ($testers) { // Send a notification. $comment = "This PR has received new commits.\n\n**CC:** @" . implode(', @', $testers); $comment .= $gitHubBot->getApplicationComment($this->getContainer()->get('app'), $this->project, $table->issue_number); $gitHubBot->addComment($this->project, $table->issue_number, $comment, $this->project->gh_editbot_user, $this->getContainer()->get('db')); } } $data['pr_head_sha'] = $this->data->head->sha; $commits = (new GitHubHelper($this->github))->getCommits($this->project, $this->data->number); $data['commits'] = json_encode($commits); try { $model->save($data); } catch (\Exception $e) { $this->setStatusCode($e->getCode()); $logMessage = sprintf('Error updating GitHub pull request %s/%s #%d (Database ID #%d) to the tracker: %s', $this->project->gh_user, $this->project->gh_project, $this->data->number, $table->id, $e->getMessage()); $this->response->error = $logMessage; $this->logger->error($logMessage); return false; } // Refresh the table object for the listeners $table->load($data['id']); $this->triggerEvent('onPullAfterUpdate', ['table' => $table]); // Add a reopen record to the activity table if the status is reopened if ($action == 'reopened') { $this->addActivityEvent('reopen', $this->data->updated_at, $this->hookData->sender->login, $this->project->project_id, $this->data->number); } // Add a synchronize record to the activity table if the action is synchronized if ($action == 'synchronize') { $this->addActivityEvent('synchronize', $this->data->updated_at, $this->hookData->sender->login, $this->project->project_id, $this->data->number); } // Add a close record to the activity table if the status is closed if ($this->data->closed_at) { $this->addActivityEvent('close', $this->data->closed_at, $this->hookData->sender->login, $this->project->project_id, $this->data->number); } // Store was successful, update status $this->logger->info(sprintf('Updated GitHub pull request %s/%s #%d (Database ID #%d) to the tracker.', $this->project->gh_user, $this->project->gh_project, $this->data->number, $table->id)); return true; }