Make the analysis page more useful
[WebKit-https.git] / Websites / perf.webkit.org / public / v2 / analysis.js
1 App.AnalysisTask = App.NameLabelModel.extend({
2     author: DS.attr('string'),
3     createdAt: DS.attr('date'),
4     formattedCreatedAt: function () {
5         var format = d3.time.format("%Y-%m-%d");
6         return format(this.get('createdAt'));
7     }.property('createdAt'),
8     platform: DS.belongsTo('platform'),
9     metric: DS.belongsTo('metric'),
10     startRun: DS.attr('number'),
11     endRun: DS.attr('number'),
12     bugs: DS.hasMany('bugs'),
13     buildRequestCount: DS.attr('number'),
14     finishedBuildRequestCount: DS.attr('number'),
15     statusLabel: function ()
16     {
17         var total = this.get('buildRequestCount');
18         var finished = this.get('finishedBuildRequestCount');
19         if (!total)
20             return 'Empty';
21         if (total != finished)
22             return finished + ' out of ' + total;
23         return 'Done';
24     }.property('buildRequestCount', 'finishedBuildRequestCount'),
25     testGroups: function (key, value, oldValue)
26     {
27         return this.store.find('testGroup', {task: this.get('id')});
28     }.property(),
29     triggerable: function ()
30     {
31         return this.store.find('triggerable', {task: this.get('id')}).then(function (triggerables) {
32             return triggerables.objectAt(0);
33         }, function (error) {
34             console.log('Failed to fetch triggerables', error);
35             return null;
36         });
37     }.property(),
38     label: function ()
39     {
40         var label = this.get('name');
41         var bugs = this.get('bugs').map(function (bug) { return bug.get('label'); }).join(' / ');
42         return bugs ? label + ' (' + bugs + ')' : label;
43     }.property('name', 'bugs'),
44 });
45
46 App.Bug = App.Model.extend({
47     task: DS.belongsTo('AnalysisTask'),
48     bugTracker: DS.belongsTo('BugTracker'),
49     createdAt: DS.attr('date'),
50     number: DS.attr('number'),
51     label: function () {
52         return this.get('bugTracker').get('label') + ': ' + this.get('number');
53     }.property('name', 'bugTracker'),
54 });
55
56 // FIXME: Use DS.RESTAdapter instead.
57 App.AnalysisTask.create = function (name, startMeasurement, endMeasurement)
58 {
59     return PrivilegedAPI.sendRequest('create-analysis-task', {
60         name: name,
61         startRun: startMeasurement.id(),
62         endRun: endMeasurement.id(),
63     });
64 }
65
66 App.AnalysisTaskAdapter = DS.RESTAdapter.extend({
67     buildURL: function (type, id)
68     {
69         return '../api/analysis-tasks/' + (id ? id : '');
70     },
71 });
72
73 App.BugAdapter = DS.RESTAdapter.extend({
74     createRecord: function (store, type, record)
75     {
76         var param = {
77             task: record.get('task').get('id'),
78             bugTracker: record.get('bugTracker').get('id'),
79             number: record.get('number'),
80         };
81         return PrivilegedAPI.sendRequest('associate-bug', param).then(function (data) {
82             param['id'] = data['bugId'];
83             return {'bug': param};
84         });
85     }
86 });
87
88 App.Root = App.Model.extend({
89     repository: DS.belongsTo('repository'),
90     revision: DS.attr('string'),
91 });
92
93 App.RootSet = App.Model.extend({
94     roots: DS.hasMany('roots'),
95     revisionForRepository: function (repository)
96     {
97         var root = this.get('roots').findBy('repository', repository);
98         if (!root)
99             return null;
100         return root.get('revision');
101     }
102 });
103
104 App.TestGroup = App.NameLabelModel.extend({
105     task: DS.belongsTo('analysisTask'),
106     author: DS.attr('string'),
107     createdAt: DS.attr('date'),
108     buildRequests: DS.hasMany('buildRequests'),
109     rootSets: DS.hasMany('rootSets'),
110     _fetchTestResults: function ()
111     {
112         var task = this.get('task');
113         if (!task)
114             return null;
115         var self = this;
116         return App.Manifest.fetchRunsWithPlatformAndMetric(this.store,
117             task.get('platform').get('id'), task.get('metric').get('id'), this.get('id')).then(
118             function (result) { self.set('testResults', result.data); },
119             function (error) {
120                 // FIXME: Somehow this never gets called.
121                 alert('Failed to fetch the results:' + error);
122                 return null;
123             });
124     }.observes('task', 'task.platform', 'task.metric').on('init'),
125 });
126
127 App.TestGroup.create = function (analysisTask, name, rootSets, repetitionCount)
128 {
129     var param = {
130         task: analysisTask.get('id'),
131         name: name,
132         rootSets: rootSets,
133         repetitionCount: repetitionCount,
134     };
135     return PrivilegedAPI.sendRequest('create-test-group', param).then(function (data) {
136         analysisTask.set('testGroups'); // Refetch test groups.
137         return analysisTask.store.find('testGroup', data['testGroupId']);
138     });
139 }
140
141 App.TestGroupAdapter = DS.RESTAdapter.extend({
142     buildURL: function (type, id)
143     {
144         return '../api/test-groups/' + (id ? id : '');
145     },
146 });
147
148 App.Triggerable = App.NameLabelModel.extend({
149     acceptedRepositories: DS.hasMany('repositories'),
150 });
151
152 App.TriggerableAdapter = DS.RESTAdapter.extend({
153     buildURL: function (type, id)
154     {
155         return '../api/triggerables/' + (id ? id : '');
156     },
157 });
158
159 App.AnalysisTaskSerializer = App.TestGroupSerializer = App.TriggerableSerializer = DS.RESTSerializer.extend({
160     normalizePayload: function (payload)
161     {
162         delete payload['status'];
163         return payload;
164     }
165 });
166
167 App.BuildRequest = App.Model.extend({
168     testGroup: DS.belongsTo('testGroup'),
169     order: DS.attr('number'),
170     orderLabel: function ()
171     {
172         return this.get('order') + 1;
173     }.property('order'),
174     rootSet: DS.belongsTo('rootSet'),
175     status: DS.attr('string'),
176     statusLabel: function ()
177     {
178         switch (this.get('status')) {
179         case 'pending':
180             return 'Waiting to be scheduled';
181         case 'scheduled':
182             return 'Scheduled';
183         case 'running':
184             return 'Running';
185         case 'failed':
186             return 'Failed';
187         case 'completed':
188             return 'Completed';
189         }
190     }.property('status'),
191     url: DS.attr('string'),
192     build: DS.attr('number'),
193 });
194
195 App.BuildRequest.aggregateStatuses = function (requests)
196 {
197     var completeCount = 0;
198     var failureCount = 0;
199     requests.forEach(function (request) {
200         switch (request.get('status')) {
201         case 'failed':
202             failureCount++;
203             break;
204         case 'completed':
205             completeCount++;
206             break;
207         }
208     });
209     if (completeCount == requests.length)
210         return 'Done';
211     if (failureCount == requests.length)
212         return 'All failed';
213     var status = completeCount + ' out of ' + requests.length + ' completed';
214     if (failureCount)
215         status += ', ' + failureCount + ' failed';
216     return status;
217 }