public function computeSample() { // Initialize the projects array. $this->projectIssueMetricInitProjects(array('response_rate' => 0, 'response_time' => 0)); // Query for issues created in the previosu time period, so there is a full time period for responses. $query = db_select('node', 'n', array('target' => 'slave'))->condition('n.created', $this->currentSample->sample_startstamp - ($this->currentSample->sample_endstamp - $this->currentSample->sample_startstamp), '>=')->condition('n.created', $this->currentSample->sample_startstamp, '<')->condition('n.type', project_issue_issue_node_types())->condition('n.status', NODE_PUBLISHED)->groupBy('n.nid'); $query->innerJoin('field_data_field_project', 'p', "n.nid = p.entity_id AND p.entity_type = 'node'"); $query->addField('p', 'field_project_target_id'); // Restrict to only the passed projects. if (!empty($this->currentSample->options['object_ids'])) { $query->condition('p.field_project_target_id', $this->currentSample->options['object_ids']); } // Issues not posted by the maintainer themself. $maintainers = db_select('project_maintainer', 'pm')->where('pm.nid = p.field_project_target_id')->fields('pm', array('uid')); $query->condition('n.uid', $maintainers, 'NOT IN'); $query->leftJoin('comment', 'c', 'c.nid = n.nid AND c.status = :status AND c.uid <> n.uid AND c.uid <> :robot AND c.created < :end', array(':status' => COMMENT_PUBLISHED, ':robot' => variable_get('project_issue_followup_user', 0), ':end' => $this->currentSample->sample_endstamp)); $query->addExpression('c.cid IS NOT NULL', 'responded'); $query->addExpression('min(c.created) - n.created', 'time'); $projects = array(); $issues = $query->execute(); foreach ($issues as $issue) { if (!isset($projects[$issue->field_project_target_id])) { $projects[$issue->field_project_target_id] = array('count' => 0, 'responded' => 0, 'time' => 0); } $projects[$issue->field_project_target_id]['count'] += 1; $projects[$issue->field_project_target_id]['responded'] += $issue->responded; $projects[$issue->field_project_target_id]['time'] += $issue->time; } foreach ($projects as $id => $project) { $this->currentSample->values[$id]['response_rate'] = round($project['responded'] / $project['count'] * 100); $this->currentSample->values[$id]['response_time'] = round($project['time'] / $project['count']); } }
public function computeSample() { // Load options. $options = $this->currentSample->options; // Initialize the projects array. $data_types = $this->newProjectOpenedVsClosed(); $this->projectIssueMetricInitProjects($data_types); // The initial arguments are the 'open' project issue status options. $open_states = project_issue_open_states(); // @todo Until http://drupal.org/node/214347 is resolved, we don't want // the 'fixed' status in the list of open statuses, so unset it here. $open_states = array_diff($open_states, array(PROJECT_ISSUE_STATE_FIXED)); // Pull last possible issue nid for the end of the period being measured. $max_nid = db_query('SELECT MAX(nid) FROM {node} WHERE created <= :created', array(':created' => $this->currentSample->sample_endstamp), array('target' => 'slave'))->fetchField(); if (!empty($max_nid)) { // Subquery to calculate the timestamp of the applicable revision $tsquery = db_select('node_revision', 'sv', array('target' => 'slave')); $tsquery->condition('sv.timestamp', $this->currentSample->sample_endstamp, '<='); $tsquery->addField('sv', 'nid', 'nid'); $tsquery->addExpression('MAX(sv.vid)', 'vid'); $tsquery->groupBy('sv.nid'); $tsquery->orderBy('NULL'); $query = db_select($tsquery, 'r', array('target' => 'slave')); $query->innerJoin('field_revision_field_project', 'p', "r.nid = p.entity_id AND r.vid = p.revision_id AND p.entity_type = 'node'"); $query->innerJoin('field_revision_field_issue_status', 's', "r.nid = s.entity_id AND r.vid = s.revision_id AND s.entity_type = 'node'"); $query->innerJoin('field_revision_field_issue_category', 'c', "r.nid = c.entity_id AND r.vid = c.revision_id AND c.entity_type = 'node'"); $query->innerJoin('node', 'n', 'n.nid = r.nid'); $query->condition('n.type', project_issue_issue_node_types()); $query->condition('n.nid', $max_nid, '<='); $query->condition('n.status', NODE_PUBLISHED); $query->addField('p', 'field_project_target_id', 'pid'); $query->addField('s', 'field_issue_status_value', 'sid'); $query->addField('c', 'field_issue_category_value', 'category'); // Restrict to only the passed projects. if (!empty($options['object_ids'])) { $query->condition('p.field_project_target_id', $options['object_ids']); } $issues = $query->execute(); // @todo When http://drupal.org/node/115553 lands, this hard-coded list of // categories will need to be handled differently. $categories = array(1 => 'bug', 3 => 'feature', 2 => 'task', 4 => 'support'); foreach ($issues as $issue) { // Add to the total count for the category in the values array. if (isset($categories[$issue->category])) { if (!isset($this->currentSample->values[$issue->pid])) { $this->currentSample->values[$issue->pid] = array('bug_open' => 0, 'bug_closed' => 0, 'feature_open' => 0, 'feature_closed' => 0, 'task_open' => 0, 'task_closed' => 0, 'support_open' => 0, 'support_closed' => 0); } $this->currentSample->values[$issue->pid][$categories[$issue->category] . '_' . (in_array($issue->sid, $open_states) ? 'open' : 'closed')] += 1; } } } // Add in total counts across all categories. foreach ($this->currentSample->values as $project_id => $values_array) { $this->currentSample->values[$project_id]['total_open'] = $values_array['bug_open'] + $values_array['feature_open'] + $values_array['task_open'] + $values_array['support_open']; $this->currentSample->values[$project_id]['total_closed'] = $values_array['bug_closed'] + $values_array['feature_closed'] + $values_array['task_closed'] + $values_array['support_closed']; } }
public function computeSample() { // Initialize the projects array. $this->projectIssueMetricInitProjects(array('reporters' => 0, 'participants' => 0)); // Load options. $sample = $this->currentSample; $query = db_select('node', 'n', array('target' => 'slave'))->condition('n.created', $sample->sample_startstamp, '>=')->condition('n.created', $sample->sample_endstamp, '<')->condition('n.type', project_issue_issue_node_types()); $query->innerJoin('field_data_field_project', 'p', "n.nid = p.entity_id AND p.entity_type = 'node'"); $query->addField('p', 'field_project_target_id', 'pid'); $query->addField('n', 'uid', 'uid'); $query->distinct(); // Restrict to only the passed projects. if (!empty($sample->options['object_ids'])) { $query->condition('p.field_project_target_id', $sample->options['object_ids']); } // Pull the count of unique reporters per project. $projects = $query->execute(); $project_participants = array(); foreach ($projects as $project) { // Increment the number of reporters for the project, and also store // them as a participant. if (isset($this->currentSample->values[$project->pid]['reporters'])) { $this->currentSample->values[$project->pid]['reporters'] += 1; } else { $this->currentSample->values[$project->pid]['reporters'] = 1; } $this->currentSample->values[$project->pid]['participants'] = 0; $project_participants[$project->pid][$project->uid] = TRUE; } // Pull the count of unique participants per project. $query = db_select('comment', 'c', array('target' => 'slave'))->condition('c.created', $sample->sample_startstamp, '>=')->condition('c.created', $sample->sample_endstamp, '<'); $query->innerJoin('field_data_field_project', 'p', "c.nid = p.entity_id AND p.entity_type = 'node'"); $query->addField('p', 'field_project_target_id', 'pid'); $query->addField('c', 'uid'); $query->distinct(); $projects = $query->execute(); // Add in participants from comments. This will overwrite the reporters if // they are the same uid, thus avoiding double counting. foreach ($projects as $project) { $project_participants[$project->pid][$project->uid] = TRUE; } // Store the total participants for each project. foreach ($project_participants as $pid => $participants) { if (!isset($this->currentSample->values[$pid]['reporters'])) { $this->currentSample->values[$pid]['reporters'] = 0; } $this->currentSample->values[$pid]['participants'] = count($participants); } unset($project_participants); }
public function computeSample() { // Initialize the projects array. $this->projectIssueMetricInitProjects(array('new_issues' => 0, 'new_comments' => 0, 'new_total' => 0)); // Load options. $sample = $this->currentSample; $query = db_select('node', 'n', array('target' => 'slave'))->condition('n.created', $sample->sample_startstamp, '>=')->condition('n.created', $sample->sample_endstamp, '<')->condition('n.type', project_issue_issue_node_types()); $query->innerJoin('field_data_field_project', 'p', "n.nid = p.entity_id AND p.entity_type = 'node'"); $query->addField('p', 'field_project_target_id', 'pid'); $query->addExpression('COUNT(n.nid)', 'count'); $query->groupBy('p.field_project_target_id'); // Restrict to only the passed projects. if (!empty($sample->options['object_ids'])) { $query->condition('p.field_project_target_id', $sample->options['object_ids']); } // Pull the count of new issues per project. $projects = $query->execute(); foreach ($projects as $project) { $this->currentSample->values[$project->pid]['new_issues'] = (int) $project->count; $this->currentSample->values[$project->pid]['new_comments'] = 0; $this->currentSample->values[$project->pid]['new_total'] = (int) $project->count; } // Pull the count of new issue comments per project. $query = db_select('comment', 'c', array('target' => 'slave'))->condition('c.created', $sample->sample_startstamp, '>=')->condition('c.created', $sample->sample_endstamp, '<'); $query->innerJoin('field_data_field_project', 'p', "c.nid = p.entity_id AND p.entity_type = 'node'"); $query->addField('p', 'field_project_target_id', 'pid'); $query->addExpression('COUNT(c.cid)', 'count'); $query->groupBy('p.field_project_target_id'); // Restrict to only the passed projects. if (!empty($sample->options['object_ids'])) { $query->condition('p.field_project_target_id', $sample->options['object_ids']); } $projects = $query->execute(); foreach ($projects as $project) { if (!isset($this->currentSample->values[$project->pid]['new_issues'])) { $this->currentSample->values[$project->pid]['new_issues'] = 0; } $this->currentSample->values[$project->pid]['new_comments'] = (int) $project->count; if (isset($this->currentSample->values[$project->pid]['new_total'])) { $this->currentSample->values[$project->pid]['new_total'] += (int) $project->count; } else { $this->currentSample->values[$project->pid]['new_total'] = (int) $project->count; } } }
/** * Implements EntityReferenceHandler::getReferencableEntities(). */ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 25) { $options = array(); $target_node_types = $this->field['settings']['handler_settings']['target_bundles']; // No target node types means all issue types may be selected. if (empty($target_node_types)) { $target_node_types = project_issue_issue_node_types(); } global $base_url; // Early return if the short match string would generate too many results. // @todo: Make this configurable? Return even if user has ebereted #nid. // It's unlikely they're trying to reference a two digit nid issue. if (strlen($match) < 4) { return $options; } // If the given string begins with the site domain, try to match it to the // URL of an issue node. if (substr($match, 0, strlen($base_url)) == $base_url) { $matches = array(); // Extract the node ID from the URL, allowing for an anchor tag. preg_match("@^{$base_url}/node/(\\d+)(?:#\\S+)?\$@", $match, $matches); if (isset($matches[1])) { $nid = $matches[1]; $node = node_load($nid); if ($node) { // Only allow the node if it's of the right type and the user has // access to view it. if (in_array($node->type, $target_node_types) && node_access('view', $node)) { $options[$node->type][$nid] = check_plain($this->getLabel($node)); // Don't return yet, as there is a slim chance that the URL is part // of the title of an issue which starts with the domain name or // even the full URL, as in 'http://example.com/node/1 is broken'. } } } } // If the given string is of the form '#1234' then try to match that as a // nid. if (strpos($match, '#') === 0) { if (preg_match("@^#(\\d+)\$@", $match)) { $nid = substr($match, 1); $node = node_load($nid); if ($node) { // Only allow the node if it's of the right type and the user has // access to view it. if (in_array($node->type, $target_node_types) && node_access('view', $node)) { $options[$node->type][$nid] = check_plain($this->getLabel($node)); // Don't return, same reason as above. } } } } // Build a query for the nodes. We can't use buildEntityFieldQuery() because // we have to use a SelectQuery rather than an EntityFieldQuery to have an // OR condition. $query = db_select('node', 'n'); if (isset($match)) { // Try to match on the title or nid. $query->condition(db_or()->condition('n.title', '%' . db_like($match) . '%', 'LIKE')->condition('n.nid', $match)); } // Set the node type. $query->condition('type', $target_node_types); if (!user_access('bypass node access')) { // Restrict the query to published nodes. $query->condition('n.status', NODE_PUBLISHED); } // Restrict the number of returned rows. jQuery UI autocomplete defaults to // showing 10 only. if (!empty($limit)) { $query->range(0, $limit); } // Order the returned nodes by some sort of relevancy. $query->orderBy('n.changed', 'DESC'); $node_data = $query->fields('n', array('nid', 'title', 'type'))->addTag('node_access')->execute()->fetchAll(); foreach ($node_data as $item) { $options[$item->type][$item->nid] = check_plain($item->title); } return $options; }
/** * Builds the historical values for total open/closed issues per project. * * @param $open_states * An array of state IDs that are considered 'open'. * @param $where * Additional filters for the query. * @param $args * Query arguments. */ protected function buildSampleResultsHistorical($open_states, $ids) { // Load current sample. $sample = $this->currentSample; $endstamp = $sample->sample_endstamp; // Pull last possible issue nid for the end of the period being measured. $max_nid = db_query('SELECT MAX(nid) FROM {node} WHERE created <= :created', array(':created' => $endstamp))->fetchField(); if (!empty($max_nid)) { // Subquery to calculate the timestamp of the applicable revision $tsquery = db_select('node_revision', 'sv'); $tsquery->condition('sv.timestamp', $endstamp, '<='); $tsquery->addField('sv', 'nid', 'nid'); $tsquery->addExpression('MAX(sv.timestamp)', 'timestamp'); $query = db_select($tsquery, 'sq'); $query->innerJoin('node_revision', 'r', 'sq.nid = r.nid AND sq.timestamp = r.timestamp'); $query->innerJoin('field_revision_field_project', 'p', "r.nid = p.entity_id AND r.vid = p.revision_id AND p.entity_type = 'node'"); $query->innerJoin('field_revision_field_issue_status', 's', "r.nid = s.entity_id AND r.vid = s.revision_id AND s.entity_type = 'node'"); $query->innerJoin('field_revision_field_issue_category', 'c', "r.nid = c.entity_id AND r.vid = c.revision_id AND c.entity_type = 'node'"); $query->innerJoin('node', 'n', 'n.nid = r.nid'); $query->condition('n.type', project_issue_issue_node_types()); $query->condition('n.nid', $max_nid, '<='); $query->groupBy('n.nid'); $query->groupBy('p.field_project_target_id'); $query->addField('n', 'nid', 'nid'); $query->addField('p', 'field_project_target_id', 'pid'); $query->addField('s', 'field_issue_status_value', 'sid'); $query->addField('c', 'field_issue_category_value', 'category'); // Restrict to only the passed projects. if (!empty($ids)) { $query->condition('p.field_project_target_id', $ids); } $issues = $query->execute(); // @todo When http://drupal.org/node/115553 lands, this hard-coded list of // categories will need to be handled differently. $categories = array(1 => 'bug', 3 => 'feature', 2 => 'task', 4 => 'support'); foreach ($issues as $issue) { $state = in_array($issue->sid, $open_states) ? 'open' : 'closed'; // Add to the total count for the category in the values array. if (isset($categories[$issue->category])) { $this->currentSample->values[$issue->pid][$categories[$issue->category] . "_{$state}"]++; } } // Clean up potentially huge arrays that are sucking memory. unset($issues); } else { return FALSE; } }