Perf dashboard should have UI to set status on analysis tasks
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Apr 2015 02:48:22 +0000 (02:48 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Apr 2015 02:48:22 +0000 (02:48 +0000)
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

Websites/perf.webkit.org/ChangeLog
Websites/perf.webkit.org/init-database.sql
Websites/perf.webkit.org/public/api/analysis-tasks.php
Websites/perf.webkit.org/public/include/db.php
Websites/perf.webkit.org/public/include/json-header.php
Websites/perf.webkit.org/public/privileged-api/update-analysis-task.php [new file with mode: 0644]
Websites/perf.webkit.org/public/v2/analysis.js
Websites/perf.webkit.org/public/v2/app.css
Websites/perf.webkit.org/public/v2/app.js
Websites/perf.webkit.org/public/v2/index.html

index 5c55a80..b6aa8c4 100644 (file)
@@ -1,3 +1,39 @@
+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.
index 7e859c0..efb5247 100644 (file)
@@ -174,6 +174,7 @@ CREATE TABLE reports (
     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,
@@ -183,6 +184,8 @@ CREATE TABLE analysis_tasks (
     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)));
index a31b067..e7fe6b2 100644 (file)
@@ -86,6 +86,8 @@ function format_task($task_row) {
         '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(),
     );
 }
index 23ab2ca..209f115 100644 (file)
@@ -64,6 +64,10 @@ class Database
         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, '.');
index e77f106..a58e816 100644 (file)
@@ -56,6 +56,11 @@ function require_format($name, $value, $pattern) {
         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 .= '_';
diff --git a/Websites/perf.webkit.org/public/privileged-api/update-analysis-task.php b/Websites/perf.webkit.org/public/privileged-api/update-analysis-task.php
new file mode 100644 (file)
index 0000000..e0b0643
--- /dev/null
@@ -0,0 +1,49 @@
+<?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();
+
+?>
index 99a2907..a09e6b3 100644 (file)
@@ -12,10 +12,23 @@ App.AnalysisTask = App.NameLabelModel.extend({
     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)
index 5a46f81..12334bd 100755 (executable)
@@ -570,7 +570,23 @@ form .analysis-group > * {
     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;
 }
index d2787fa..835aeff 100755 (executable)
@@ -1146,6 +1146,32 @@ App.AnalysisTaskController = Ember.Controller.extend({
     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');
@@ -1286,6 +1312,24 @@ App.AnalysisTaskController = Ember.Controller.extend({
                     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');
index bc800a7..8c1fac1 100755 (executable)
                 </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>