An analysis task should be closed if a progression cause is identified
[WebKit.git] / Websites / perf.webkit.org / public / v3 / models / analysis-task.js
1
2 class AnalysisTask extends LabeledObject {
3     constructor(id, object)
4     {
5         super(id, object);
6         this._author = object.author;
7         this._createdAt = object.createdAt;
8
9         console.assert(object.platform instanceof Platform);
10         this._platform = object.platform;
11
12         console.assert(object.metric instanceof Metric);
13         this._metric = object.metric;
14
15         this._startMeasurementId = object.startRun;
16         this._startTime = object.startRunTime;
17         this._endMeasurementId = object.endRun;
18         this._endTime = object.endRunTime;
19         this._category = object.category;
20         this._changeType = object.result; // Can't change due to v2 compatibility.
21         this._needed = object.needed;
22         this._bugs = object.bugs || [];
23         this._causes = object.causes || [];
24         this._fixes = object.fixes || [];
25         this._buildRequestCount = object.buildRequestCount;
26         this._finishedBuildRequestCount = object.finishedBuildRequestCount;
27     }
28
29     static findByPlatformAndMetric(platformId, metricId)
30     {
31         return this.all().filter(function (task) { return task._platform.id() == platformId && task._metric.id() == metricId; });
32     }
33
34     updateSingleton(object)
35     {
36         super.updateSingleton(object);
37
38         console.assert(this._author == object.author);
39         console.assert(+this._createdAt == +object.createdAt);
40         console.assert(this._platform == object.platform);
41         console.assert(this._metric == object.metric);
42         console.assert(this._startMeasurementId == object.startRun);
43         console.assert(this._startTime == object.startRunTime);
44         console.assert(this._endMeasurementId == object.endRun);
45         console.assert(this._endTime == object.endRunTime);
46
47         this._category = object.category;
48         this._changeType = object.result; // Can't change due to v2 compatibility.
49         this._needed = object.needed;
50         this._bugs = object.bugs || [];
51         this._causes = object.causes || [];
52         this._fixes = object.fixes || [];
53         this._buildRequestCount = object.buildRequestCount;
54         this._finishedBuildRequestCount = object.finishedBuildRequestCount;
55     }
56
57     hasResults() { return this._finishedBuildRequestCount; }
58     hasPendingRequests() { return this._finishedBuildRequestCount < this._buildRequestCount; }
59     requestLabel() { return `${this._finishedBuildRequestCount} of ${this._buildRequestCount}`; }
60
61     startMeasurementId() { return this._startMeasurementId; }
62     startTime() { return this._startTime; }
63     endMeasurementId() { return this._endMeasurementId; }
64     endTime() { return this._endTime; }
65
66     author() { return this._author || ''; }
67     createdAt() { return this._createdAt; }
68     bugs() { return this._bugs; }
69     causes() { return this._causes; }
70     fixes() { return this._fixes; }
71     platform() { return this._platform; }
72     metric() { return this._metric; }
73     category() { return this._category; }
74     changeType() { return this._changeType; }
75
76     updateName(newName) { return this._updateRemoteState({name: newName}); }
77     updateChangeType(changeType) { return this._updateRemoteState({result: changeType}); }
78
79     _updateRemoteState(param)
80     {
81         param.task = this.id();
82         return PrivilegedAPI.sendRequest('update-analysis-task', param).then(function (data) {
83             return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: param.task}, true)
84                 .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
85         });
86     }
87
88     associateBug(tracker, bugNumber)
89     {
90         console.assert(tracker instanceof BugTracker);
91         console.assert(typeof(bugNumber) == 'number');
92         var id = this.id();
93         return PrivilegedAPI.sendRequest('associate-bug', {
94             task: id,
95             bugTracker: tracker.id(),
96             number: bugNumber,
97         }).then(function (data) {
98             return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
99                 .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
100         });
101     }
102
103     dissociateBug(bug)
104     {
105         console.assert(bug instanceof Bug);
106         console.assert(this.bugs().includes(bug));
107         var id = this.id();
108         return PrivilegedAPI.sendRequest('associate-bug', {
109             task: id,
110             bugTracker: bug.bugTracker().id(),
111             number: bug.bugNumber(),
112             shouldDelete: true,
113         }).then(function (data) {
114             return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
115                 .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
116         });
117     }
118
119     associateCommit(kind, repository, revision)
120     {
121         console.assert(kind == 'cause' || kind == 'fix');
122         console.assert(repository instanceof Repository);
123         var id = this.id();
124         return PrivilegedAPI.sendRequest('associate-commit', {
125             task: id,
126             repository: repository.id(),
127             revision: revision,
128             kind: kind,
129         }).then(function (data) {
130             return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
131                 .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
132         });
133     }
134
135     dissociateCommit(commit)
136     {
137         console.assert(commit instanceof CommitLog);
138         var id = this.id();
139         return PrivilegedAPI.sendRequest('associate-commit', {
140             task: id,
141             commit: commit.remoteId(),
142         }).then(function (data) {
143             return AnalysisTask.cachedFetch('../api/analysis-tasks', {id: id}, true)
144                 .then(AnalysisTask._constructAnalysisTasksFromRawData.bind(AnalysisTask));
145         });
146     }
147
148     static categories()
149     {
150         return [
151             'unconfirmed',
152             'bisecting',
153             'identified',
154             'closed'
155         ];
156     }
157
158     static fetchById(id)
159     {
160         return this._fetchSubset({id: id}).then(function (data) { return AnalysisTask.findById(id); });
161     }
162
163     static fetchByBuildRequestId(id)
164     {
165         return this._fetchSubset({buildRequest: id}).then(function (tasks) { return tasks[0]; });
166     }
167
168     static fetchByPlatformAndMetric(platformId, metricId)
169     {
170         return this._fetchSubset({platform: platformId, metric: metricId}).then(function (data) {
171             return AnalysisTask.findByPlatformAndMetric(platformId, metricId);
172         });
173     }
174
175     static fetchRelatedTasks(taskId)
176     {
177         // FIXME: We should add new sever-side API to just fetch the related tasks.
178         return this.fetchAll().then(function () {
179             var task = AnalysisTask.findById(taskId);
180             if (!task)
181                 return undefined;
182             var relatedTasks = new Set;
183             for (var bug of task.bugs()) {
184                 for (var otherTask of AnalysisTask.all()) {
185                     if (otherTask.bugs().includes(bug))
186                         relatedTasks.add(otherTask);
187                 }
188             }
189             for (var otherTask of AnalysisTask.all()) {
190                 if (task.endTime() < otherTask.startTime()
191                     || otherTask.endTime() < task.startTime()
192                     || task.metric() != otherTask.metric())
193                     continue;
194                 relatedTasks.add(otherTask);
195             }
196             return Array.from(relatedTasks);
197         });
198     }
199
200     static _fetchSubset(params)
201     {
202         if (this._fetchAllPromise)
203             return this._fetchAllPromise;
204         return this.cachedFetch('../api/analysis-tasks', params).then(this._constructAnalysisTasksFromRawData.bind(this));
205     }
206
207     static fetchAll()
208     {
209         if (!this._fetchAllPromise)
210             this._fetchAllPromise = getJSONWithStatus('../api/analysis-tasks').then(this._constructAnalysisTasksFromRawData.bind(this));
211         return this._fetchAllPromise;
212     }
213
214     static _constructAnalysisTasksFromRawData(data)
215     {
216         Instrumentation.startMeasuringTime('AnalysisTask', 'construction');
217
218         // FIXME: The backend shouldn't create a separate bug row per task for the same bug number.
219         var taskToBug = {};
220         for (var rawData of data.bugs) {
221             rawData.bugTracker = BugTracker.findById(rawData.bugTracker);
222             if (!rawData.bugTracker)
223                 continue;
224
225             var bug = Bug.ensureSingleton(rawData);
226             if (!taskToBug[rawData.task])
227                 taskToBug[rawData.task] = [];
228             taskToBug[rawData.task].push(bug);
229         }
230
231         for (var rawData of data.commits) {
232             rawData.repository = Repository.findById(rawData.repository);
233             if (!rawData.repository)
234                 continue;
235             CommitLog.ensureSingleton(rawData.repository, rawData);
236         }
237
238         function resolveCommits(commits) {
239             return commits.map(function (id) { return CommitLog.findByRemoteId(id); }).filter(function (commit) { return !!commit; });
240         }
241
242         var results = [];
243         for (var rawData of data.analysisTasks) {
244             rawData.platform = Platform.findById(rawData.platform);
245             rawData.metric = Metric.findById(rawData.metric);
246             if (!rawData.platform || !rawData.metric)
247                 continue;
248
249             rawData.bugs = taskToBug[rawData.id];
250             rawData.causes = resolveCommits(rawData.causes);
251             rawData.fixes = resolveCommits(rawData.fixes);
252             results.push(AnalysisTask.ensureSingleton(rawData.id, rawData));
253         }
254
255         Instrumentation.endMeasuringTime('AnalysisTask', 'construction');
256
257         return results;
258     }
259
260     static create(name, startRunId, endRunId)
261     {
262         return PrivilegedAPI.sendRequest('create-analysis-task', {
263             name: name,
264             startRun: startRunId,
265             endRun: endRunId,
266         });
267     }
268 }