https://bugs.webkit.org/show_bug.cgi?id=138910
Reviewed by Benjamin Poulain.
This patch reverts some parts of r175006 and re-introduces bugs associated with analysis tasks.
I'll add UI to show and edit bug numbers associated with an analysis task in a follow up patch.
With this patch, we can create a new analysis task by selection a range of points and opening
"analysis pane" (renamed from "bugs pane"). Each analysis task created is represented by a yellow bar
in the chart hyperlinked to the analysis task.
* init-database.sql: Redefined the bugs to be associated with an analysis task instead of a test run.
* public/api/analysis-tasks.php: Added the support for querying analysis tasks for a specific metric
on a specific platform. Also retrieve and return all bugs associated with analysis tasks.
(main):
(fetch_and_push_bugs_to_tasks): Added. Fetches all bugs associated with an array of analysis tasks
and adds the associated bugs to each task in the array.
(format_task):
* public/api/runs.php: Reverted changes made in r175006.
(fetch_runs_for_config):
(format_run):
* public/api/test-groups.php:
(fetch_test_groups_for_task): Use the newly added Database::select_rows.
* public/include/db.php:
(Database::select_first_or_last_row):
(Database::select_rows): Extracted from select_first_or_last_row.
* public/v2/analysis.js:
(App.AnalysisTask): Added "bugs" property.
(App.Bug): Added now that bugs are regular data store objects.
* public/v2/app.js:
(App.Pane._fetch): Calls this.fetchAnalyticRanges to fetch analysis tasks as well as test runs.
(App.Pane.fetchAnalyticRanges): Added. Fetches analysis tasks for the current metric on the current
platform that are associated with a specific range of runs.
(App.PaneController.actions.toggleBugsPane): Updated per showingBugsPane to showingAnalysisPane rename.
(App.PaneController.actions.associateBug): Deleted.
(App.PaneController.actions.createAnalysisTask): Replaced the pre-condition checks with assertions as
this action should never be triggered when the pre-condition is not met. Also re-fetch analysis tasks
once we've created one.
(App.PaneController.toggleSearchPane): Updated per showingBugsPane to showingAnalysisPane rename.
(App.PaneController._detailsChanged): Ditto. Removed selectedSinglePoint since it's no longer used.
(App.PaneController._showDetails): Call _updateCanAnalyze to update the status of "Analyze" button.
(App.PaneController._updateBugs): Deleted.
(App.PaneController._updateMarkedPoints): Deleted.
(App.PaneController._updateCanAnalyze): Added. Disables the button to create an analysis task when
the name is missing or when at most one point is selected.
(App.InteractiveChartComponent._constructGraphIfPossible): Update the locations of range rects.
(App.InteractiveChartComponent._relayoutDataAndAxes): Ditto.
(App.InteractiveChartComponent._mousePointInGraph): Don't return a point unless the mouse cursor is
on our svg element to avoid locking the current item when a bar shown for an analysis task is clicked.
(App.InteractiveChartComponent._rangesChanged): Added. Creates an array of objects representing
clickable bars for analysis tasks.
(App.InteractiveChartComponent._updateRangeBarRects): Computes the inline style used by each clickable
bar for analysis tasks to place them at the right location.
(App.InteractiveChartComponent.actions.openRange): Added. Forwards the action to the parent controller.
* public/v2/chart-pane.css:
(.chart .extent): Use the same color as the vertical indicator in the highlight behind the selection.
(.chart .rangeBar): Added.
* public/v2/data.js:
(TimeSeries.prototype.nextPoint): Added. Used by _rangesChanged.
* public/v2/index.html: Renamed "bugs pane" to "analysis pane" and removed the UI to associate bugs.
This ability will be reinstated in a follow up patch. Also added a container div and spans for analysis
task bars in the interactive chart component.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@176422
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2014-11-20 Ryosuke Niwa <rniwa@webkit.org>
+
+ New perf dashboard should provide UI to create a new analysis task
+ https://bugs.webkit.org/show_bug.cgi?id=138910
+
+ Reviewed by Benjamin Poulain.
+
+ This patch reverts some parts of r175006 and re-introduces bugs associated with analysis tasks.
+ I'll add UI to show and edit bug numbers associated with an analysis task in a follow up patch.
+
+ With this patch, we can create a new analysis task by selection a range of points and opening
+ "analysis pane" (renamed from "bugs pane"). Each analysis task created is represented by a yellow bar
+ in the chart hyperlinked to the analysis task.
+
+ * init-database.sql: Redefined the bugs to be associated with an analysis task instead of a test run.
+
+ * public/api/analysis-tasks.php: Added the support for querying analysis tasks for a specific metric
+ on a specific platform. Also retrieve and return all bugs associated with analysis tasks.
+ (main):
+ (fetch_and_push_bugs_to_tasks): Added. Fetches all bugs associated with an array of analysis tasks
+ and adds the associated bugs to each task in the array.
+ (format_task):
+
+ * public/api/runs.php: Reverted changes made in r175006.
+ (fetch_runs_for_config):
+ (format_run):
+
+ * public/api/test-groups.php:
+ (fetch_test_groups_for_task): Use the newly added Database::select_rows.
+
+ * public/include/db.php:
+ (Database::select_first_or_last_row):
+ (Database::select_rows): Extracted from select_first_or_last_row.
+
+ * public/v2/analysis.js:
+ (App.AnalysisTask): Added "bugs" property.
+ (App.Bug): Added now that bugs are regular data store objects.
+
+ * public/v2/app.js:
+ (App.Pane._fetch): Calls this.fetchAnalyticRanges to fetch analysis tasks as well as test runs.
+ (App.Pane.fetchAnalyticRanges): Added. Fetches analysis tasks for the current metric on the current
+ platform that are associated with a specific range of runs.
+ (App.PaneController.actions.toggleBugsPane): Updated per showingBugsPane to showingAnalysisPane rename.
+ (App.PaneController.actions.associateBug): Deleted.
+ (App.PaneController.actions.createAnalysisTask): Replaced the pre-condition checks with assertions as
+ this action should never be triggered when the pre-condition is not met. Also re-fetch analysis tasks
+ once we've created one.
+ (App.PaneController.toggleSearchPane): Updated per showingBugsPane to showingAnalysisPane rename.
+ (App.PaneController._detailsChanged): Ditto. Removed selectedSinglePoint since it's no longer used.
+ (App.PaneController._showDetails): Call _updateCanAnalyze to update the status of "Analyze" button.
+ (App.PaneController._updateBugs): Deleted.
+ (App.PaneController._updateMarkedPoints): Deleted.
+ (App.PaneController._updateCanAnalyze): Added. Disables the button to create an analysis task when
+ the name is missing or when at most one point is selected.
+
+ (App.InteractiveChartComponent._constructGraphIfPossible): Update the locations of range rects.
+ (App.InteractiveChartComponent._relayoutDataAndAxes): Ditto.
+ (App.InteractiveChartComponent._mousePointInGraph): Don't return a point unless the mouse cursor is
+ on our svg element to avoid locking the current item when a bar shown for an analysis task is clicked.
+ (App.InteractiveChartComponent._rangesChanged): Added. Creates an array of objects representing
+ clickable bars for analysis tasks.
+ (App.InteractiveChartComponent._updateRangeBarRects): Computes the inline style used by each clickable
+ bar for analysis tasks to place them at the right location.
+ (App.InteractiveChartComponent.actions.openRange): Added. Forwards the action to the parent controller.
+
+ * public/v2/chart-pane.css:
+ (.chart .extent): Use the same color as the vertical indicator in the highlight behind the selection.
+ (.chart .rangeBar): Added.
+
+ * public/v2/data.js:
+ (TimeSeries.prototype.nextPoint): Added. Used by _rangesChanged.
+
+ * public/v2/index.html: Renamed "bugs pane" to "analysis pane" and removed the UI to associate bugs.
+ This ability will be reinstated in a follow up patch. Also added a container div and spans for analysis
+ task bars in the interactive chart component.
+
2014-11-19 Ryosuke Niwa <rniwa@webkit.org>
Fix typos in r176203.
report_failure varchar(64),
report_failure_details text);
-CREATE TABLE bugs (
- bug_id serial PRIMARY KEY,
- bug_run integer REFERENCES test_runs NOT NULL,
- bug_tracker integer REFERENCES bug_trackers NOT NULL,
- bug_number integer NOT NULL,
- CONSTRAINT bug_tracker_and_run_must_be_unique UNIQUE(bug_tracker, bug_run));
-CREATE INDEX bugs_tracker_number_index ON bugs(bug_tracker, bug_number);
-CREATE INDEX bugs_run_index ON bugs(bug_run);
-
CREATE TABLE analysis_tasks (
task_id serial PRIMARY KEY,
task_name varchar(256) NOT NULL,
task_metric integer REFERENCES test_metrics NOT NULL,
task_start_run integer REFERENCES test_runs,
task_end_run integer REFERENCES test_runs,
- CONSTRAINT analysis_task_should_be_unique_for_range UNIQUE(task_start_run, task_end_run));
+ CONSTRAINT analysis_task_should_be_unique_for_range UNIQUE(task_start_run, task_end_run)
+ CONSTRAINT analysis_task_should_not_be_associated_with_single_run
+ CHECK ((task_start_run IS NULL AND task_end_run IS NULL) OR (task_start_run IS NOT NULL AND task_end_run IS NOT NULL)));
+
+CREATE TABLE bugs (
+ bug_id serial PRIMARY KEY,
+ bug_task integer REFERENCES analysis_tasks NOT NULL,
+ bug_tracker integer REFERENCES bug_trackers NOT NULL,
+ bug_number integer NOT NULL,
+ CONSTRAINT bug_task_and_tracker_must_be_unique UNIQUE(bug_task, bug_tracker));
CREATE TABLE analysis_test_groups (
testgroup_id serial PRIMARY KEY,
exit_with_error('TaskNotFound', array('id' => $task_id));
$tasks = array($task);
} else {
- // FIXME: Limit the number of tasks we fetch.
- $tasks = array_reverse($db->fetch_table('analysis_tasks', 'task_created_at'));
+ $metric_id = array_get($_GET, 'metric');
+ $platform_id = array_get($_GET, 'platform');
+ if (!!$metric_id != !!$platform_id)
+ exit_with_error('InvalidArguments', array('metricId' => $metric_id, 'platformId' => $platform_id));
+
+ if ($metric_id)
+ $tasks = $db->select_rows('analysis_tasks', 'task', array('platform' => $platform_id, 'metric' => $metric_id));
+ else {
+ // FIXME: Limit the number of tasks we fetch.
+ $tasks = array_reverse($db->fetch_table('analysis_tasks', 'task_created_at'));
+ }
+
if (!is_array($tasks))
exit_with_error('FailedToFetchTasks');
}
- exit_with_success(array('analysisTasks' => array_map("format_task", $tasks)));
+ $tasks = array_map("format_task", $tasks);
+ $bugs = fetch_and_push_bugs_to_tasks($db, $tasks);
+
+ exit_with_success(array('analysisTasks' => $tasks, 'bugs' => $bugs));
+}
+
+function fetch_and_push_bugs_to_tasks($db, &$tasks) {
+ $task_ids = array();
+ $task_by_id = array();
+ foreach ($tasks as &$task) {
+ array_push($task_ids, $task['id']);
+ $task_by_id[$task['id']] = &$task;
+ }
+
+ $bugs = $db->query_and_fetch_all('SELECT bug_id AS "id", bug_task AS "task", bug_tracker AS "bugTracker", bug_number AS "number"
+ FROM bugs WHERE bug_task = ANY ($1)', array('{' . implode(', ', $task_ids) . '}'));
+ if (!is_array($bugs))
+ exit_with_error('FailedToFetchBugs');
+
+ foreach ($bugs as $bug) {
+ $associated_task = &$task_by_id[$bug['task']];
+ array_push($associated_task['bugs'], $bug['id']);
+ }
+
+ return $bugs;
}
date_default_timezone_set('UTC');
'metric' => $task_row['task_metric'],
'startRun' => $task_row['task_start_run'],
'endRun' => $task_row['task_end_run'],
+ 'bugs' => array(),
);
}
function fetch_runs_for_config($db, $config) {
$raw_runs = $db->query_and_fetch_all('
SELECT test_runs.*, builds.*, array_agg((commit_repository, commit_revision, commit_time)) AS revisions
- FROM builds LEFT OUTER JOIN build_commits ON commit_build = build_id
- LEFT OUTER JOIN commits ON build_commit = commit_id,
- (SELECT test_runs.*, array_agg((bug_tracker, bug_number)) AS bugs
- FROM test_runs LEFT OUTER JOIN bugs ON bug_run = run_id WHERE run_config = $1 GROUP BY run_id) as test_runs
- WHERE run_build = build_id
- GROUP BY run_id, run_config, run_build, run_mean_cache, run_iteration_count_cache,
- run_sum_cache, run_square_sum_cache, bugs, build_id', array($config['config_id']));
+ FROM builds
+ LEFT OUTER JOIN build_commits ON commit_build = build_id
+ LEFT OUTER JOIN commits ON build_commit = commit_id, test_runs
+ WHERE run_build = build_id AND run_config = $1
+ GROUP BY build_id, run_id', array($config['config_id']));
$formatted_runs = array();
if (!$raw_runs)
return $revisions;
}
-function parse_bugs_array($postgres_array) {
- // e.g. {"(1 /* Bugzilla */, 12345)","(2 /* Radar */, 67890)"}
- $outer_array = json_decode('[' . trim($postgres_array, '{}') . ']');
- $bugs = array();
- foreach ($outer_array as $item) {
- $raw_data = explode(',', trim($item, '()'));
- if (!$raw_data[0])
- continue;
- $bugs[trim($raw_data[0], '"')] = trim($raw_data[1], '"');
- }
- return $bugs;
-}
-
function format_run($run) {
return array(
'id' => intval($run['run_id']),
'sum' => floatval($run['run_sum_cache']),
'squareSum' => floatval($run['run_square_sum_cache']),
'revisions' => parse_revisions_array($run['revisions']),
- 'bugs' => parse_bugs_array($run['bugs']),
'buildTime' => strtotime($run['build_time']) * 1000,
'buildNumber' => intval($run['build_number']),
'builder' => $run['build_builder']);
}
function fetch_test_groups_for_task($db, $task_id) {
- return $db->query_and_fetch_all('SELECT * FROM analysis_test_groups WHERE testgroup_task = $1
- ORDER BY testgroup_created_at', array($task_id));
+ return $db->select_rows('analysis_test_groups', 'testgroup', array('task' => $task_id));
}
function fetch_build_requests_for_task($db, $task_id) {
}
private function select_first_or_last_row($table, $prefix, $params, $order_by, $descending_order) {
+ $rows = $this->select_rows($table, $prefix, $params, $order_by, $descending_order, 0, 1);
+ return $rows ? $rows[0] : NULL;
+ }
+
+ function select_rows($table, $prefix, $params,
+ $order_by = NULL, $descending_order = FALSE, $offset = NULL, $limit = NULL) {
+
$placeholders = array();
$values = array();
$column_names = $this->prefixed_column_names($this->prepare_params($params, $placeholders, $values), $prefix);
if ($descending_order)
$query .= ' DESC';
}
- $rows = $this->query_and_fetch_all($query . ' LIMIT 1', $values);
+ if ($offset !== NULL)
+ $query .= ' OFFSET ' . intval($offset);
+ if ($limit !== NULL)
+ $query .= ' LIMIT ' . intval($limit);
- return $rows ? $rows[0] : NULL;
+ return $this->query_and_fetch_all($query, $values);
}
function query_and_get_affected_rows($query, $params = array()) {
metric: DS.belongsTo('metric'),
startRun: DS.attr('number'),
endRun: DS.attr('number'),
+ bugs: DS.hasMany('bugs'),
testGroups: function () {
return this.store.find('testGroup', {task: this.get('id')});
}.property(),
});
+App.Bug = App.NameLabelModel.extend({
+ task: DS.belongsTo('AnalysisTask'),
+ bugTracker: DS.belongsTo('BugTracker'),
+ createdAt: DS.attr('date'),
+ number: DS.attr('number'),
+});
+
// FIXME: Use DS.RESTAdapter instead.
App.AnalysisTask.create = function (name, startMeasurement, endMeasurement)
{
else
self.set('failure', 'An internal error');
});
+
+ this.fetchAnalyticRanges();
}
}.observes('platformId', 'metricId').on('init'),
+ fetchAnalyticRanges: function ()
+ {
+ var platformId = this.get('platformId');
+ var metricId = this.get('metricId');
+ var self = this;
+ this.get('store')
+ .find('analysisTask', {platform: platformId, metric: metricId})
+ .then(function (tasks) {
+ self.set('analyticRanges', tasks.filter(function (task) { return task.get('startRun') && task.get('endRun'); }));
+ });
+ },
_isValidId: function (id)
{
if (typeof(id) == "number")
},
toggleBugsPane: function ()
{
- if (this.toggleProperty('showingBugsPane'))
+ if (this.toggleProperty('showingAnalysisPane'))
this.set('showingSearchPane', false);
},
- associateBug: function (bugTracker, bugNumber)
- {
- var point = this.get('selectedSinglePoint');
- if (!point)
- return;
- var self = this;
- point.measurement.associateBug(bugTracker.get('id'), bugNumber).then(function () {
- self._updateBugs();
- self._updateMarkedPoints();
- }, function (error) {
- alert(error);
- });
- },
createAnalysisTask: function ()
{
var name = this.get('newAnalysisTaskName');
var points = this._selectedPoints;
- if (!name || !points || points.length < 2)
- return;
+ Ember.assert('The analysis name should not be empty', name);
+ Ember.assert('There should be at least two points in the range', points && points.length >= 2);
var newWindow = window.open();
+ var self = this;
App.AnalysisTask.create(name, points[0].measurement, points[points.length - 1].measurement).then(function (data) {
// FIXME: Update the UI to show the new analysis task.
var url = App.Router.router.generate('analysisTask', data['taskId']);
newWindow.location.href = '#' + url;
+ self.get('model').fetchAnalyticRanges();
}, function (error) {
newWindow.close();
if (error === 'DuplicateAnalysisTask') {
if (!model.get('commitSearchRepository'))
model.set('commitSearchRepository', App.Manifest.repositoriesWithReportedCommits[0]);
if (this.toggleProperty('showingSearchPane'))
- this.set('showingBugsPane', false);
+ this.set('showingAnalysisPane', false);
},
searchCommit: function () {
var model = this.get('model');
},
_detailsChanged: function ()
{
- this.set('showingBugsPane', false);
- this.set('selectedSinglePoint', !this._hasRange && this._selectedPoints ? this._selectedPoints[0] : null);
+ this.set('showingAnalysisPane', false);
}.observes('details'),
_overviewSelectionChanged: function ()
{
buildTime: currentMeasurement.formattedBuildTime(),
revisions: revisions,
}));
- this._updateBugs();
- },
- _updateBugs: function ()
- {
- if (!this._selectedPoints)
- return;
-
- var bugTrackers = App.Manifest.get('bugTrackers');
- var trackerToBugNumbers = {};
- bugTrackers.forEach(function (tracker) { trackerToBugNumbers[tracker.get('id')] = new Array(); });
-
- var points = this._hasRange ? this._selectedPoints : [this._selectedPoints[1]];
- points.map(function (point) {
- var bugs = point.measurement.bugs();
- bugTrackers.forEach(function (tracker) {
- var bugNumber = bugs[tracker.get('id')];
- if (bugNumber)
- trackerToBugNumbers[tracker.get('id')].push(bugNumber);
- });
- });
-
- this.set('details.bugTrackers', App.Manifest.get('bugTrackers').map(function (tracker) {
- var bugNumbers = trackerToBugNumbers[tracker.get('id')];
- return Ember.ObjectProxy.create({
- content: tracker,
- bugs: bugNumbers.map(function (bugNumber) {
- return {
- bugNumber: bugNumber,
- bugUrl: bugNumber && tracker.get('bugUrl') ? tracker.get('bugUrl').replace(/\$number/g, bugNumber) : null
- };
- }),
- editedBugNumber: this._hasRange ? null : bugNumbers[0],
- }); // FIXME: Create urls for new bugs.
- }));
+ this._updateCanAnalyze();
},
- _updateMarkedPoints: function ()
+ _updateCanAnalyze: function ()
{
- var chartData = this.get('chartData');
- if (!chartData || !chartData.current) {
- this.set('markedPoints', {});
- return;
- }
-
- var series = chartData.current.timeSeriesByCommitTime().series();
- var markedPoints = {};
- for (var i = 0; i < series.length; i++) {
- var measurement = series[i].measurement;
- if (measurement.hasBugs())
- markedPoints[measurement.id()] = true;
- }
- this.set('markedPoints', markedPoints);
- }.observes('chartData'),
+ var points = this._selectedPoints;
+ this.set('cannotAnalyze', !this.get('newAnalysisTaskName') || !this._hasRange || !points || points.length < 2);
+ }.observes('newAnalysisTaskName'),
});
App.InteractiveChartComponent = Ember.Component.extend({
setTimeout(this._selectedItemChanged.bind(this), 0);
this._needsConstruction = false;
+
+ this._rangesChanged();
},
_updateDomain: function ()
{
});
this._updateMarkedDots();
this._updateHighlightPositions();
+ this._updateRangeBarRects();
if (this._brush) {
if (selection)
this._yAxisUnitContainer.remove();
this._yAxisUnitContainer = this._yAxisLabels.append("text")
.attr("x", 0.5 * this._rem)
- .attr("y", this._rem)
+ .attr("y", 0.2 * this._rem)
.attr("dy", 0.8 * this._rem)
.style("text-anchor", "start")
.style("z-index", "100")
_mousePointInGraph: function (event)
{
var offset = $(this.get('element')).offset();
- if (!offset)
+ if (!offset || !$(event.target).closest('svg').length)
return null;
var point = {
this._updateHighlightPositions();
}.observes('highlightedItems'),
+ _rangesChanged: function ()
+ {
+ if (!this._currentTimeSeries)
+ return;
+
+ function midPoint(firstPoint, secondPoint) {
+ if (firstPoint && secondPoint)
+ return (+firstPoint.time + +secondPoint.time) / 2;
+ if (firstPoint)
+ return firstPoint.time;
+ return secondPoint.time;
+ }
+ var currentTimeSeries = this._currentTimeSeries;
+ var linkRoute = this.get('rangeRoute');
+ this.set('rangeBars', (this.get('ranges') || []).map(function (range) {
+ var start = currentTimeSeries.findPointByMeasurementId(range.get('startRun'));
+ var end = currentTimeSeries.findPointByMeasurementId(range.get('endRun'));
+ return Ember.Object.create({
+ startTime: midPoint(currentTimeSeries.previousPoint(start), start),
+ endTime: midPoint(end, currentTimeSeries.nextPoint(end)),
+ range: range,
+ left: null,
+ right: null,
+ rowIndex: null,
+ top: null,
+ bottom: null,
+ linkRoute: linkRoute,
+ linkId: range.get('id'),
+ });
+ }));
+
+ this._updateRangeBarRects();
+ }.observes('ranges'),
+ _updateRangeBarRects: function () {
+ var rangeBars = this.get('rangeBars');
+ if (!rangeBars || !rangeBars.length)
+ return;
+
+ var xScale = this._x;
+ var yScale = this._y;
+
+ // Expand the width of each range as needed and sort ranges by the left-edge of ranges.
+ var minWidth = 3;
+ var sortedBars = rangeBars.map(function (bar) {
+ var left = xScale(bar.get('startTime'));
+ var right = xScale(bar.get('endTime'));
+ if (right - left < minWidth) {
+ left -= minWidth / 2;
+ right += minWidth / 2;
+ }
+ bar.set('left', left);
+ bar.set('right', right);
+ return bar;
+ }).sort(function (first, second) { return first.get('left') - second.get('left'); });
+
+ // At this point, left edges of all ranges prior to a range R1 is on the left of R1.
+ // Place R1 into a row in which right edges of all ranges prior to R1 is on the left of R1 to avoid overlapping ranges.
+ var rows = [];
+ sortedBars.forEach(function (bar) {
+ var rowIndex = 0;
+ for (; rowIndex < rows.length; rowIndex++) {
+ var currentRow = rows[rowIndex];
+ if (currentRow[currentRow.length - 1].get('right') < bar.get('left')) {
+ currentRow.push(bar);
+ break;
+ }
+ }
+ if (rowIndex >= rows.length)
+ rows.push([bar]);
+ bar.set('rowIndex', rowIndex);
+ });
+ var rowHeight = 0.6 * this._rem;
+ var firstRowTop = this._contentHeight - rows.length * rowHeight;
+ var barHeight = 0.5 * this._rem;
+
+ $(this.get('element')).find('.rangeBarsContainerInlineStyle').css({
+ left: this._margin.left + 'px',
+ top: this._margin.top + firstRowTop + 'px',
+ width: this._contentWidth + 'px',
+ height: rows.length * barHeight + 'px',
+ overflow: 'hidden',
+ position: 'absolute',
+ });
+
+ var margin = this._margin;
+ sortedBars.forEach(function (bar) {
+ var top = bar.get('rowIndex') * rowHeight;
+ var height = barHeight;
+ var left = bar.get('left');
+ var width = bar.get('right') - left;
+ bar.set('inlineStyle', 'left: ' + left + 'px; top: ' + top + 'px; width: ' + width + 'px; height: ' + height + 'px;');
+ });
+ },
_updateCurrentItemIndicators: function ()
{
if (!this._currentItemLine)
this.sendAction('zoom', this._currentSelection());
this.set('selection', null);
},
+ openRange: function (range)
+ {
+ this.sendAction('openRange', range);
+ },
},
});
}
.chart .extent {
- stroke: #9c6;
+ stroke: #f93;
stroke-width: 1px;
fill: #9c6;
fill-opacity: .125;
fill: #333;
stroke: none;
}
+
+.chart .rangeBar {
+ display: block;
+ background-color: #fc6;
+ position: absolute;
+}
return null;
return this._series[point.seriesIndex - 1];
}
+
+TimeSeries.prototype.nextPoint = function (point)
+{
+ if (!point.seriesIndex)
+ return null;
+ return this._series[point.seriesIndex + 1];
+}
<h1 {{action "toggleDetails"}}>{{metric.fullName}} - {{ platform.name}}</h1>
<a href="#" title="Close" class="close-button" {{action "close"}}>{{partial "close-button"}}</a>
{{#if App.Manifest.bugTrackers}}
- <a href="#" title="Bugs and Analysis" class="bugs-button" {{action "toggleBugsPane"}}>
- {{partial "bugs-button"}}
+ <a href="#" title="Analysis" class="bugs-button" {{action "toggleBugsPane"}}>
+ {{partial "analysis-button"}}
</a>
{{/if}}
{{#if App.Manifest.repositoriesWithReportedCommits}}
{{#if chartData}}
{{interactive-chart
chartData=chartData
+ ranges=analyticRanges
domain=mainPlotDomain
interactive=true
chartPointRadius=2
currentTime=sharedTime
selectedItem=selectedItem
highlightedItems=highlightedItems
+ rangeRoute="analysisTask"
selection=timeRange
sharedSelection=sharedSelection
selectionChanged="rangeChanged"
{{input action="searchCommit" placeholder="Name or email" value=commitSearchKeyword}}
</form>
- <div {{bind-attr class=":bugs-pane showingBugsPane::hidden"}}>
+ <div {{bind-attr class=":bugs-pane showingAnalysisPane::hidden"}}>
<table>
<tbody>
- {{#if selectedSinglePoint}}
- {{#each details.bugTrackers}}
- <tr>
- <th>{{label}}</th>
- <td>
- <form {{action "associateBug" this editedBugNumber on="submit"}}>
- {{input type=text value=editedBugNumber}}
- </form>
- </td>
- </tr>
- {{/each}}
- {{/if}}
<tr>
<th>
<label>Name: {{input type=text value=newAnalysisTaskName}}</label>
- <button {{action "createAnalysisTask"}}>Analyze</button>
+ <button {{action "createAnalysisTask"}} {{bind-attr disabled=cannotAnalyze}}>Analyze</button>
</th>
</tr>
</tbody>
</a>
</div>
{{/if}}
+ <div class="rangeBarsContainerInlineStyle">
+ {{#each rangeBars}}
+ {{#link-to linkRoute linkId}}
+ <span class="rangeBar" {{bind-attr style=inlineStyle}}></span>
+ {{/link-to}}
+ {{/each}}
+ </div>
</script>
<script type="text/x-handlebars" data-template-name="chart-details">
</svg>
</script>
- <script type="text/x-handlebars" data-template-name="bugs-button">
- <svg class="bugs-button icon-button" viewBox="0 0 100 100">
+ <script type="text/x-handlebars" data-template-name="analysis-button">
+ <svg class="analysis-button icon-button" viewBox="0 0 100 100">
<g stroke="black" stroke-width="15">
<circle cx="50" cy="50" r="40" fill="transparent"/>
<line x1="50" y1="25" x2="50" y2="55"/>