Changeset 226259 in webkit
- Timestamp:
- Dec 21, 2017 10:25:24 PM (6 years ago)
- Location:
- trunk/Websites/perf.webkit.org
- Files:
-
- 3 added
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Websites/perf.webkit.org/ChangeLog
r225898 r226259 1 2017-12-21 Dewei Zhu <dewei_zhu@apple.com> 2 3 Add UI for A/B testing on owned commits. 4 https://bugs.webkit.org/show_bug.cgi?id=177993 5 6 Reviewed by Ryosuke Niwa. 7 8 Customizable test group form should support specifying and A/B testing owned commits. 9 Introduce 'IntermediateCommitSet' to achieve the goal of specifying owned commits for A/B test. 10 In order to support configure A/B testing that may need to add/remove owned commits, CommitSet may be the 11 closest thing we can get. However, it is a subclass of DataModelObject, which means CommitSet is a representation 12 of 'commit_sets' table and can only be updated from server data. Thus, we want something like CustomCommitSet that 13 is not a representation of database table, but unlike CustomCommitSet, it should store information about commits 14 rather than a revision. As a result, IntermediateCommitSet is introduced. For a longer term, we may replace 15 CustomCommitSet with IntermediateCommitSet as it carries more information and could potentially simplify some 16 CustomCommitSet related APIs by using commit id instead of commit revision. 17 Extend ButtonBase class so that we can enable/disable a button. 18 19 * public/v3/components/button-base.js: 20 (ButtonBase): 21 (ButtonBase.prototype.setDisabled): Enable/disable a button. 22 (ButtonBase.prototype.render): 23 (ButtonBase.cssTemplate): Added css rule for disabled button. 24 * public/v3/components/combo-box.js: Added. 25 (ComboBox): 26 (ComboBox.prototype.didConstructShadowTree): Setup text field. 27 (ComboBox.prototype.render): 28 (ComboBox.prototype._candidateNameForCurrentIndex): Returns candidate name based on current index. 29 (ComboBox.prototype._candidateElementForCurrentIndex): Returns a list element based on current index. 30 (ComboBox.prototype._autoCompleteIfOnlyOneMatchingItem): Supports auto completion. 31 (ComboBox.prototype._moveCandidate): Supports arrow up/down. 32 (ComboBox.prototype._updateCandidateList): Hide/unhide candidate list and high-light selected candidate. 33 (ComboBox.prototype._renderCandidateList): Render candidate list base on value on text field. 34 (ComboBox.htmlTemplate): 35 (ComboBox.cssTemplate): 36 * public/v3/components/customizable-test-group-form.js: 37 (CustomizableTestGroupForm): 38 (CustomizableTestGroupForm.prototype.didConstructShadowTree): Only fetch the full commits when we about to create a customized A/B tests. 39 (CustomizableTestGroupForm.prototype._computeCommitSetMap): Compute the CustomCommitSet based on IntermediateCommitSet and 40 other revision related information in some map. 41 (CustomizableTestGroupForm.prototype.render): 42 (CustomizableTestGroupForm.prototype._renderCustomRevisionTable): 43 (CustomizableTestGroupForm.prototype._constructTableBodyList): This function builds table body for each highest level repository. 44 It will also include the owned repository rows in the same table body if the commits for highest level repository owns other commits. 45 (CustomizableTestGroupForm.prototype._constructTableRowForCommitsWithoutOwner): Build a table row for a highest level repository. 46 (CustomizableTestGroupForm.prototype._constructTableRowForCommitsWithOwner): Build a table row for repository with owner. 47 (CustomizableTestGroupForm.prototype._constructTableRowForIncompleteOwnedCommits): Build a table row for an unspecified repository. 48 (CustomizableTestGroupForm.prototype._constructRevisionRadioButtons): Update the logic to support build radio buttons for the owned repository rows. 49 (CustomizableTestGroupForm.cssTemplate): 50 * public/v3/components/minus-button.js: Added. 51 (MinusButton): 52 (MinusButton.buttonContent): 53 * public/v3/components/owned-commit-viewer.js: 54 (OwnedCommitViewer.prototype._renderOwnedCommitTable): 55 * public/v3/components/plus-button.js: Added. 56 (PlusButton): 57 (PlusButton.buttonContent): 58 * public/v3/index.html: Added new components. 59 * public/v3/models/commit-log.js: Added owner and owned commit information. 60 (CommitLog): 61 (CommitLog.prototype.ownedCommits): Returns a list of commits owned by current commit. 62 (CommitLog.prototype.ownerCommit): Return owner commit of current commit. 63 (CommitLog.prototype.setOwnerCommits): Set owner commit of current commit. 64 (CommitLog.prototype.label): Remove unnecessary 'else'. 65 (CommitLog.prototype.diff): Remove unused 'fromRevisionForURL' and tiny code cleanup. 66 (CommitLog.prototype.ownedCommitForOwnedRepository): 67 (CommitLog.prototype.fetchOwnedCommits): Sets the owner for those owned commits. The owner of a commit with multiple owner 68 commits will be overwritten by each time this function is called. 69 (CommitLog.ownedCommitDifferenceForOwnerCommits): A more generic version of diffOwnedCommits. diffOwnedCommits only accepts 2 commits, 70 but ownedCommitDifferenceForOwnerCommits supports multiple commits. 71 (CommitLog.diffOwnedCommits): Deleted and should use 'CommitLog.ownedCommitDifferenceForOwnerCommits' instead. 72 * public/v3/models/commit-set.js: 73 (CommitSet.prototype.topLevelRepositories): 74 (CommitSet.prototype.commitForRepository): 75 (IntermediateCommitSet): Take CommitSet as argument, note the commit from CommitSet doesn't contains full information of the commit. 76 Always call 'fetchFullCommits' once before any further usages. 77 (IntermediateCommitSet.prototype.fetchCommitLogs): Fetch all commits information in current commit set. 78 (IntermediateCommitSet.prototype._fetchCommitLogAndOwnedCommits): Fetch commit log and owned commits if necessary. 79 (IntermediateCommitSet.prototype.updateRevisionForOwnerRepository): Updates a commit for a repository by given a revision of the repository. 80 (IntermediateCommitSet.prototype.setCommitForRepository): Sets a commit for a repository in commit set. 81 (IntermediateCommitSet.prototype.removeCommitForRepository): Removes a commit for a repository in commit set. 82 (IntermediateCommitSet.prototype.ownsCommitsForRepository): Returns whether the commit for repository owns commits. 83 (IntermediateCommitSet.prototype.repositories): Returns all repositories in the commit set. 84 (IntermediateCommitSet.prototype.highestLevelRepositories): Returns all repositories those don't have an owner. 85 (IntermediateCommitSet.prototype.commitForRepository): Returns a commit for a given repository. 86 (IntermediateCommitSet.prototype.ownedRepositoriesForOwnerRepository): Returns all repositories owned by a given repository in current commit set. 87 (IntermediateCommitSet.prototype.ownerCommitForRepository): Returns owner commit for a given owned repository. 88 * tools/js/v3-models.js: Added import for 'IntermediateCommitSet'. 89 * unit-tests/commit-log-tests.js: Updated unittest which tests 'ownerCommit' function. 90 * unit-tests/commit-set-tests.js: Added unit tests for IntermediateCommitSet. 91 1 92 2017-12-13 Dewei Zhu <dewei_zhu@apple.com> 2 93 -
trunk/Websites/perf.webkit.org/public/v3/components/button-base.js
r210938 r226259 1 1 2 2 class ButtonBase extends ComponentBase { 3 4 constructor(name) 5 { 6 super(name); 7 this._disabled = false; 8 } 9 10 setDisabled(disabled) 11 { 12 this._disabled = disabled; 13 this.enqueueToRender(); 14 } 15 3 16 didConstructShadowTree() 4 17 { … … 6 19 this.dispatchAction('activate'); 7 20 })); 21 } 22 23 render() 24 { 25 super.render(); 26 if (this._disabled) 27 this.content('button').setAttribute('disabled', ''); 28 else 29 this.content('button').removeAttribute('disabled'); 8 30 } 9 31 … … 36 58 } 37 59 60 a[disabled] { 61 pointer-events: none; 62 cursor: default; 63 opacity: 0.2; 64 } 65 38 66 svg { 39 67 display: block; -
trunk/Websites/perf.webkit.org/public/v3/components/customizable-test-group-form.js
r214502 r226259 1 2 1 class CustomizableTestGroupForm extends TestGroupForm { 3 2 … … 8 7 this._name = null; 9 8 this._isCustomized = false; 10 this._revisionEditorMap = {}; 11 12 this._renderCustomRevisionTableLazily = new LazilyEvaluatedFunction(this._renderCustomRevisionTable.bind(this)); 9 this._revisionEditorMap = null; 10 this._ownerRevisionMap = null; 11 this._checkedLabelByPosition = null; 12 this._hasIncompleteOwnedRepository = null; 13 this._fetchingCommitPromises = []; 13 14 } 14 15 … … 17 18 this._commitSetMap = map; 18 19 this._isCustomized = false; 20 this._fetchingCommitPromises = []; 21 this._checkedLabelByPosition = new Map; 22 this._hasIncompleteOwnedRepository = new Map; 19 23 this.enqueueToRender(); 20 24 } … … 33 37 this._name = nameControl.value; 34 38 this.enqueueToRender(); 35 } 39 }; 36 40 37 41 this.content('customize-link').onclick = this.createEventHandler(() => { 42 if (!this._isCustomized) { 43 const originalCommitSetMap = this._commitSetMap; 44 const fetchingCommitPromises = []; 45 this._commitSetMap = new Map; 46 for (const label in originalCommitSetMap) { 47 const intermediateCommitSet = new IntermediateCommitSet(originalCommitSetMap[label]); 48 fetchingCommitPromises.push(intermediateCommitSet.fetchCommitLogs()); 49 this._commitSetMap.set(label, intermediateCommitSet); 50 } 51 this._fetchingCommitPromises = fetchingCommitPromises; 52 53 return Promise.all(fetchingCommitPromises).then(() => { 54 if (this._fetchingCommitPromises !== fetchingCommitPromises) 55 return; 56 this._isCustomized = true; 57 this._fetchingCommitPromises = []; 58 for (const label in originalCommitSetMap) 59 this._checkedLabelByPosition.set(label, new Map); 60 this.enqueueToRender(); 61 }); 62 } 38 63 this._isCustomized = true; 39 64 this.enqueueToRender(); … … 48 73 49 74 const map = {}; 50 for (const label in this._commitSetMap) { 51 const originalCommitSet = this._commitSetMap; 75 for (const [label, commitSet] of this._commitSetMap) { 52 76 const customCommitSet = new CustomCommitSet; 53 for ( let repository of this._commitSetMap[label].repositories()) {54 const revisionEditor = this._revisionEditorMap [label].get(repository);77 for (const repository of commitSet.repositories()) { 78 const revisionEditor = this._revisionEditorMap.get(label).get(repository); 55 79 console.assert(revisionEditor); 56 customCommitSet.setRevisionForRepository(repository, revisionEditor.value); 80 const ownerRevision = this._ownerRevisionMap.get(label).get(repository) || null; 81 82 customCommitSet.setRevisionForRepository(repository, revisionEditor.value, null, ownerRevision); 57 83 } 58 84 map[label] = customCommitSet; … … 68 94 this.content('customize-link-container').style.display = !this._commitSetMap ? 'none' : null; 69 95 70 this._renderCustomRevisionTable Lazily.evaluate(this._commitSetMap, this._isCustomized);96 this._renderCustomRevisionTable(this._commitSetMap, this._isCustomized); 71 97 } 72 98 … … 75 101 if (!commitSetMap || !isCustomized) { 76 102 this.renderReplace(this.content('custom-table'), []); 77 return null;103 return; 78 104 } 79 105 80 106 const repositorySet = new Set; 107 const ownedRepositoriesByRepository = new Map; 81 108 const commitSetLabels = []; 82 this._revisionEditorMap = {}; 83 for (const label in commitSetMap) { 84 for (const repository of commitSetMap[label].repositories()) 109 this._revisionEditorMap = new Map; 110 this._ownerRevisionMap = new Map; 111 for (const [label, commitSet] of commitSetMap) { 112 for (const repository of commitSet.highestLevelRepositories()) { 85 113 repositorySet.add(repository); 114 const ownedRepositories = commitSetMap.get(label).ownedRepositoriesForOwnerRepository(repository); 115 116 if (!ownedRepositories) 117 continue; 118 119 if (!ownedRepositoriesByRepository.has(repository)) 120 ownedRepositoriesByRepository.set(repository, new Set); 121 const ownedRepositorySet = ownedRepositoriesByRepository.get(repository); 122 for (const ownedRepository of ownedRepositories) 123 ownedRepositorySet.add(ownedRepository) 124 } 86 125 commitSetLabels.push(label); 87 this._revisionEditorMap[label] = new Map; 126 this._revisionEditorMap.set(label, new Map); 127 this._ownerRevisionMap.set(label, new Map); 88 128 } 89 129 … … 93 133 element('thead', 94 134 element('tr', 95 [element('td', 'Repository'), commitSetLabels.map((label) => element('td', {colspan: commitSetLabels.length + 1}, label))])), 96 element('tbody', 97 repositoryList.map((repository) => { 98 const cells = [element('th', repository.label())]; 99 for (const label in commitSetMap) 100 cells.push(this._constructRevisionRadioButtons(commitSetMap, repository, label)); 101 return element('tr', cells); 102 }))]); 103 104 return repositoryList; 105 } 106 107 _constructRevisionRadioButtons(commitSetMap, repository, rowLabel) 108 { 109 const element = ComponentBase.createElement; 110 const revisionEditor = element('input'); 111 112 this._revisionEditorMap[rowLabel].set(repository, revisionEditor); 135 [element('td', {colspan: 2}, 'Repository'), commitSetLabels.map((label) => element('td', {colspan: commitSetLabels.length + 1}, label)), element('td')])), 136 this._constructTableBodyList(repositoryList, commitSetMap, ownedRepositoriesByRepository, this._hasIncompleteOwnedRepository)]); 137 } 138 139 _constructTableBodyList(repositoryList, commitSetMap, ownedRepositoriesByRepository, hasIncompleteOwnedRepository) 140 { 141 const element = ComponentBase.createElement; 142 const tableBodyList = []; 143 for(const repository of repositoryList) { 144 const rows = []; 145 const allCommitSetSpecifiesOwnerCommit = Array.from(commitSetMap.values()).every((commitSet) => commitSet.ownsCommitsForRepository(repository)); 146 const hasIncompleteRow = hasIncompleteOwnedRepository.get(repository); 147 const ownedRepositories = ownedRepositoriesByRepository.get(repository); 148 149 rows.push(this._constructTableRowForCommitsWithoutOwner(commitSetMap, repository, allCommitSetSpecifiesOwnerCommit, hasIncompleteOwnedRepository)); 150 151 if ((!ownedRepositories || !ownedRepositories.size) && !hasIncompleteRow) { 152 tableBodyList.push(element('tbody', rows)); 153 continue; 154 } 155 156 if (ownedRepositories) { 157 for (const ownedRepository of ownedRepositories) 158 rows.push(this._constructTableRowForCommitsWithOwner(commitSetMap, ownedRepository, repository)); 159 } 160 161 if (hasIncompleteRow) { 162 const commits = Array.from(commitSetMap.values()).map((commitSet) => commitSet.commitForRepository(repository)); 163 const commitDiff = CommitLog.ownedCommitDifferenceForOwnerCommits(...commits); 164 rows.push(this._constructTableRowForIncompleteOwnedCommits(commitSetMap, repository, commitDiff)); 165 } 166 tableBodyList.push(element('tbody', rows)); 167 } 168 return tableBodyList; 169 } 170 171 _constructTableRowForCommitsWithoutOwner(commitSetMap, repository, ownsRepositories, hasIncompleteOwnedRepository) 172 { 173 const element = ComponentBase.createElement; 174 const cells = [element('th', {colspan: 2}, repository.label())]; 175 176 for (const label of commitSetMap.keys()) 177 cells.push(this._constructRevisionRadioButtons(commitSetMap, repository, label, null, ownsRepositories)); 178 179 if (ownsRepositories) { 180 const plusButton = new PlusButton(); 181 plusButton.setDisabled(hasIncompleteOwnedRepository.get(repository)); 182 plusButton.listenToAction('activate', () => { 183 this._hasIncompleteOwnedRepository.set(repository, true); 184 this.enqueueToRender(); 185 }); 186 cells.push(element('td', plusButton)); 187 } else 188 cells.push(element('td')); 189 190 return element('tr', cells); 191 } 192 193 _constructTableRowForCommitsWithOwner(commitSetMap, repository, ownerRepository) 194 { 195 const element = ComponentBase.createElement; 196 const cells = [element('td', {class: 'owner-repository-label'}), element('th', repository.label())]; 197 const minusButton = new MinusButton(); 198 199 for (const label of commitSetMap.keys()) 200 cells.push(this._constructRevisionRadioButtons(commitSetMap, repository, label, ownerRepository, false)); 201 202 minusButton.listenToAction('activate', () => { 203 for (const commitSet of commitSetMap.values()) 204 commitSet.removeCommitForRepository(repository) 205 this.enqueueToRender(); 206 }); 207 cells.push(element('td', minusButton)); 208 return element('tr', cells); 209 } 210 211 _constructTableRowForIncompleteOwnedCommits(commitSetMap, ownerRepository, commitDiff) 212 { 213 const element = ComponentBase.createElement; 214 const configurationCount = commitSetMap.size; 215 const numberOfCellsPerConfiguration = configurationCount + 1; 216 const changedRepositories = Array.from(commitDiff.keys()); 217 const minusButton = new MinusButton(); 218 const comboBox = new ComboBox(changedRepositories.map((repository) => repository.name()).sort()); 219 220 comboBox.listenToAction('update', (repositoryName) => { 221 const targetRepository = changedRepositories.find((repository) => repositoryName === repository.name()); 222 const ownedCommitDifferenceForRepository = Array.from(commitDiff.get(targetRepository).values()); 223 const commitSetList = Array.from(commitSetMap.values()); 224 225 console.assert(ownedCommitDifferenceForRepository.length === commitSetList.length); 226 for (let i = 0; i < commitSetList.length; i++) 227 commitSetList[i].setCommitForRepository(targetRepository, ownedCommitDifferenceForRepository[i]); 228 this._hasIncompleteOwnedRepository.set(ownerRepository, false); 229 this.enqueueToRender(); 230 }); 231 232 const cells = [element('td', {class: 'owner-repository-label'}), element('th', comboBox), element('td', {colspan: configurationCount * numberOfCellsPerConfiguration})]; 233 234 minusButton.listenToAction('activate', () => { 235 this._hasIncompleteOwnedRepository.set(ownerRepository, false); 236 this.enqueueToRender(); 237 }); 238 cells.push(element('td', minusButton)); 239 240 return element('tr', cells); 241 } 242 243 _constructRevisionRadioButtons(commitSetMap, repository, rowLabel, ownerRepository, ownsRepositories) 244 { 245 const element = ComponentBase.createElement; 246 const revisionEditor = element('input', {disabled: !!ownerRepository, 247 onchange: () => { 248 if (!ownsRepositories) 249 return; 250 commitSetMap.get(rowLabel).updateRevisionForOwnerRepository(repository, revisionEditor.value).catch( 251 () => revisionEditor.value = ''); 252 }}); 253 254 this._revisionEditorMap.get(rowLabel).set(repository, revisionEditor); 113 255 114 256 const nodes = []; 115 for (let labelToChoose in commitSetMap) { 116 const commit = commitSetMap[labelToChoose].commitForRepository(repository); 117 const checked = labelToChoose == rowLabel; 257 for (const labelToChoose of commitSetMap.keys()) { 258 const commit = commitSetMap.get(labelToChoose).commitForRepository(repository); 259 const checkedLabel = this._checkedLabelByPosition.get(rowLabel).get(repository) || rowLabel; 260 const checked = labelToChoose == checkedLabel; 118 261 const radioButton = element('input', {type: 'radio', name: `${rowLabel}-${repository.id()}-radio`, checked, 119 onchange: () => { revisionEditor.value = commit ? commit.revision() : ''; }}); 120 121 if (checked) 262 onchange: () => { 263 this._checkedLabelByPosition.get(rowLabel).set(repository, labelToChoose); 264 revisionEditor.value = commit ? commit.revision() : ''; 265 if (commit && commit.ownerCommit()) 266 this._ownerRevisionMap.get(rowLabel).set(repository, commit.ownerCommit().revision()); 267 }}); 268 269 if (checked) { 122 270 revisionEditor.value = commit ? commit.revision() : ''; 271 if (commit && commit.ownerCommit()) 272 this._ownerRevisionMap.get(rowLabel).set(repository, commit.ownerCommit().revision()); 273 } 123 274 nodes.push(element('td', element('label', [radioButton, labelToChoose]))); 124 275 } … … 138 289 #custom-table:not(:empty) { 139 290 margin: 1rem 0; 291 } 292 293 #custom-table td.owner-repository-label { 294 border-top: solid 2px transparent; 295 border-bottom: solid 1px transparent; 296 min-width: 2rem; 297 text-align: right; 298 } 299 300 #custom-table tr:last-child td.owner-repository-label { 301 border-bottom: solid 1px #ddd; 302 } 303 304 #custom-table th { 305 min-width: 12rem; 140 306 } 141 307 -
trunk/Websites/perf.webkit.org/public/v3/components/owned-commit-viewer.js
r222227 r226259 44 44 return; 45 45 46 const difference = CommitLog. diffOwnedCommits(this._previousCommit, this._currentCommit);46 const difference = CommitLog.ownedCommitDifferenceForOwnerCommits(this._previousCommit, this._currentCommit); 47 47 const sortedRepositories = Repository.sortByName([...difference.keys()]); 48 48 const element = ComponentBase.createElement; -
trunk/Websites/perf.webkit.org/public/v3/index.html
r225898 r226259 100 100 <script src="components/instant-file-uploader.js"></script> 101 101 <script src="components/freshness-indicator.js"></script> 102 <script src="components/plus-button.js"></script> 103 <script src="components/minus-button.js"></script> 104 <script src="components/combo-box.js"></script> 102 105 103 106 <script src="pages/page.js"></script> -
trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js
r222227 r226259 13 13 this.ensureNamedStaticMap('remoteId')[this._remoteId] = this; 14 14 this._ownedCommits = null; 15 this._ownerCommit = null; 16 this._ownedCommitByOwnedRepository = new Map; 15 17 } 16 18 … … 37 39 url() { return this._repository.urlForRevision(this._rawData['revision']); } 38 40 ownsCommits() { return this._rawData['ownsCommits']; } 41 ownedCommits() { return this._ownedCommits; } 42 ownerCommit() { return this._ownerCommit; } 43 44 setOwnerCommits(ownerCommit) { this._ownerCommit = ownerCommit; } 39 45 40 46 label() 41 47 { 42 varrevision = this.revision();48 const revision = this.revision(); 43 49 if (parseInt(revision) == revision) // e.g. r12345 44 50 return 'r' + revision; 45 elseif (revision.length == 40) // e.g. git hash51 if (revision.length == 40) // e.g. git hash 46 52 return revision.substring(0, 8); 47 53 return revision; … … 60 66 const to = this.revision(); 61 67 const from = previousCommit.revision(); 62 let fromRevisionForURL = from;63 68 let label = null; 64 if (parseInt(from) == from) { // e.g. r12345. 65 fromRevisionForURL = (parseInt(from) + 1).toString; 69 if (parseInt(from) == from)// e.g. r12345. 66 70 label = `r${from}-r${this.revision()}`; 67 }else if (to.length == 40) // e.g. git hash71 else if (to.length == 40) // e.g. git hash 68 72 label = `${from.substring(0, 8)}..${to.substring(0, 8)}`; 69 73 else … … 87 91 } 88 92 93 ownedCommitForOwnedRepository(ownedRepository) { return this._ownedCommitByOwnedRepository.get(ownedRepository); } 94 89 95 fetchOwnedCommits() 90 96 { … … 100 106 return CommitLog.cachedFetch(`../api/commits/${this.repository().id()}/owned-commits?owner-revision=${escape(this.revision())}`).then((data) => { 101 107 this._ownedCommits = CommitLog._constructFromRawData(data); 108 this._ownedCommits.forEach((ownedCommit) => { 109 ownedCommit.setOwnerCommits(this); 110 this._ownedCommitByOwnedRepository.set(ownedCommit.repository(), ownedCommit); 111 }); 102 112 return this._ownedCommits; 103 113 }); … … 112 122 } 113 123 114 static diffOwnedCommits(previousCommit, currentCommit)124 static ownedCommitDifferenceForOwnerCommits(...commits) 115 125 { 116 console.assert(previousCommit); 117 console.assert(currentCommit); 118 console.assert(previousCommit._ownedCommits); 119 console.assert(currentCommit._ownedCommits); 126 console.assert(commits.length >= 2); 120 127 121 const previousOwnedCommitMap = previousCommit._buildOwnedCommitMap(); 122 const currentOwnedCommitMap = currentCommit._buildOwnedCommitMap(); 123 const ownedCommitRepositories = new Set([...currentOwnedCommitMap.keys(), ...previousOwnedCommitMap.keys()]); 124 const difference = new Map; 125 126 ownedCommitRepositories.forEach((ownedCommitRepository) => { 127 const currentRevision = currentOwnedCommitMap.get(ownedCommitRepository); 128 const previousRevision = previousOwnedCommitMap.get(ownedCommitRepository); 129 if (currentRevision != previousRevision) 130 difference.set(ownedCommitRepository, [previousRevision, currentRevision]); 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; 131 136 }); 132 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 }); 133 145 return difference; 134 146 } -
trunk/Websites/perf.webkit.org/public/v3/models/commit-set.js
r222648 r226259 71 71 ownerCommitForRepository(repository) { return this._repositoryToCommitOwnerMap.get(repository); } 72 72 topLevelRepositories() { return Repository.sortByNamePreferringOnesWithURL(this._repositories.filter((repository) => !this.ownerRevisionForRepository(repository))); } 73 74 73 ownedRepositoriesForOwnerRepository(repository) { return this._ownerRepositoryToOwnedRepositoriesMap.get(repository); } 74 commitForRepository(repository) { return this._repositoryToCommitMap.get(repository); } 75 75 76 76 revisionForRepository(repository) … … 249 249 } 250 250 251 class IntermediateCommitSet { 252 253 constructor(commitSet) 254 { 255 console.assert(commitSet instanceof CommitSet); 256 this._commitByRepository = new Map; 257 this._ownerToOwnedRepositories = new Map; 258 this._fetchingPromiseByRepository = new Map; 259 260 for (const repository of commitSet.repositories()) 261 this.setCommitForRepository(repository, commitSet.commitForRepository(repository), commitSet.ownerCommitForRepository(repository)); 262 } 263 264 fetchCommitLogs() 265 { 266 const fetchingPromises = []; 267 for (const [repository, commit] of this._commitByRepository) 268 fetchingPromises.push(this._fetchCommitLogAndOwnedCommits(repository, commit.revision())); 269 return Promise.all(fetchingPromises); 270 } 271 272 _fetchCommitLogAndOwnedCommits(repository, revision) 273 { 274 return CommitLog.fetchForSingleRevision(repository, revision).then((commits) => { 275 console.assert(commits.length === 1); 276 const commit = commits[0]; 277 if (!commit.ownsCommits()) 278 return commit; 279 return commit.fetchOwnedCommits().then(() => commit); 280 }); 281 } 282 283 updateRevisionForOwnerRepository(repository, revision) 284 { 285 const fetchingPromise = this._fetchCommitLogAndOwnedCommits(repository, revision); 286 this._fetchingPromiseByRepository.set(repository, fetchingPromise); 287 return fetchingPromise.then((commit) => { 288 const currentFetchingPromise = this._fetchingPromiseByRepository.get(repository); 289 if (currentFetchingPromise !== fetchingPromise) 290 return; 291 this._fetchingPromiseByRepository.set(repository, null); 292 this.setCommitForRepository(repository, commit); 293 }); 294 } 295 296 setCommitForRepository(repository, commit, ownerCommit = null) 297 { 298 console.assert(repository instanceof Repository); 299 console.assert(commit instanceof CommitLog); 300 this._commitByRepository.set(repository, commit); 301 if (!ownerCommit) 302 ownerCommit = commit.ownerCommit(); 303 if (ownerCommit) { 304 const ownerRepository = ownerCommit.repository(); 305 if (!this._ownerToOwnedRepositories.has(ownerRepository)) 306 this._ownerToOwnedRepositories.set(ownerRepository, new Set); 307 const repositorySet = this._ownerToOwnedRepositories.get(ownerRepository); 308 repositorySet.add(repository); 309 } 310 } 311 312 removeCommitForRepository(repository) 313 { 314 console.assert(repository instanceof Repository); 315 this._fetchingPromiseByRepository.set(repository, null); 316 const ownerCommit = this.ownerCommitForRepository(repository); 317 if (ownerCommit) { 318 const repositorySet = this._ownerToOwnedRepositories.get(ownerCommit.repository()); 319 console.assert(repositorySet.has(repository)); 320 repositorySet.delete(repository); 321 } else if (this._ownerToOwnedRepositories.has(repository)) { 322 const ownedRepositories = this._ownerToOwnedRepositories.get(repository); 323 for (const ownedRepository of ownedRepositories) 324 this._commitByRepository.delete(ownedRepository); 325 this._ownerToOwnedRepositories.delete(repository); 326 } 327 this._commitByRepository.delete(repository); 328 } 329 330 ownsCommitsForRepository(repository) { return this.commitForRepository(repository).ownsCommits(); } 331 332 repositories() { return Array.from(this._commitByRepository.keys()); } 333 highestLevelRepositories() { return Repository.sortByNamePreferringOnesWithURL(this.repositories().filter((repository) => !this.ownerCommitForRepository(repository))); } 334 commitForRepository(repository) { return this._commitByRepository.get(repository); } 335 ownedRepositoriesForOwnerRepository(repository) { return this._ownerToOwnedRepositories.get(repository); } 336 337 ownerCommitForRepository(repository) 338 { 339 const commit = this._commitByRepository.get(repository); 340 if (!commit) 341 return null; 342 return commit.ownerCommit(); 343 } 344 } 345 251 346 if (typeof module != 'undefined') { 252 347 module.exports.CommitSet = CommitSet; 253 348 module.exports.MeasurementCommitSet = MeasurementCommitSet; 254 349 module.exports.CustomCommitSet = CustomCommitSet; 255 } 350 module.exports.IntermediateCommitSet = IntermediateCommitSet; 351 } -
trunk/Websites/perf.webkit.org/tools/js/v3-models.js
r222219 r226259 17 17 importFromV3('models/builder.js', 'Builder'); 18 18 importFromV3('models/commit-log.js', 'CommitLog'); 19 importFromV3('models/commit-set.js', 'CustomCommitSet')20 19 importFromV3('models/manifest.js', 'Manifest'); 21 20 importFromV3('models/measurement-adaptor.js', 'MeasurementAdaptor'); … … 28 27 importFromV3('models/commit-set.js', 'CommitSet'); 29 28 importFromV3('models/commit-set.js', 'CustomCommitSet'); 29 importFromV3('models/commit-set.js', 'IntermediateCommitSet'); 30 30 importFromV3('models/test.js', 'Test'); 31 31 importFromV3('models/test-group.js', 'TestGroup'); -
trunk/Websites/perf.webkit.org/unit-tests/commit-log-tests.js
r222227 r226259 192 192 193 193 it('should return owned-commit for a valid commit revision', () => { 194 const fetchingPromise = ownerCommit().fetchOwnedCommits(); 194 const commit = ownerCommit(); 195 const fetchingPromise = commit.fetchOwnedCommits(); 195 196 const requests = MockRemoteAPI.requests; 196 197 assert.equal(requests.length, 1); … … 209 210 assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb'); 210 211 assert.equal(ownedCommits[0].id(), 233); 212 assert.equal(ownedCommits[0].ownerCommit(), commit); 211 213 }); 212 214 }); … … 234 236 assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb'); 235 237 assert.equal(ownedCommits[0].id(), 233); 238 assert.equal(ownedCommits[0].ownerCommit(), commit); 236 239 return commit.fetchOwnedCommits(); 237 240 }).then((ownedCommits) => { … … 242 245 }); 243 246 244 describe(' diffOwnedCommits', () => {247 describe('ownedCommitDifferenceForOwnerCommits', () => { 245 248 beforeEach(() => { 246 249 MockRemoteAPI.reset(); 247 250 }); 248 251 249 it('should return difference between 2 owned-commits', () => {252 it('should return difference between owned-commits of 2 owner commits', () => { 250 253 const oneCommit = ownerCommit(); 251 254 const otherCommit = otherOwnerCommit(); … … 268 271 }]}); 269 272 270 return fetchingPromise.then(() => { 273 return fetchingPromise.then((ownedCommits) => { 274 assert.equal(ownedCommits.length, 2); 275 assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository); 276 assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb'); 277 assert.equal(ownedCommits[0].id(), 233); 278 assert.equal(ownedCommits[0].ownerCommit(), oneCommit); 279 assert.equal(ownedCommits[1].repository(), MockModels.webkitGit); 280 assert.equal(ownedCommits[1].revision(), '04a6c72038f0b771a19248ca2549e1258617b5fc'); 281 assert.equal(ownedCommits[1].id(), 299); 282 assert.equal(ownedCommits[1].ownerCommit(), oneCommit); 283 271 284 const otherFetchingPromise = otherCommit.fetchOwnedCommits(); 272 285 assert.equal(requests.length, 2); … … 287 300 288 301 return otherFetchingPromise; 289 }).then(() => { 290 const difference = CommitLog.diffOwnedCommits(oneCommit, otherCommit); 302 }).then((ownedCommits) => { 303 assert.equal(ownedCommits.length, 2); 304 assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository); 305 assert.equal(ownedCommits[0].revision(), 'd5099e03b482abdd77f6c4dcb875afd05bda5ab8'); 306 assert.equal(ownedCommits[0].id(), 234); 307 assert.equal(ownedCommits[0].ownerCommit(), otherCommit); 308 assert.equal(ownedCommits[1].repository(), MockModels.webkitGit); 309 assert.equal(ownedCommits[1].revision(), '04a6c72038f0b771a19248ca2549e1258617b5fc'); 310 assert.equal(ownedCommits[1].id(), 299); 311 assert.equal(ownedCommits[1].ownerCommit(), otherCommit); 312 const difference = CommitLog.ownedCommitDifferenceForOwnerCommits(oneCommit, otherCommit); 291 313 assert.equal(difference.size, 1); 292 314 assert.equal(difference.keys().next().value, MockModels.ownedRepository); -
trunk/Websites/perf.webkit.org/unit-tests/commit-set-tests.js
r222648 r226259 4 4 require('../tools/js/v3-models.js'); 5 5 const MockModels = require('./resources/mock-v3-models.js').MockModels; 6 const MockRemoteAPI = require('../unit-tests/resources/mock-remote-api.js').MockRemoteAPI; 6 7 7 8 function createPatch() … … 51 52 return customCommitSet; 52 53 } 54 55 function ownerCommit() 56 { 57 return new CommitLog(5, { 58 repository: MockModels.ownerRepository, 59 revision: 'owner-commit-0', 60 ownsCommits: true, 61 time: null, 62 }); 63 } 64 65 function partialOwnerCommit() 66 { 67 return new CommitLog(5, { 68 repository: MockModels.ownerRepository, 69 revision: 'owner-commit-0', 70 ownsCommits: null, 71 time: +(new Date('2016-05-13T00:55:57.841344Z')), 72 }); 73 } 74 75 function ownedCommit() 76 { 77 return new CommitLog(6, { 78 repository: MockModels.ownedRepository, 79 revision: 'owned-commit-0', 80 ownsCommits: null, 81 time: 1456932774000 82 }); 83 } 84 85 function webkitCommit() 86 { 87 return new CommitLog(2017, { 88 repository: MockModels.webkit, 89 revision: 'webkit-commit-0', 90 ownsCommits: false, 91 time: 1456932773000 92 }); 93 } 94 95 describe('IntermediateCommitSet', () => { 96 MockRemoteAPI.inject(); 97 MockModels.inject(); 98 99 describe('setCommitForRepository', () => { 100 it('should allow set commit for owner repository', () => { 101 const commitSet = new IntermediateCommitSet(new CommitSet); 102 const commit = ownerCommit(); 103 commitSet.setCommitForRepository(MockModels.ownerRepository, commit); 104 assert.equal(commit, commitSet.commitForRepository(MockModels.ownerRepository)); 105 }); 106 107 it('should allow set commit for owned repository', () => { 108 const commitSet = new IntermediateCommitSet(new CommitSet); 109 const commit = ownerCommit(); 110 111 const fetchingPromise = commit.fetchOwnedCommits(); 112 const requests = MockRemoteAPI.requests; 113 assert.equal(requests.length, 1); 114 assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=owner-commit-0'); 115 assert.equal(requests[0].method, 'GET'); 116 117 requests[0].resolve({commits: [{ 118 id: 233, 119 repository: MockModels.ownedRepository.id(), 120 revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb', 121 time: +(new Date('2016-05-13T00:55:57.841344Z')), 122 }]}); 123 124 return fetchingPromise.then(() => { 125 const ownedCommit = commit.ownedCommits()[0]; 126 commitSet.setCommitForRepository(MockModels.ownerRepository, commit); 127 commitSet.setCommitForRepository(MockModels.ownedRepository, ownedCommit); 128 assert.equal(commit, commitSet.commitForRepository(MockModels.ownerRepository)); 129 assert.equal(ownedCommit, commitSet.commitForRepository(MockModels.ownedRepository)); 130 assert.deepEqual(commitSet.repositories(), [MockModels.ownerRepository, MockModels.ownedRepository]); 131 }); 132 }); 133 }); 134 135 describe('fetchCommitLogs', () => { 136 137 it('should fetch CommitLog object with owned commits information', () => { 138 const commit = partialOwnerCommit(); 139 assert.equal(commit.ownsCommits(), null); 140 const owned = ownedCommit(); 141 142 const commitSet = CommitSet.ensureSingleton('53246456', {revisionItems: [{commit}, {commit: owned, ownerCommit: commit}]}); 143 const intermediateCommitSet =new IntermediateCommitSet(commitSet); 144 const fetchingPromise = intermediateCommitSet.fetchCommitLogs(); 145 146 const requests = MockRemoteAPI.requests; 147 assert.equal(requests.length, 2); 148 assert.equal(requests[0].url, '/api/commits/111/owner-commit-0'); 149 assert.equal(requests[0].method, 'GET'); 150 assert.equal(requests[1].url, '/api/commits/112/owned-commit-0'); 151 assert.equal(requests[1].method, 'GET'); 152 153 requests[0].resolve({commits: [{ 154 id: 5, 155 repository: MockModels.ownerRepository, 156 revision: 'owner-commit-0', 157 ownsCommits: true, 158 time: +(new Date('2016-05-13T00:55:57.841344Z')), 159 }]}); 160 requests[1].resolve({commits: [{ 161 id: 6, 162 repository: MockModels.ownedRepository, 163 revision: 'owned-commit-0', 164 ownsCommits: false, 165 time: 1456932774000, 166 }]}); 167 168 return MockRemoteAPI.waitForRequest().then(() => { 169 assert.equal(requests.length, 3); 170 assert.equal(requests[2].url, '../api/commits/111/owned-commits?owner-revision=owner-commit-0'); 171 assert.equal(requests[2].method, 'GET'); 172 173 requests[2].resolve({commits: [{ 174 id: 6, 175 repository: MockModels.ownedRepository.id(), 176 revision: 'owned-commit-0', 177 ownsCommits: false, 178 time: 1456932774000, 179 }]}); 180 return fetchingPromise; 181 }).then(() => { 182 assert(commit.ownsCommits()); 183 assert.equal(commit.ownedCommits().length, 1); 184 assert.equal(commit.ownedCommits()[0], owned); 185 assert.equal(owned.ownerCommit(), commit); 186 assert.equal(owned.repository(), MockModels.ownedRepository); 187 assert.equal(intermediateCommitSet.commitForRepository(MockModels.ownedRepository), owned); 188 assert.equal(intermediateCommitSet.ownerCommitForRepository(MockModels.ownedRepository), commit); 189 assert.deepEqual(intermediateCommitSet.repositories(), [MockModels.ownerRepository, MockModels.ownedRepository]); 190 }); 191 }); 192 }); 193 194 describe('updateRevisionForOwnerRepository', () => { 195 196 it('should update CommitSet based on the latest invocation', () => { 197 const commitSet = new IntermediateCommitSet(new CommitSet); 198 const firstUpdatePromise = commitSet.updateRevisionForOwnerRepository(MockModels.webkit, 'webkit-commit-0'); 199 const secondUpdatePromise = commitSet.updateRevisionForOwnerRepository(MockModels.webkit, 'webkit-commit-1'); 200 const requests = MockRemoteAPI.requests; 201 202 assert(requests.length, 2); 203 assert.equal(requests[0].url, '/api/commits/11/webkit-commit-0'); 204 assert.equal(requests[0].method, 'GET'); 205 assert.equal(requests[1].url, '/api/commits/11/webkit-commit-1'); 206 assert.equal(requests[1].method, 'GET'); 207 208 requests[1].resolve({commits: [{ 209 id: 2018, 210 repository: MockModels.webkit.id(), 211 revision: 'webkit-commit-1', 212 ownsCommits: false, 213 time: 1456932774000, 214 }]}); 215 216 let commit = null; 217 return secondUpdatePromise.then(() => { 218 commit = commitSet.commitForRepository(MockModels.webkit); 219 220 requests[0].resolve({commits: [{ 221 id: 2017, 222 repository: MockModels.webkit.id(), 223 revision: 'webkit-commit-0', 224 ownsCommits: false, 225 time: 1456932773000, 226 }]}); 227 228 assert.equal(commit.revision(), 'webkit-commit-1'); 229 assert.equal(commit.id(), 2018); 230 231 return firstUpdatePromise; 232 }).then(() => { 233 const currentCommit = commitSet.commitForRepository(MockModels.webkit); 234 assert.equal(commit, currentCommit); 235 }); 236 }); 237 238 }); 239 240 describe('removeCommitForRepository', () => { 241 it('should remove owned commits when owner commit is removed', () => { 242 const commitSet = new IntermediateCommitSet(new CommitSet); 243 const commit = ownerCommit(); 244 245 const fetchingPromise = commit.fetchOwnedCommits(); 246 const requests = MockRemoteAPI.requests; 247 assert.equal(requests.length, 1); 248 assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=owner-commit-0'); 249 assert.equal(requests[0].method, 'GET'); 250 251 requests[0].resolve({commits: [{ 252 id: 233, 253 repository: MockModels.ownedRepository.id(), 254 revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb', 255 ownsCommits: true 256 }]}); 257 258 return fetchingPromise.then(() => { 259 commitSet.setCommitForRepository(MockModels.ownerRepository, commit); 260 commitSet.setCommitForRepository(MockModels.ownedRepository, commit.ownedCommits()[0]); 261 commitSet.removeCommitForRepository(MockModels.ownerRepository); 262 assert.deepEqual(commitSet.repositories(), []); 263 }); 264 }); 265 266 it('should not remove owner commits when owned commit is removed', () => { 267 const commitSet = new IntermediateCommitSet(new CommitSet); 268 const commit = ownerCommit(); 269 270 const fetchingPromise = commit.fetchOwnedCommits(); 271 const requests = MockRemoteAPI.requests; 272 assert.equal(requests.length, 1); 273 assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=owner-commit-0'); 274 assert.equal(requests[0].method, 'GET'); 275 276 requests[0].resolve({commits: [{ 277 id: 233, 278 repository: MockModels.ownedRepository.id(), 279 revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb', 280 time: +(new Date('2016-05-13T00:55:57.841344Z')), 281 }]}); 282 283 return fetchingPromise.then(() => { 284 commitSet.setCommitForRepository(MockModels.ownerRepository, commit); 285 commitSet.setCommitForRepository(MockModels.ownedRepository, commit.ownedCommits()[0]); 286 commitSet.removeCommitForRepository(MockModels.ownedRepository); 287 assert.deepEqual(commitSet.repositories(), [MockModels.ownerRepository]); 288 }); 289 }); 290 291 it('should not update commit set for repository if removeCommitForRepository called before updateRevisionForOwnerRepository finishes', () => { 292 const commitSet = new IntermediateCommitSet(new CommitSet); 293 const commit = webkitCommit(); 294 commitSet.setCommitForRepository(MockModels.webkit, commit); 295 const updatePromise = commitSet.updateRevisionForOwnerRepository(MockModels.webkit, 'webkit-commit-1'); 296 297 commitSet.removeCommitForRepository(MockModels.webkit); 298 299 const requests = MockRemoteAPI.requests; 300 assert.equal(requests[0].url, '/api/commits/11/webkit-commit-1'); 301 assert.equal(requests[0].method, 'GET'); 302 303 requests[0].resolve({commits: [{ 304 id: 2018, 305 repository: MockModels.webkit.id(), 306 revision: 'webkit-commit-1', 307 ownsCommits: false, 308 time: 1456932774000, 309 }]}); 310 311 return updatePromise.then(() => { 312 assert.deepEqual(commitSet.repositories(), []); 313 assert(!commitSet.commitForRepository(MockModels.webkit)); 314 }); 315 }); 316 317 }); 318 319 }); 53 320 54 321 describe('CustomCommitSet', () => {
Note: See TracChangeset
for help on using the changeset viewer.