Add a bisect button to automatically schedule bisecting A/B tasks.
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / models / test-group.js
1 'use strict';
2
3 class TestGroup extends LabeledObject {
4
5     constructor(id, object)
6     {
7         super(id, object);
8         this._taskId = object.task;
9         this._authorName = object.author;
10         this._createdAt = new Date(object.createdAt);
11         this._isHidden = object.hidden;
12         this._buildRequests = [];
13         this._orderBuildRequestsLazily = new LazilyEvaluatedFunction((...buildRequests) => {
14             return buildRequests.sort((a, b) => a.order() - b.order());
15         });
16         this._repositories = null;
17         this._computeRequestedCommitSetsLazily = new LazilyEvaluatedFunction(TestGroup._computeRequestedCommitSets);
18         this._requestedCommitSets = null;
19         this._commitSetToLabel = new Map;
20         console.assert(!object.platform || object.platform instanceof Platform);
21         this._platform = object.platform;
22     }
23
24     updateSingleton(object)
25     {
26         super.updateSingleton(object);
27
28         console.assert(this._taskId == object.task);
29         console.assert(+this._createdAt == +object.createdAt);
30         console.assert(this._platform == object.platform);
31
32         this._isHidden = object.hidden;
33     }
34
35     task() { return AnalysisTask.findById(this._taskId); }
36     createdAt() { return this._createdAt; }
37     isHidden() { return this._isHidden; }
38     buildRequests() { return this._buildRequests; }
39     addBuildRequest(request)
40     {
41         this._buildRequests.push(request);
42         this._requestedCommitSets = null;
43         this._commitSetToLabel.clear();
44     }
45
46     test()
47     {
48         const request = this._lastRequest();
49         return request ? request.test() : null;
50     }
51
52     platform() { return this._platform; }
53
54     _lastRequest()
55     {
56         const requests = this._orderedBuildRequests();
57         return requests.length ? requests[requests.length - 1] : null;
58     }
59
60     _orderedBuildRequests()
61     {
62         return this._orderBuildRequestsLazily.evaluate(...this._buildRequests);
63     }
64
65     repetitionCount()
66     {
67         if (!this._buildRequests.length)
68             return 0;
69         const commitSet = this._buildRequests[0].commitSet();
70         let count = 0;
71         for (const request of this._buildRequests) {
72             if (request.isTest() && request.commitSet() == commitSet)
73                 count++;
74         }
75         return count;
76     }
77
78     requestedCommitSets()
79     {
80         return this._computeRequestedCommitSetsLazily.evaluate(...this._orderedBuildRequests());
81     }
82
83     static _computeRequestedCommitSets(...orderedBuildRequests)
84     {
85         const requestedCommitSets = [];
86         const commitSetLabelMap = new Map;
87         for (const request of orderedBuildRequests) {
88             const set = request.commitSet();
89             if (!requestedCommitSets.includes(set))
90                 requestedCommitSets.push(set);
91         }
92         return requestedCommitSets;
93     }
94
95     requestsForCommitSet(commitSet)
96     {
97         return this._orderedBuildRequests().filter((request) => request.commitSet() == commitSet);
98     }
99
100     labelForCommitSet(commitSet)
101     {
102         const requestedSets = this.requestedCommitSets();
103         const setIndex = requestedSets.indexOf(commitSet);
104         if (setIndex < 0)
105             return null;
106         return String.fromCharCode('A'.charCodeAt(0) + setIndex);
107     }
108
109     hasFinished()
110     {
111         return this._buildRequests.every(function (request) { return request.hasFinished(); });
112     }
113
114     hasStarted()
115     {
116         return this._buildRequests.some(function (request) { return request.hasStarted(); });
117     }
118
119     hasPending()
120     {
121         return this._buildRequests.some(function (request) { return request.isPending(); });
122     }
123
124     compareTestResults(metric, beforeValues, afterValues)
125     {
126         console.assert(metric);
127         const beforeMean = Statistics.sum(beforeValues) / beforeValues.length;
128         const afterMean = Statistics.sum(afterValues) / afterValues.length;
129
130         var result = {changeType: null, status: 'failed', label: 'Failed', fullLabel: 'Failed', isStatisticallySignificant: false};
131
132         var hasCompleted = this.hasFinished();
133         if (!hasCompleted) {
134             if (this.hasStarted()) {
135                 result.status = 'running';
136                 result.label = 'Running';
137                 result.fullLabel = 'Running';
138             } else {
139                 console.assert(result.changeType === null);
140                 result.status = 'pending';
141                 result.label = 'Pending';
142                 result.fullLabel = 'Pending';
143             }
144         }
145
146         if (beforeValues.length && afterValues.length) {
147             const summary = metric.labelForDifference(beforeMean, afterMean, 'better', 'worse');
148             result.changeType = summary.changeType;
149             result.label = summary.changeLabel;
150             var isSignificant = Statistics.testWelchsT(beforeValues, afterValues);
151             var significanceLabel = isSignificant ? 'significant' : 'insignificant';
152
153             if (hasCompleted)
154                 result.status = isSignificant ? result.changeType : 'unchanged';
155             result.fullLabel = `${result.label} (statistically ${significanceLabel})`;
156             result.isStatisticallySignificant = isSignificant;
157         }
158
159         return result;
160     }
161
162     updateName(newName)
163     {
164         var self = this;
165         var id = this.id();
166         return PrivilegedAPI.sendRequest('update-test-group', {
167             group: id,
168             name: newName,
169         }).then(function (data) {
170             return TestGroup.cachedFetch(`/api/test-groups/${id}`, {}, true)
171                 .then(TestGroup._createModelsFromFetchedTestGroups.bind(TestGroup));
172         });
173     }
174
175     updateHiddenFlag(hidden)
176     {
177         var self = this;
178         var id = this.id();
179         return PrivilegedAPI.sendRequest('update-test-group', {
180             group: id,
181             hidden: !!hidden,
182         }).then(function (data) {
183             return TestGroup.cachedFetch(`/api/test-groups/${id}`, {}, true)
184                 .then(TestGroup._createModelsFromFetchedTestGroups.bind(TestGroup));
185         });
186     }
187
188     static createWithTask(taskName, platform, test, groupName, repetitionCount, commitSets)
189     {
190         console.assert(commitSets.length == 2);
191         const revisionSets = this._revisionSetsFromCommitSets(commitSets);
192         const params = {taskName, name: groupName, platform: platform.id(), test: test.id(), repetitionCount, revisionSets};
193         return PrivilegedAPI.sendRequest('create-test-group', params).then((data) => {
194             return AnalysisTask.fetchById(data['taskId']);
195         }).then((task) => {
196             return this.fetchForTask(task.id()).then(() => task);
197         });
198     }
199
200     static createWithCustomConfiguration(task, platform, test, groupName, repetitionCount, commitSets)
201     {
202         console.assert(commitSets.length == 2);
203         const revisionSets = this._revisionSetsFromCommitSets(commitSets);
204         const params = {task: task.id(), name: groupName, platform: platform.id(), test: test.id(), repetitionCount, revisionSets};
205         return PrivilegedAPI.sendRequest('create-test-group', params).then((data) => {
206             return this.fetchForTask(data['taskId'], true);
207         });
208     }
209
210     static createAndRefetchTestGroups(task, name, repetitionCount, commitSets)
211     {
212         console.assert(commitSets.length == 2);
213         const revisionSets = this._revisionSetsFromCommitSets(commitSets);
214         return PrivilegedAPI.sendRequest('create-test-group', {
215             task: task.id(),
216             name: name,
217             repetitionCount: repetitionCount,
218             revisionSets: revisionSets,
219         }).then((data) => this.fetchForTask(data['taskId'], true));
220     }
221
222     static _revisionSetsFromCommitSets(commitSets)
223     {
224         return commitSets.map((commitSet) => {
225             console.assert(commitSet instanceof CustomCommitSet || commitSet instanceof CommitSet);
226             const revisionSet = {};
227             for (let repository of commitSet.repositories()) {
228                 const patchFile = commitSet.patchForRepository(repository);
229                 revisionSet[repository.id()] = {
230                     revision: commitSet.revisionForRepository(repository),
231                     ownerRevision: commitSet.ownerRevisionForRepository(repository),
232                     patch: patchFile ? patchFile.id() : null,
233                 };
234             }
235             const customRoots = commitSet.customRoots();
236             if (customRoots && customRoots.length)
237                 revisionSet['customRoots'] = customRoots.map((uploadedFile) => uploadedFile.id());
238             return revisionSet;
239         });
240     }
241
242     static findAllByTask(taskId)
243     {
244         return TestGroup.all().filter((testGroup) => testGroup._taskId == taskId);
245     }
246
247     static fetchForTask(taskId, ignoreCache = false)
248     {
249         return this.cachedFetch('/api/test-groups', {task: taskId}, ignoreCache).then(this._createModelsFromFetchedTestGroups.bind(this));
250     }
251
252     static _createModelsFromFetchedTestGroups(data)
253     {
254         var testGroups = data['testGroups'].map(function (row) {
255             row.platform = Platform.findById(row.platform);
256             return TestGroup.ensureSingleton(row.id, row);
257         });
258
259         BuildRequest.constructBuildRequestsFromData(data);
260
261         return testGroups;
262     }
263 }
264
265 if (typeof module != 'undefined')
266     module.exports.TestGroup = TestGroup;