Add the support for scheduling a A/B testing with a patch.
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / models / commit-set.js
1 'use strict';
2
3 class CommitSet extends DataModelObject {
4
5     constructor(id, object)
6     {
7         super(id);
8         this._repositories = [];
9         this._repositoryToCommitMap = new Map;
10         this._repositoryToPatchMap = new Map;
11         this._latestCommitTime = null;
12         this._customRoots = [];
13
14         if (!object)
15             return;
16
17         for (const item of object.revisionItems) {
18             const commit = item.commit;
19             console.assert(commit instanceof CommitLog);
20             console.assert(!item.patch || item.patch instanceof UploadedFile);
21             const repository = commit.repository();
22             this._repositoryToCommitMap.set(repository, commit);
23             this._repositoryToPatchMap.set(repository, item.patch);
24             this._repositories.push(commit.repository());
25         }
26         this._customRoots = object.customRoots;
27     }
28
29     repositories() { return this._repositories; }
30     customRoots() { return this._customRoots; }
31     commitForRepository(repository) { return this._repositoryToCommitMap.get(repository); }
32
33     revisionForRepository(repository)
34     {
35         var commit = this._repositoryToCommitMap.get(repository);
36         return commit ? commit.revision() : null;
37     }
38
39     patchForRepository(repository) { return this._repositoryToPatchMap.get(repository); }
40
41     // FIXME: This should return a Date object.
42     latestCommitTime()
43     {
44         if (this._latestCommitTime == null) {
45             var maxTime = 0;
46             for (const [repository, commit] of this._repositoryToCommitMap)
47                 maxTime = Math.max(maxTime, +commit.time());
48             this._latestCommitTime = maxTime;
49         }
50         return this._latestCommitTime;
51     }
52
53     equals(other)
54     {
55         if (this._repositories.length != other._repositories.length)
56             return false;
57         for (const [repository, commit] of this._repositoryToCommitMap) {
58             if (commit != other._repositoryToCommitMap.get(repository))
59                 return false;
60             if (this._repositoryToPatchMap.get(repository) != other._repositoryToCommitMap.get(repository))
61                 return false;
62         }
63         return CommitSet.areCustomRootsEqual(this._customRoots, other._customRoots);
64     }
65
66     static areCustomRootsEqual(customRoots1, customRoots2)
67     {
68         if (customRoots1.length != customRoots2.length)
69             return false;
70         const set2 = new Set(customRoots2);
71         for (let file of customRoots1) {
72             if (!set2.has(file))
73                 return false;
74         }
75         return true;
76     }
77
78     static containsMultipleCommitsForRepository(commitSets, repository)
79     {
80         console.assert(repository instanceof Repository);
81         if (commitSets.length < 2)
82             return false;
83         const firstCommit = commitSets[0].commitForRepository(repository);
84         for (let set of commitSets) {
85             const anotherCommit = set.commitForRepository(repository);
86             if (!firstCommit != !anotherCommit || (firstCommit && firstCommit.revision() != anotherCommit.revision()))
87                 return true;
88         }
89         return false;
90     }
91 }
92
93 class MeasurementCommitSet extends CommitSet {
94
95     constructor(id, revisionList)
96     {
97         super(id, null);
98         for (var values of revisionList) {
99             // [<commit-id>, <repository-id>, <revision>, <time>]
100             var commitId = values[0];
101             var repositoryId = values[1];
102             var revision = values[2];
103             var time = values[3];
104             var repository = Repository.findById(repositoryId);
105             if (!repository)
106                 continue;
107
108             // FIXME: Add a flag to remember the fact this commit log is incomplete.
109             const commit = CommitLog.ensureSingleton(commitId, {repository: repository, revision: revision, time: time});
110             this._repositoryToCommitMap.set(repository, commit);
111             this._repositories.push(repository);
112         }
113     }
114
115     // Use CommitSet's static maps because MeasurementCommitSet and CommitSet are logically of the same type.
116     // FIXME: Idaelly, DataModel should take care of this but traversing prototype chain is expensive.
117     namedStaticMap(name) { return CommitSet.namedStaticMap(name); }
118     ensureNamedStaticMap(name) { return CommitSet.ensureNamedStaticMap(name); }
119     static namedStaticMap(name) { return CommitSet.namedStaticMap(name); }
120     static ensureNamedStaticMap(name) { return CommitSet.ensureNamedStaticMap(name); }
121
122     static ensureSingleton(measurementId, revisionList)
123     {
124         const commitSetId = measurementId + '-commitset';
125         return CommitSet.findById(commitSetId) || (new MeasurementCommitSet(commitSetId, revisionList));
126     }
127 }
128
129 class CustomCommitSet {
130
131     constructor()
132     {
133         this._revisionListByRepository = new Map;
134         this._customRoots = [];
135     }
136
137     setRevisionForRepository(repository, revision, patch = null)
138     {
139         console.assert(repository instanceof Repository);
140         console.assert(!patch || patch instanceof UploadedFile);
141         this._revisionListByRepository.set(repository, {revision, patch});
142     }
143
144     equals(other)
145     {
146         console.assert(other instanceof CustomCommitSet);
147         if (this._revisionListByRepository.size != other._revisionListByRepository.size)
148             return false;
149         for (let repository of this._revisionListByRepository.keys()) {
150             const thisRevision = this._revisionListByRepository.get(repository);
151             const otherRevision = other._revisionListByRepository.get(repository);
152             if (!thisRevision != !otherRevision)
153                 return false;
154             if (thisRevision && (thisRevision.revision != otherRevision.revision
155                 || thisRevision.patch != otherRevision.patch))
156                 return false;
157         }
158         return CommitSet.areCustomRootsEqual(this._customRoots, other._customRoots);
159     }
160
161     repositories() { return Array.from(this._revisionListByRepository.keys()); }
162     revisionForRepository(repository)
163     {
164         const entry = this._revisionListByRepository.get(repository);
165         if (!entry)
166             return null;
167         return entry.revision;
168     }
169     patchForRepository(repository)
170     {
171         const entry = this._revisionListByRepository.get(repository);
172         if (!entry)
173             return null;
174         return entry.patch;
175     }
176     customRoots() { return this._customRoots; }
177
178     addCustomRoot(uploadedFile)
179     {
180         console.assert(uploadedFile instanceof UploadedFile);
181         this._customRoots.push(uploadedFile);
182     }
183 }
184
185 if (typeof module != 'undefined') {
186     module.exports.CommitSet = CommitSet;
187     module.exports.MeasurementCommitSet = MeasurementCommitSet;
188     module.exports.CustomCommitSet = CustomCommitSet;
189 }