https://bugs.webkit.org/show_bug.cgi?id=143977
Reviewed by Chris Dumez.
Added the UI to set the result of an analysis task to 'progression', 'regression', 'unchanged', and 'inconclusive'
as well as a boolean indicating whether creating the analysis task was the right thing to do or not.
The latter will be a useful metric once we start automatically creating analysis tasks.
* init-database.sql: Added two columns to analysis_tasks table.
* public/api/analysis-tasks.php: Include the added columns in the JSON.
* public/include/db.php:
(Database::to_database_boolean): Added.
* public/include/json-header.php:
(require_match_one_of_values): Added.
* public/privileged-api/update-analysis-task.php: Added. Updates 'result' and 'needed' values of an analysis task.
(main):
* public/v2/analysis.js:
(App.AnalysisTask.result): Added.
(App.AnalysisTask.needed): Added. We don't use DS.attr('boolean') here since that would coerce null into false
and we want to differentiate null from false in order to differentiate the null-ness of the value.
(App.AnalysisTask.saveStatus): Added.
(App.AnalysisTask.statusLabel): Use 'result' as the label if it's set and all build requests have been processed.
* public/v2/app.css:
* public/v2/app.js:
(App.AnalysisTaskController.analysisResultOptions): Added.
(App.AnalysisTaskController.shouldNotHaveBeenCreated): Added.
(App.AnalysisTaskController.needsFeedback): Added. Show the checkbox to indicate the analysis task should not have
been created if 'no change' is selected.
(App.AnalysisTaskController._updateChosenAnalysisResult): Added.
(App.AnalysisTaskController.actions.saveStatus): Added.
* public/v2/index.html: Extracted a partial template for updating the bug numbers. Also added the UI to update
'result' and 'needed' values of the analysis task.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@183041
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2015-04-20 Ryosuke Niwa <rniwa@webkit.org>
+
+ Perf dashboard should have UI to set status on analysis tasks
+ https://bugs.webkit.org/show_bug.cgi?id=143977
+
+ Reviewed by Chris Dumez.
+
+ Added the UI to set the result of an analysis task to 'progression', 'regression', 'unchanged', and 'inconclusive'
+ as well as a boolean indicating whether creating the analysis task was the right thing to do or not.
+ The latter will be a useful metric once we start automatically creating analysis tasks.
+
+ * init-database.sql: Added two columns to analysis_tasks table.
+ * public/api/analysis-tasks.php: Include the added columns in the JSON.
+ * public/include/db.php:
+ (Database::to_database_boolean): Added.
+ * public/include/json-header.php:
+ (require_match_one_of_values): Added.
+ * public/privileged-api/update-analysis-task.php: Added. Updates 'result' and 'needed' values of an analysis task.
+ (main):
+ * public/v2/analysis.js:
+ (App.AnalysisTask.result): Added.
+ (App.AnalysisTask.needed): Added. We don't use DS.attr('boolean') here since that would coerce null into false
+ and we want to differentiate null from false in order to differentiate the null-ness of the value.
+ (App.AnalysisTask.saveStatus): Added.
+ (App.AnalysisTask.statusLabel): Use 'result' as the label if it's set and all build requests have been processed.
+ * public/v2/app.css:
+ * public/v2/app.js:
+ (App.AnalysisTaskController.analysisResultOptions): Added.
+ (App.AnalysisTaskController.shouldNotHaveBeenCreated): Added.
+ (App.AnalysisTaskController.needsFeedback): Added. Show the checkbox to indicate the analysis task should not have
+ been created if 'no change' is selected.
+ (App.AnalysisTaskController._updateChosenAnalysisResult): Added.
+ (App.AnalysisTaskController.actions.saveStatus): Added.
+ * public/v2/index.html: Extracted a partial template for updating the bug numbers. Also added the UI to update
+ 'result' and 'needed' values of the analysis task.
+
2015-04-10 Ryosuke Niwa <rniwa@webkit.org>
Unreviewed build fix. Updated config.json after recent changes.
report_failure varchar(64),
report_failure_details text);
+CREATE TYPE analysis_task_result_type as ENUM ('progression', 'regression', 'unchanged', 'inconclusive');
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,
+ task_result analysis_task_result_type,
+ task_needed boolean,
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)));
'metric' => $task_row['task_metric'],
'startRun' => $task_row['task_start_run'],
'endRun' => $task_row['task_end_run'],
+ 'result' => $task_row['task_result'],
+ 'needed' => $task_row['task_needed'] ? Database::is_true($task_row['task_needed']) : null,
'bugs' => array(),
);
}
return $value == 't';
}
+ static function to_database_boolean($value) {
+ return $value ? 't' : 'f';
+ }
+
static function to_js_time($time_str) {
$timestamp_in_s = strtotime($time_str);
$dot_index = strrpos($time_str, '.');
exit_with_error('Invalid' . $name, array('value' => $value));
}
+function require_match_one_of_values($name, $value, $valid_values) {
+ if (!in_array($value, $valid_values))
+ exit_with_error('Invalid' . $name, array('value' => $value));
+}
+
function require_existence_of($array, $list_of_arguments, $prefix = '') {
if ($prefix)
$prefix .= '_';
--- /dev/null
+<?php
+
+require_once('../include/json-header.php');
+
+function main() {
+ $data = ensure_privileged_api_data_and_token();
+
+ $analysis_task_id = array_get($data, 'task');
+ if (!$analysis_task_id)
+ exit_with_error('AnalysisTaskNotSpecified');
+
+ $values = array();
+
+ if (array_key_exists('result', $data)) {
+ require_match_one_of_values('Result', $data['result'], array(null, 'progression', 'regression', 'unchanged', 'inconclusive'));
+ $values['result'] = $data['result'];
+ }
+
+ if (array_key_exists('needed', $data)) {
+ $needed = $data['needed'];
+ if ($needed === null)
+ $values['needed'] = null;
+ else if (in_array($needed, array(0, false)))
+ $values['needed'] = Database::to_database_boolean(false);
+ else if (in_array($needed, array(1, true)))
+ $values['needed'] = Database::to_database_boolean(true);
+ else
+ exit_with_error('InvalidValueForFeedback', array('value' => $data['needed']));
+ }
+
+ if (!$values)
+ exit_with_error('NothingToUpdate');
+
+ $db = connect();
+ $db->begin_transaction();
+
+ if (!$db->update_row('analysis_tasks', 'task', array('id' => $analysis_task_id), $values)) {
+ $db->rollback_transaction();
+ exit_with_error('FailedToUpdateTask', array('id' => $analysis_task_id, 'values' => $values));
+ }
+
+ $db->commit_transaction();
+
+ exit_with_success();
+}
+
+main();
+
+?>
bugs: DS.hasMany('bugs'),
buildRequestCount: DS.attr('number'),
finishedBuildRequestCount: DS.attr('number'),
+ result: DS.attr('string'),
+ needed: DS.attr('number'), // DS.attr('boolean') treats null as false.
+ saveStatus: function ()
+ {
+ return PrivilegedAPI.sendRequest('update-analysis-task', {
+ task: this.get('id'),
+ result: this.get('result'),
+ needed: this.get('needed'),
+ });
+ },
statusLabel: function ()
{
var total = this.get('buildRequestCount');
var finished = this.get('finishedBuildRequestCount');
+ var result = this.get('result');
+ if (result && total == finished)
+ return result.capitalize();
if (!total)
return 'Empty';
if (total != finished)
margin: 0.2rem 0;
}
+.analysis-bugs {
+ margin: 0;
+ padding: 0;
+}
+
.analysis-bugs th {
font-weight: normal;
text-align: right;
+ width: 8rem;
+}
+
+.analysis-bugs td {
+ font-weight: normal;
+ text-align: left;
+ font-size: 0.9rem;
+}
+
+.analysis-bugs .hidden {
+ display: none;
}
roots: [],
bugTrackers: [],
possibleRepetitionCounts: [1, 2, 3, 4, 5, 6],
+ analysisResultOptions: [
+ {label: 'Still in investigation', result: null},
+ {label: 'Inconclusive', result: 'inconclusive', needed: true},
+ {label: 'Definite progression', result: 'progression', needed: true},
+ {label: 'Definite regression', result: 'regression', needed: true},
+ {label: 'No change', result: 'unchanged', needsFeedback: true},
+ ],
+ shouldNotHaveBeenCreated: false,
+ needsFeedback: function ()
+ {
+ var chosen = this.get('chosenAnalysisResult');
+ return chosen && chosen.needsFeedback;
+ }.property('chosenAnalysisResult'),
+ _updateChosenAnalysisResult: function ()
+ {
+ var analysisTask = this.get('model');
+ if (!analysisTask)
+ return;
+ var currentResult = analysisTask.get('result');
+ for (var option of this.analysisResultOptions) {
+ if (option.result == currentResult) {
+ this.set('chosenAnalysisResult', option);
+ break;
+ }
+ }
+ }.observes('model'),
_taskUpdated: function ()
{
var model = this.get('model');
alert('Failed to associate the bug: ' + error);
});
},
+ saveStatus: function ()
+ {
+ var chosenResult = this.get('chosenAnalysisResult');
+ var analysisTask = this.get('model');
+ analysisTask.set('result', chosenResult.result);
+ if (chosenResult.needed)
+ analysisTask.set('needed', true);
+ else if (chosenResult.needsFeedback && this.get('notNeeded'))
+ analysisTask.set('needed', false);
+ else
+ analysisTask.set('needed', null);
+
+ analysisTask.saveStatus().then(function () {
+ alert('Saved the status');
+ }, function (error) {
+ alert('Failed to save the status: ' + error);
+ });
+ },
createTestGroup: function (name, repetitionCount)
{
var analysisTask = this.get('model');
</div>
<div class="details">
<div class="details-table-container">
- <table class="analysis-bugs">
- <tbody>
- {{#each bugTrackers}}
- <tr>
- <th><label {{bind-attr for=elementId}}>{{label}}</label></th>
- <td>
- <form {{action "associateBug" this editedBugNumber on="submit"}}>
- {{input id=elementId type=text value=editedBugNumber}}
- </form>
- </td>
- </tr>
- {{/each}}
- </tbody>
- </table>
- {{partial "chart-details"}}
+ {{#if details}}
+ {{partial "chart-details"}}
+ {{else}}
+ {{partial "analysisStatusForm"}}
+ {{/if}}
</div>
</div>
</section>
{{/if}}
</script>
+ <script type="text/x-handlebars" data-template-name="analysisStatusForm">
+ <table class="analysis-bugs">
+ <tbody>
+ {{#each bugTrackers}}
+ <tr>
+ <th><label {{bind-attr for=elementId}}>{{label}}</label></th>
+ <td>
+ <form {{action "associateBug" this editedBugNumber on="submit"}}>
+ {{input id=elementId type=text value=editedBugNumber}}
+ </form>
+ </td>
+ </tr>
+ {{/each}}
+ </tbody>
+ <tbody>
+ <tr>
+ <th><label for="analysis-status">Status<label></th>
+ <td>
+ <form class="analysis-bugs" {{action "saveStatus" on="submit"}}>
+ {{view Ember.Select id="analysis-status" content=analysisResultOptions optionValuePath="content"
+ optionLabelPath="content.label" selection=chosenAnalysisResult}}
+ <input type="submit" value="Save"><br>
+ <label {{bind-attr class="needsFeedback::hidden"}}>{{input type=checkbox checked=notNeeded}} This should not have been created</label>
+ </form>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </script>
+
</head>
<body>
</body>