https://bugs.webkit.org/show_bug.cgi?id=138977
Reviewed by Benjamin Poulain.
Updated associate-bug.php to match the new database schema.
* public/include/json-header.php:
(require_format): Removed the call to camel_case_words_separated_by_underscore since the name is
already camel-cased in require_existence_of. This makes the function usable elsewhere.
* public/privileged-api/associate-bug.php:
(main): Changed the API to take run, bugTracker, and number to match the new database schema.
Also verify that those values are integers using require_format.
* public/v2/analysis.js:
(App.AnalysisTask.label): Added. Concatenates the task's name with the bug numbers.
(App.Bug.label): Added.
(App.BugAdapter): Added.
(App.BugAdapter.createRecord): Use PrivilegedAPI instead of the builtin ajax call.
(App.BuildRequest): Inherit from newly added App.Model, which is set to DS.Model right now.
* public/v2/app.css: Renamed .test-groups to .analysis-group. Also added new rules for the table
containing the bug information.
* public/v2/app.js:
(App.InteractiveChartComponent._rangesChanged): Added label to range bar objects.
(App.AnalysisTaskRoute):
(App.AnalysisTaskController): Replaced the functionality of App.AnalysisTaskViewModel.
(App.AnalysisTaskController._fetchedManifest): Added.
(App.AnalysisTaskController.actions.associateBug): Added.
* public/v2/chart-pane.css: Renamed .bugs-pane to .analysis-pane.
* public/v2/data.js:
(Measurement.prototype.associateBug): Deleted.
* public/v2/index.html: Renamed .bugs-pane to .analysis-pane and .test-groups to .analysis-group.
Added a table show the bug information. Also hide the chart until chartData is available.
* public/v2/manifest.js:
(App.Model): Added.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@176493
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2014-11-21 Ryosuke Niwa <rniwa@webkit.org>
+
+ There should be a way to associate bugs with analysis tasks
+ https://bugs.webkit.org/show_bug.cgi?id=138977
+
+ Reviewed by Benjamin Poulain.
+
+ Updated associate-bug.php to match the new database schema.
+
+ * public/include/json-header.php:
+ (require_format): Removed the call to camel_case_words_separated_by_underscore since the name is
+ already camel-cased in require_existence_of. This makes the function usable elsewhere.
+
+ * public/privileged-api/associate-bug.php:
+ (main): Changed the API to take run, bugTracker, and number to match the new database schema.
+ Also verify that those values are integers using require_format.
+
+ * public/v2/analysis.js:
+ (App.AnalysisTask.label): Added. Concatenates the task's name with the bug numbers.
+ (App.Bug.label): Added.
+ (App.BugAdapter): Added.
+ (App.BugAdapter.createRecord): Use PrivilegedAPI instead of the builtin ajax call.
+ (App.BuildRequest): Inherit from newly added App.Model, which is set to DS.Model right now.
+
+ * public/v2/app.css: Renamed .test-groups to .analysis-group. Also added new rules for the table
+ containing the bug information.
+
+ * public/v2/app.js:
+ (App.InteractiveChartComponent._rangesChanged): Added label to range bar objects.
+ (App.AnalysisTaskRoute):
+ (App.AnalysisTaskController): Replaced the functionality of App.AnalysisTaskViewModel.
+ (App.AnalysisTaskController._fetchedManifest): Added.
+ (App.AnalysisTaskController.actions.associateBug): Added.
+
+ * public/v2/chart-pane.css: Renamed .bugs-pane to .analysis-pane.
+
+ * public/v2/data.js:
+ (Measurement.prototype.associateBug): Deleted.
+
+ * public/v2/index.html: Renamed .bugs-pane to .analysis-pane and .test-groups to .analysis-group.
+ Added a table show the bug information. Also hide the chart until chartData is available.
+
+ * public/v2/manifest.js:
+ (App.Model): Added.
+
2014-11-20 Ryosuke Niwa <rniwa@webkit.org>
Fix misc bugs and typos in app.js
return implode('', array_map('ucfirst', explode('_', $name)));
}
-function require_format($key, $value, $pattern) {
+function require_format($name, $value, $pattern) {
if (!preg_match($pattern, $value))
- exit_with_error('Invalid' . camel_case_words_separated_by_underscore($key), array('value' => $value));
+ exit_with_error('Invalid' . $name, array('value' => $value));
}
function require_existence_of($array, $list_of_arguments, $prefix = '') {
function main() {
$data = ensure_privileged_api_data_and_token();
- $run_id = array_get($data, 'run');
- $bug_tracker_id = array_get($data, 'tracker');
- $bug_number = array_get($data, 'bugNumber');
+ $analysis_task_id = array_get($data, 'task');
+ $bug_tracker_id = array_get($data, 'bugTracker');
+ $bug_number = array_get($data, 'number');
- if (!$run_id)
- exit_with_error('InvalidRunId', array('run' => $run_id));
- if (!$bug_tracker_id)
- exit_with_error('InvalidBugTrackerId', array('tracker' => $bug_tracker_id));
+ require_format('AnalysisTask', $analysis_task_id, '/^\d+$/');
+ require_format('BugTracker', $bug_tracker_id, '/^\d+$/');
+ require_format('BugNumber', $bug_number, '/^\d*$/');
$db = connect();
$db->begin_transaction();
$bug_id = NULL;
if (!$bug_number) {
- $count = $db->query_and_get_affected_rows("DELETE FROM bugs WHERE bug_run = $1 AND bug_tracker = $2",
- array($run_id, $bug_tracker_id));
+ $count = $db->query_and_get_affected_rows("DELETE FROM bugs WHERE bug_task = $1 AND bug_tracker = $2",
+ array($analysis_task_id, $bug_tracker_id));
if ($count > 1) {
$db->rollback_transaction();
exit_with_error('UnexpectedNumberOfAffectedRows', array('affectedRows' => $count));
}
} else {
- $bug_id = $db->update_or_insert_row('bugs', 'bug', array('run' => $run_id, 'tracker' => $bug_tracker_id),
- array('run' => $run_id, 'tracker' => $bug_tracker_id, 'number' => $bug_number));
+ $bug_id = $db->update_or_insert_row('bugs', 'bug', array('task' => $analysis_task_id, 'tracker' => $bug_tracker_id),
+ array('task' => $analysis_task_id, 'tracker' => $bug_tracker_id, 'number' => $bug_number));
}
$db->commit_transaction();
testGroups: function () {
return this.store.find('testGroup', {task: this.get('id')});
}.property(),
+ label: function () {
+ var label = this.get('name');
+ var bugs = this.get('bugs').map(function (bug) { return bug.get('label'); }).join(' / ');
+ return bugs ? label + ' (' + bugs + ')' : label;
+ }.property('name', 'bugs'),
});
-App.Bug = App.NameLabelModel.extend({
+App.Bug = App.Model.extend({
task: DS.belongsTo('AnalysisTask'),
bugTracker: DS.belongsTo('BugTracker'),
createdAt: DS.attr('date'),
number: DS.attr('number'),
+ label: function () {
+ return this.get('bugTracker').get('label') + ': ' + this.get('number');
+ }.property('name', 'bugTracker'),
});
// FIXME: Use DS.RESTAdapter instead.
},
});
+App.BugAdapter = DS.RESTAdapter.extend({
+ createRecord: function (store, type, record)
+ {
+ var param = {
+ task: record.get('task').get('id'),
+ bugTracker: record.get('bugTracker').get('id'),
+ number: record.get('number'),
+ };
+ return PrivilegedAPI.sendRequest('associate-bug', param).then(function (data) {
+ param['id'] = data['bugId'];
+ return {'bug': param};
+ });
+ }
+});
+
App.TestGroup = App.NameLabelModel.extend({
analysisTask: DS.belongsTo('analysisTask'),
author: DS.attr('string'),
}
});
-App.BuildRequest = DS.Model.extend({
+App.BuildRequest = App.Model.extend({
group: DS.belongsTo('testGroup'),
order: DS.attr('number'),
rootSet: DS.attr('number'),
}
#analysis-tasks,
-.test-groups > table {
+.analysis-group > table {
border: solid 0px #999;
border-collapse: collapse;
}
#analysis-tasks thead,
-.test-groups > table thead {
+.analysis-group > table thead {
color: #c93;
}
#analysis-tasks th,
-.test-groups > table th {
+.analysis-group > table th {
font-weight: normal;
}
#analysis-tasks td,
#analysis-tasks th,
-.test-groups > table td,
-.test-groups > table th {
+.analysis-group > table td,
+.analysis-group > table th {
padding: 0.2rem 0.5rem;
}
#analysis-tasks tbody td,
#analysis-tasks tbody th,
-.test-groups > table tbody td,
-.test-groups > table tbody th {
+.analysis-group > table tbody td,
+.analysis-group > table tbody th {
border-top: solid 1px #ddd;
}
color: #333;
}
-.test-groups {
+.analysis-group {
border: 1px solid #bbb;
border-radius: 0.5rem;
box-shadow: rgba(0, 0, 0, 0.03) 1px 1px 0px 0px;
margin-bottom: 1.5rem;
}
-.test-groups caption {
+.analysis-group caption {
font-size: 1.1rem;
text-align: left;
margin-bottom: 0.5rem;
}
+
+.analysis-bugs th {
+ font-weight: normal;
+ text-align: right;
+}
bottom: null,
linkRoute: linkRoute,
linkId: range.get('id'),
+ label: range.get('label'),
});
}));
});
App.AnalysisTaskRoute = Ember.Route.extend({
- model: function (param) {
- return this.store.find('analysisTask', param.taskId).then(function (task) {
- return App.AnalysisTaskViewModel.create({content: task, store: store});
- });
+ model: function (param)
+ {
+ return this.store.find('analysisTask', param.taskId);
},
});
-App.AnalysisTaskViewModel = Ember.ObjectProxy.extend({
+App.AnalysisTaskController = Ember.Controller.extend({
+ label: Ember.computed.alias('model.name'),
+ platform: Ember.computed.alias('model.platform'),
+ metric: Ember.computed.alias('model.metric'),
testSets: [],
roots: [],
+ bugTrackers: [],
_taskUpdated: function ()
{
- var platformId = this.get('platform').get('id');
- var metricId = this.get('metric').get('id');
- App.Manifest.fetchRunsWithPlatformAndMetric(this.get('store'), platformId, metricId).then(this._fetchedRuns.bind(this));
- }.observes('platform', 'metric').on('init'),
+ var model = this.get('model');
+ if (!model)
+ return;
+
+ var platformId = model.get('platform').get('id');
+ var metricId = model.get('metric').get('id');
+ App.Manifest.fetch(this.store).then(this._fetchedManifest.bind(this));
+ App.Manifest.fetchRunsWithPlatformAndMetric(this.store, platformId, metricId).then(this._fetchedRuns.bind(this));
+ }.observes('model').on('init'),
+ _fetchedManifest: function ()
+ {
+ var trackerIdToBugNumber = {};
+ this.get('model').get('bugs').forEach(function (bug) {
+ trackerIdToBugNumber[bug.get('bugTracker').get('id')] = bug.get('number');
+ });
+
+ this.set('bugTrackers', App.Manifest.get('bugTrackers').map(function (bugTracker) {
+ var bugNumber = trackerIdToBugNumber[bugTracker.get('id')];
+ return Ember.ObjectProxy.create({
+ content: bugTracker,
+ bugNumber: bugNumber,
+ editedBugNumber: bugNumber,
+ });
+ }));
+ },
_fetchedRuns: function (data) {
var runs = data.runs;
if (!currentTimeSeries)
return; // FIXME: Report an error.
- var start = currentTimeSeries.findPointByMeasurementId(this.get('startRun'));
- var end = currentTimeSeries.findPointByMeasurementId(this.get('endRun'));
+ var start = currentTimeSeries.findPointByMeasurementId(this.get('model').get('startRun'));
+ var end = currentTimeSeries.findPointByMeasurementId(this.get('model').get('endRun'));
if (!start || !end)
return; // FIXME: Report an error.
}
return roots;
}.property('analysisPoints'),
+ actions: {
+ associateBug: function (bugTracker, bugNumber)
+ {
+ var model = this.get('model');
+ this.store.createRecord('bug',
+ {task: this.get('model'), bugTracker: bugTracker.get('content'), number: bugNumber}).save().then(function () {
+ // FIXME: Should we notify the user?
+ }, function (error) {
+ alert('Failed to associate the bug: ' + error);
+ });
+ }
+ },
});
top: 0.55rem;
}
-.search-pane, .bugs-pane {
+.search-pane, .analysis-pane {
position: absolute;
top: 1.7rem;
border: 1px solid #bbb;
background: white;
}
-.bugs-pane {
+.analysis-pane {
right: 1.3rem;
}
-.bugs-pane table {
+.analysis-pane table {
margin: 0.2rem;
font-size: 0.8rem;
}
-.bugs-pane th {
+.analysis-pane th {
font-weight: normal;
}
right: 0rem;
}
-.bugs-pane.hidden,
+.analysis-pane.hidden,
.search-pane.hidden {
display: none;
}
return bugs && Object.keys(bugs).length;
}
-Measurement.prototype.associateBug = function (trackerId, bugNumber)
-{
- var bugs = this._raw['bugs'];
- trackerId = parseInt(trackerId);
- bugNumber = bugNumber ? parseInt(bugNumber) : null;
- return PrivilegedAPI.sendRequest('associate-bug', {
- run: this.id(),
- tracker: trackerId,
- bugNumber: bugNumber,
- }).then(function () {
- if (bugNumber)
- bugs[trackerId] = bugNumber;
- else
- delete bugs[trackerId];
- });
-}
-
function RunsData(rawData)
{
this._measurements = rawData.map(function (run) { return new Measurement(run); });
{{input action="searchCommit" placeholder="Name or email" value=commitSearchKeyword}}
</form>
- <div {{bind-attr class=":bugs-pane showingAnalysisPane::hidden"}}>
+ <div {{bind-attr class=":analysis-pane showingAnalysisPane::hidden"}}>
<table>
<tbody>
<tr>
{{/if}}
<div class="rangeBarsContainerInlineStyle">
{{#each rangeBars}}
- {{#link-to linkRoute linkId}}
+ {{#link-to linkRoute linkId title=label}}
<span class="rangeBar" {{bind-attr style=inlineStyle}}></span>
{{/link-to}}
{{/each}}
{{partial "navbar"}}
</header>
- <h2 id="analysis-task-title">{{name}}</h2>
+ <h2 id="analysis-task-title">{{label}}</h2>
{{#if platform.label}}
<h3 id="analysis-task-testname">{{metric.fullName}} - {{platform.label}}</h3>
+ {{/if}}
+ {{#if chartData}}
<section class="analysis-chart-pane chart-pane">
<div class="svg-container">
{{interactive-chart
markedPoints=markedPoints}}
</div>
<div class="details">
+ <table class="analysis-bugs">
+ <tbody>
+ {{#each bugTrackers}}
+ <tr>
+ <th>{{label}}</th>
+ <td>
+ <form {{action "associateBug" this editedBugNumber on="submit"}}>
+ {{input type=text value=editedBugNumber}}
+ </form>
+ </td>
+ </tr>
+ {{/each}}
+ </tbody>
+ </table>
<table>
<tbody>
{{#each analysisPoints}}
</section>
{{#each testGroups}}
- <section class="test-groups">
+ <section class="analysis-group">
<table>
<caption>{{name}}</caption>
<thead>
</section>
{{/each}}
- <form class="test-groups">
+ <form class="analysis-group">
<table>
<caption><input name="name" placeholder="Test group name" required></caption>
<thead>
+App.Model = DS.Model;
+
App.NameLabelModel = DS.Model.extend({
name: DS.attr('string'),
label: function ()