Add UI for A/B testing on owned commits.
[WebKit.git] / Websites / perf.webkit.org / public / v3 / models / commit-log.js
1 'use strict';
2
3 class CommitLog extends DataModelObject {
4     constructor(id, rawData)
5     {
6         console.assert(parseInt(id) == id);
7         super(id);
8         this._repository = rawData.repository;
9         console.assert(this._repository instanceof Repository);
10         this._rawData = rawData;
11         this._remoteId = rawData.id;
12         if (this._remoteId)
13             this.ensureNamedStaticMap('remoteId')[this._remoteId] = this;
14         this._ownedCommits = null;
15         this._ownerCommit = null;
16         this._ownedCommitByOwnedRepository = new Map;
17     }
18
19     updateSingleton(rawData)
20     {
21         super.updateSingleton(rawData);
22
23         console.assert(+this._rawData['time'] == +rawData['time']);
24         console.assert(this._rawData['revision'] == rawData['revision']);
25
26         if (rawData.authorName)
27             this._rawData.authorName = rawData.authorName;
28         if (rawData.message)
29             this._rawData.message = rawData.message;
30         if (rawData.ownsCommits)
31             this._rawData.ownsCommits = rawData.ownsCommits;
32     }
33
34     repository() { return this._repository; }
35     time() { return new Date(this._rawData['time']); }
36     author() { return this._rawData['authorName']; }
37     revision() { return this._rawData['revision']; }
38     message() { return this._rawData['message']; }
39     url() { return this._repository.urlForRevision(this._rawData['revision']); }
40     ownsCommits() { return this._rawData['ownsCommits']; }
41     ownedCommits() { return this._ownedCommits; }
42     ownerCommit() { return this._ownerCommit; }
43
44     setOwnerCommits(ownerCommit) { this._ownerCommit = ownerCommit; }
45
46     label()
47     {
48         const revision = this.revision();
49         if (parseInt(revision) == revision) // e.g. r12345
50             return 'r' + revision;
51         if (revision.length == 40) // e.g. git hash
52             return revision.substring(0, 8);
53         return revision;
54     }
55     title() { return this._repository.name() + ' at ' + this.label(); }
56
57     diff(previousCommit)
58     {
59         if (this == previousCommit)
60             previousCommit = null;
61
62         const repository = this._repository;
63         if (!previousCommit)
64             return {repository: repository, label: this.label(), url: this.url()};
65
66         const to = this.revision();
67         const from = previousCommit.revision();
68         let label = null;
69         if (parseInt(from) == from)// e.g. r12345.
70             label = `r${from}-r${this.revision()}`;
71         else if (to.length == 40) // e.g. git hash
72             label = `${from.substring(0, 8)}..${to.substring(0, 8)}`;
73         else
74             label = `${from} - ${to}`;
75
76         return {repository: repository, label: label, url: repository.urlForRevisionRange(from, to)};
77     }
78
79     static fetchLatestCommitForPlatform(repository, platform)
80     {
81         console.assert(repository instanceof Repository);
82         console.assert(platform instanceof Platform);
83         return this.cachedFetch(`/api/commits/${repository.id()}/latest`, {platform: platform.id()}).then((data) => {
84             const commits = data['commits'];
85             if (!commits || !commits.length)
86                 return null;
87             const rawData = commits[0];
88             rawData.repository = repository;
89             return CommitLog.ensureSingleton(rawData.id, rawData);
90         });
91     }
92
93     ownedCommitForOwnedRepository(ownedRepository) { return this._ownedCommitByOwnedRepository.get(ownedRepository); }
94
95     fetchOwnedCommits()
96     {
97         if (!this.repository().ownedRepositories())
98             return Promise.reject();
99
100         if (!this.ownsCommits())
101             return Promise.reject();
102
103         if (this._ownedCommits)
104             return Promise.resolve(this._ownedCommits);
105
106         return CommitLog.cachedFetch(`../api/commits/${this.repository().id()}/owned-commits?owner-revision=${escape(this.revision())}`).then((data) => {
107             this._ownedCommits = CommitLog._constructFromRawData(data);
108             this._ownedCommits.forEach((ownedCommit) => {
109                 ownedCommit.setOwnerCommits(this);
110                 this._ownedCommitByOwnedRepository.set(ownedCommit.repository(), ownedCommit);
111             });
112             return this._ownedCommits;
113         });
114     }
115
116     _buildOwnedCommitMap()
117     {
118         const ownedCommitMap = new Map;
119         for (const commit of this._ownedCommits)
120             ownedCommitMap.set(commit.repository(), commit);
121         return ownedCommitMap;
122     }
123
124     static ownedCommitDifferenceForOwnerCommits(...commits)
125     {
126         console.assert(commits.length >= 2);
127
128         const ownedCommitRepositories = new Set;
129         const ownedCommitMapList = commits.map((commit) => {
130             console.assert(commit);
131             console.assert(commit._ownedCommits);
132             const ownedCommitMap = commit._buildOwnedCommitMap();
133             for (const repository of ownedCommitMap.keys())
134                 ownedCommitRepositories.add(repository);
135             return ownedCommitMap;
136         });
137
138         const difference = new Map;
139         ownedCommitRepositories.forEach((ownedCommitRepository) => {
140             const ownedCommits = ownedCommitMapList.map((ownedCommitMap) => ownedCommitMap.get(ownedCommitRepository));
141             const uniqueOwnedCommits = new Set(ownedCommits);
142             if (uniqueOwnedCommits.size > 1)
143                 difference.set(ownedCommitRepository, ownedCommits);
144         });
145         return difference;
146     }
147
148     static fetchBetweenRevisions(repository, precedingRevision, lastRevision)
149     {
150         // FIXME: The cache should be smarter about fetching a range within an already fetched range, etc...
151         // FIXME: We should evict some entires from the cache in cachedFetch.
152         return this.cachedFetch(`/api/commits/${repository.id()}/`, {precedingRevision, lastRevision})
153             .then((data) => this._constructFromRawData(data));
154     }
155
156     static fetchForSingleRevision(repository, revision)
157     {
158         return this.cachedFetch(`/api/commits/${repository.id()}/${revision}`).then((data) => {
159             return this._constructFromRawData(data);
160         });
161     }
162
163     static _constructFromRawData(data)
164     {
165         return data['commits'].map((rawData) => {
166             rawData.repository = Repository.findById(rawData.repository);
167             return CommitLog.ensureSingleton(rawData.id, rawData);
168         });
169     }
170 }
171
172 if (typeof module != 'undefined')
173     module.exports.CommitLog = CommitLog;