Diff
Modified: trunk/Websites/perf.webkit.org/ChangeLog (226258 => 226259)
--- trunk/Websites/perf.webkit.org/ChangeLog 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/ChangeLog 2017-12-22 06:25:24 UTC (rev 226259)
@@ -1,3 +1,94 @@
+2017-12-21 Dewei Zhu <dewei_...@apple.com>
+
+ Add UI for A/B testing on owned commits.
+ https://bugs.webkit.org/show_bug.cgi?id=177993
+
+ Reviewed by Ryosuke Niwa.
+
+ Customizable test group form should support specifying and A/B testing owned commits.
+ Introduce 'IntermediateCommitSet' to achieve the goal of specifying owned commits for A/B test.
+ In order to support configure A/B testing that may need to add/remove owned commits, CommitSet may be the
+ closest thing we can get. However, it is a subclass of DataModelObject, which means CommitSet is a representation
+ of 'commit_sets' table and can only be updated from server data. Thus, we want something like CustomCommitSet that
+ is not a representation of database table, but unlike CustomCommitSet, it should store information about commits
+ rather than a revision. As a result, IntermediateCommitSet is introduced. For a longer term, we may replace
+ CustomCommitSet with IntermediateCommitSet as it carries more information and could potentially simplify some
+ CustomCommitSet related APIs by using commit id instead of commit revision.
+ Extend ButtonBase class so that we can enable/disable a button.
+
+ * public/v3/components/button-base.js:
+ (ButtonBase):
+ (ButtonBase.prototype.setDisabled): Enable/disable a button.
+ (ButtonBase.prototype.render):
+ (ButtonBase.cssTemplate): Added css rule for disabled button.
+ * public/v3/components/combo-box.js: Added.
+ (ComboBox):
+ (ComboBox.prototype.didConstructShadowTree): Setup text field.
+ (ComboBox.prototype.render):
+ (ComboBox.prototype._candidateNameForCurrentIndex): Returns candidate name based on current index.
+ (ComboBox.prototype._candidateElementForCurrentIndex): Returns a list element based on current index.
+ (ComboBox.prototype._autoCompleteIfOnlyOneMatchingItem): Supports auto completion.
+ (ComboBox.prototype._moveCandidate): Supports arrow up/down.
+ (ComboBox.prototype._updateCandidateList): Hide/unhide candidate list and high-light selected candidate.
+ (ComboBox.prototype._renderCandidateList): Render candidate list base on value on text field.
+ (ComboBox.htmlTemplate):
+ (ComboBox.cssTemplate):
+ * public/v3/components/customizable-test-group-form.js:
+ (CustomizableTestGroupForm):
+ (CustomizableTestGroupForm.prototype.didConstructShadowTree): Only fetch the full commits when we about to create a customized A/B tests.
+ (CustomizableTestGroupForm.prototype._computeCommitSetMap): Compute the CustomCommitSet based on IntermediateCommitSet and
+ other revision related information in some map.
+ (CustomizableTestGroupForm.prototype.render):
+ (CustomizableTestGroupForm.prototype._renderCustomRevisionTable):
+ (CustomizableTestGroupForm.prototype._constructTableBodyList): This function builds table body for each highest level repository.
+ It will also include the owned repository rows in the same table body if the commits for highest level repository owns other commits.
+ (CustomizableTestGroupForm.prototype._constructTableRowForCommitsWithoutOwner): Build a table row for a highest level repository.
+ (CustomizableTestGroupForm.prototype._constructTableRowForCommitsWithOwner): Build a table row for repository with owner.
+ (CustomizableTestGroupForm.prototype._constructTableRowForIncompleteOwnedCommits): Build a table row for an unspecified repository.
+ (CustomizableTestGroupForm.prototype._constructRevisionRadioButtons): Update the logic to support build radio buttons for the owned repository rows.
+ (CustomizableTestGroupForm.cssTemplate):
+ * public/v3/components/minus-button.js: Added.
+ (MinusButton):
+ (MinusButton.buttonContent):
+ * public/v3/components/owned-commit-viewer.js:
+ (OwnedCommitViewer.prototype._renderOwnedCommitTable):
+ * public/v3/components/plus-button.js: Added.
+ (PlusButton):
+ (PlusButton.buttonContent):
+ * public/v3/index.html: Added new components.
+ * public/v3/models/commit-log.js: Added owner and owned commit information.
+ (CommitLog):
+ (CommitLog.prototype.ownedCommits): Returns a list of commits owned by current commit.
+ (CommitLog.prototype.ownerCommit): Return owner commit of current commit.
+ (CommitLog.prototype.setOwnerCommits): Set owner commit of current commit.
+ (CommitLog.prototype.label): Remove unnecessary 'else'.
+ (CommitLog.prototype.diff): Remove unused 'fromRevisionForURL' and tiny code cleanup.
+ (CommitLog.prototype.ownedCommitForOwnedRepository):
+ (CommitLog.prototype.fetchOwnedCommits): Sets the owner for those owned commits. The owner of a commit with multiple owner
+ commits will be overwritten by each time this function is called.
+ (CommitLog.ownedCommitDifferenceForOwnerCommits): A more generic version of diffOwnedCommits. diffOwnedCommits only accepts 2 commits,
+ but ownedCommitDifferenceForOwnerCommits supports multiple commits.
+ (CommitLog.diffOwnedCommits): Deleted and should use 'CommitLog.ownedCommitDifferenceForOwnerCommits' instead.
+ * public/v3/models/commit-set.js:
+ (CommitSet.prototype.topLevelRepositories):
+ (CommitSet.prototype.commitForRepository):
+ (IntermediateCommitSet): Take CommitSet as argument, note the commit from CommitSet doesn't contains full information of the commit.
+ Always call 'fetchFullCommits' once before any further usages.
+ (IntermediateCommitSet.prototype.fetchCommitLogs): Fetch all commits information in current commit set.
+ (IntermediateCommitSet.prototype._fetchCommitLogAndOwnedCommits): Fetch commit log and owned commits if necessary.
+ (IntermediateCommitSet.prototype.updateRevisionForOwnerRepository): Updates a commit for a repository by given a revision of the repository.
+ (IntermediateCommitSet.prototype.setCommitForRepository): Sets a commit for a repository in commit set.
+ (IntermediateCommitSet.prototype.removeCommitForRepository): Removes a commit for a repository in commit set.
+ (IntermediateCommitSet.prototype.ownsCommitsForRepository): Returns whether the commit for repository owns commits.
+ (IntermediateCommitSet.prototype.repositories): Returns all repositories in the commit set.
+ (IntermediateCommitSet.prototype.highestLevelRepositories): Returns all repositories those don't have an owner.
+ (IntermediateCommitSet.prototype.commitForRepository): Returns a commit for a given repository.
+ (IntermediateCommitSet.prototype.ownedRepositoriesForOwnerRepository): Returns all repositories owned by a given repository in current commit set.
+ (IntermediateCommitSet.prototype.ownerCommitForRepository): Returns owner commit for a given owned repository.
+ * tools/js/v3-models.js: Added import for 'IntermediateCommitSet'.
+ * unit-tests/commit-log-tests.js: Updated unittest which tests 'ownerCommit' function.
+ * unit-tests/commit-set-tests.js: Added unit tests for IntermediateCommitSet.
+
2017-12-13 Dewei Zhu <dewei_...@apple.com>
Add a test freshness page.
Modified: trunk/Websites/perf.webkit.org/public/v3/components/button-base.js (226258 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/components/button-base.js 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/public/v3/components/button-base.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -1,5 +1,18 @@
class ButtonBase extends ComponentBase {
+
+ constructor(name)
+ {
+ super(name);
+ this._disabled = false;
+ }
+
+ setDisabled(disabled)
+ {
+ this._disabled = disabled;
+ this.enqueueToRender();
+ }
+
didConstructShadowTree()
{
this.content('button').addEventListener('click', this.createEventHandler(() => {
@@ -7,6 +20,15 @@
}));
}
+ render()
+ {
+ super.render();
+ if (this._disabled)
+ this.content('button').setAttribute('disabled', '');
+ else
+ this.content('button').removeAttribute('disabled');
+ }
+
static htmlTemplate()
{
return `<a id="button" href="" viewBox="0 0 100 100">${this.buttonContent()}</svg></a>`;
@@ -35,6 +57,12 @@
opacity: 0.6;
}
+ a[disabled] {
+ pointer-events: none;
+ cursor: default;
+ opacity: 0.2;
+ }
+
svg {
display: block;
}
Added: trunk/Websites/perf.webkit.org/public/v3/components/combo-box.js (0 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/components/combo-box.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/combo-box.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -0,0 +1,205 @@
+class ComboBox extends ComponentBase {
+
+ constructor(candidates, maxCandidateListLength)
+ {
+ super('combo-box');
+ this._candidates = candidates;
+ this._maxCandidateListLength = maxCandidateListLength || 1000;
+ this._candidateList = [];
+ this._currentCandidateIndex = null;
+ this._showCandidateList = false;
+ this._renderCandidateListLazily = new LazilyEvaluatedFunction(this._renderCandidateList.bind(this));
+ this._updateCandidateListLazily = new LazilyEvaluatedFunction(this._updateCandidateList.bind(this));
+ }
+
+ didConstructShadowTree()
+ {
+ super.didConstructShadowTree();
+ const textField = this.content('text-field');
+ textField.addEventListener('input', () => {
+ this._showCandidateList = true;
+ this._currentCandidateIndex = null;
+ this.enqueueToRender();
+ });
+ textField.addEventListener('change', () => {
+ this._autoCompleteIfOnlyOneMatchingItem();
+ this._currentCandidateIndex = null;
+ });
+ textField.addEventListener('blur', () => {
+ this._showCandidateList = false;
+ this._autoCompleteIfOnlyOneMatchingItem();
+ this.enqueueToRender();
+ });
+ textField.addEventListener('focus', () => {
+ this._showCandidateList = true;
+ this.enqueueToRender();
+ });
+ textField.addEventListener('keydown', (event) => {
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp')
+ this._moveCandidate(event.key === 'ArrowDown');
+ else if (event.key === 'Tab' || event.key === 'Enter') {
+ let candidate = this._currentCandidateIndex === null ? null : this._candidateList[this._currentCandidateIndex];
+ if (!candidate && this._candidateList.length === 1)
+ candidate = this._candidateList[0];
+ if (candidate)
+ this.dispatchAction('update', candidate);
+ }
+ });
+ }
+
+ render()
+ {
+ super.render();
+
+ const candidateElementList = this._renderCandidateListLazily.evaluate(this._candidates, this.content('text-field').value);
+
+ console.assert(this._currentCandidateIndex === null || (this._currentCandidateIndex >= 0 && this._currentCandidateIndex < candidateElementList.length));
+ const selectedCandidateElement = this._currentCandidateIndex === null ? null : candidateElementList[this._currentCandidateIndex];
+ this._updateCandidateListLazily.evaluate(selectedCandidateElement, this._showCandidateList);
+ }
+
+ _autoCompleteIfOnlyOneMatchingItem()
+ {
+ const textFieldValueInLowerCase = this.content('text-field').value.toLowerCase();
+ if (!textFieldValueInLowerCase.length)
+ return;
+
+ let matchingCandidateCount = 0;
+ let matchingCandidate = null;
+ for (const candidate of this._candidates) {
+ if (candidate.toLowerCase().includes(textFieldValueInLowerCase)) {
+ matchingCandidateCount += 1;
+ matchingCandidate = candidate;
+ }
+ if (matchingCandidateCount > 1)
+ break;
+ }
+ if (matchingCandidateCount === 1)
+ this.dispatchAction('update', matchingCandidate);
+ }
+
+ _moveCandidate(forward)
+ {
+ const candidateListLength = this._candidateList.length;
+ if (!candidateListLength)
+ return;
+ let newIndex = this._currentCandidateIndex;
+ if (newIndex === null)
+ newIndex = forward ? 0 : candidateListLength - 1;
+ else {
+ newIndex += forward ? 1 : -1;
+ if (newIndex >= this._candidateList.length)
+ newIndex = this._candidateList.length - 1;
+ if (newIndex < 0)
+ newIndex = 0;
+ }
+ this._currentCandidateIndex = newIndex;
+ this.enqueueToRender();
+ }
+
+ _renderCandidateList(candidates, key)
+ {
+ const element = ComponentBase.createElement;
+ const link = ComponentBase.createLink;
+ const candidatesStartingWithKey = [];
+ const candidatesContainingKey = [];
+ for (const candidate of candidates) {
+ const searchResult = candidate.toLowerCase().indexOf(key.toLowerCase());
+ if (key && searchResult < 0)
+ continue;
+ if (!searchResult)
+ candidatesStartingWithKey.push(candidate);
+ else
+ candidatesContainingKey.push(candidate);
+ }
+ this._candidateList = candidatesStartingWithKey.concat(candidatesContainingKey).slice(0, this._maxCandidateListLength);
+ const candidateElementList = this._candidateList.map((candidate) => {
+ const item = link(candidate, () => null);
+ // FIXME: We should use 'onlick' callback provided by 'ComponentBase.createLink'. However, in this case,
+ // 'blur' event will be triggered before 'onlick', we have to use 'mousedown' instead.
+ item.addEventListener('mousedown', () => {
+ this.dispatchAction('update', candidate);
+ this.enqueueToRender();
+ });
+ return element('li', item);
+ });
+
+ this.renderReplace(this.content('candidate-list'), candidateElementList);
+ return candidateElementList;
+ }
+
+ _updateCandidateList(selectedCandidateElement, showCandidateList)
+ {
+
+ const candidateList = this.content('candidate-list');
+ candidateList.style.display = showCandidateList ? null : 'none';
+
+ const previouslySelectedCandidateElement = candidateList.querySelector('.selected');
+ if (previouslySelectedCandidateElement)
+ previouslySelectedCandidateElement.classList.remove('selected');
+
+ if (selectedCandidateElement) {
+ selectedCandidateElement.className = 'selected';
+ selectedCandidateElement.scrollIntoViewIfNeeded();
+ }
+ }
+
+ static htmlTemplate()
+ {
+ return `<div id='combox-box'>
+ <input id='text-field'></input>
+ <ul id="candidate-list"></ul>
+ </div>`;
+ }
+
+ static cssTemplate()
+ {
+ return `
+ div {
+ position: relative;
+ height: 1.4rem;
+ left: 0;
+ }
+
+ ul:empty {
+ display: none;
+ }
+
+ ul {
+ transition: background 250ms ease-in;
+ margin: 0;
+ padding: 0.1rem 0.3rem;
+ list-style: none;
+ background: rgba(255, 255, 255, 0.95);
+ border: solid 1px #ccc;
+ top: 1.5rem;
+ border-radius: 0.2rem;
+ z-index: 10;
+ position: absolute;
+ min-width: 8.5rem;
+ max-height: 10rem;
+ overflow: auto;
+ }
+
+ li {
+ text-align: left;
+ margin: 0;
+ padding: 0;
+ }
+
+ li:hover,
+ li.selected {
+ background: rgba(204, 153, 51, 0.1);
+ }
+
+ li a {
+ text-decoration: none;
+ font-size: 0.8rem;
+ color: inherit;
+ display: block;
+ }
+ `;
+ }
+}
+
+ComponentBase.defineElement('combo-box', ComboBox);
Modified: trunk/Websites/perf.webkit.org/public/v3/components/customizable-test-group-form.js (226258 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/components/customizable-test-group-form.js 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/public/v3/components/customizable-test-group-form.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -1,4 +1,3 @@
-
class CustomizableTestGroupForm extends TestGroupForm {
constructor()
@@ -7,9 +6,11 @@
this._commitSetMap = null;
this._name = null;
this._isCustomized = false;
- this._revisionEditorMap = {};
-
- this._renderCustomRevisionTableLazily = new LazilyEvaluatedFunction(this._renderCustomRevisionTable.bind(this));
+ this._revisionEditorMap = null;
+ this._ownerRevisionMap = null;
+ this._checkedLabelByPosition = null;
+ this._hasIncompleteOwnedRepository = null;
+ this._fetchingCommitPromises = [];
}
setCommitSetMap(map)
@@ -16,6 +17,9 @@
{
this._commitSetMap = map;
this._isCustomized = false;
+ this._fetchingCommitPromises = [];
+ this._checkedLabelByPosition = new Map;
+ this._hasIncompleteOwnedRepository = new Map;
this.enqueueToRender();
}
@@ -32,9 +36,30 @@
nameControl._oninput_ = () => {
this._name = nameControl.value;
this.enqueueToRender();
- }
+ };
this.content('customize-link')._onclick_ = this.createEventHandler(() => {
+ if (!this._isCustomized) {
+ const originalCommitSetMap = this._commitSetMap;
+ const fetchingCommitPromises = [];
+ this._commitSetMap = new Map;
+ for (const label in originalCommitSetMap) {
+ const intermediateCommitSet = new IntermediateCommitSet(originalCommitSetMap[label]);
+ fetchingCommitPromises.push(intermediateCommitSet.fetchCommitLogs());
+ this._commitSetMap.set(label, intermediateCommitSet);
+ }
+ this._fetchingCommitPromises = fetchingCommitPromises;
+
+ return Promise.all(fetchingCommitPromises).then(() => {
+ if (this._fetchingCommitPromises !== fetchingCommitPromises)
+ return;
+ this._isCustomized = true;
+ this._fetchingCommitPromises = [];
+ for (const label in originalCommitSetMap)
+ this._checkedLabelByPosition.set(label, new Map);
+ this.enqueueToRender();
+ });
+ }
this._isCustomized = true;
this.enqueueToRender();
});
@@ -47,13 +72,14 @@
return this._commitSetMap;
const map = {};
- for (const label in this._commitSetMap) {
- const originalCommitSet = this._commitSetMap;
+ for (const [label, commitSet] of this._commitSetMap) {
const customCommitSet = new CustomCommitSet;
- for (let repository of this._commitSetMap[label].repositories()) {
- const revisionEditor = this._revisionEditorMap[label].get(repository);
+ for (const repository of commitSet.repositories()) {
+ const revisionEditor = this._revisionEditorMap.get(label).get(repository);
console.assert(revisionEditor);
- customCommitSet.setRevisionForRepository(repository, revisionEditor.value);
+ const ownerRevision = this._ownerRevisionMap.get(label).get(repository) || null;
+
+ customCommitSet.setRevisionForRepository(repository, revisionEditor.value, null, ownerRevision);
}
map[label] = customCommitSet;
}
@@ -67,7 +93,7 @@
this.content('start-button').disabled = !(this._commitSetMap && this._name);
this.content('customize-link-container').style.display = !this._commitSetMap ? 'none' : null;
- this._renderCustomRevisionTableLazily.evaluate(this._commitSetMap, this._isCustomized);
+ this._renderCustomRevisionTable(this._commitSetMap, this._isCustomized);
}
_renderCustomRevisionTable(commitSetMap, isCustomized)
@@ -74,17 +100,31 @@
{
if (!commitSetMap || !isCustomized) {
this.renderReplace(this.content('custom-table'), []);
- return null;
+ return;
}
const repositorySet = new Set;
+ const ownedRepositoriesByRepository = new Map;
const commitSetLabels = [];
- this._revisionEditorMap = {};
- for (const label in commitSetMap) {
- for (const repository of commitSetMap[label].repositories())
+ this._revisionEditorMap = new Map;
+ this._ownerRevisionMap = new Map;
+ for (const [label, commitSet] of commitSetMap) {
+ for (const repository of commitSet.highestLevelRepositories()) {
repositorySet.add(repository);
+ const ownedRepositories = commitSetMap.get(label).ownedRepositoriesForOwnerRepository(repository);
+
+ if (!ownedRepositories)
+ continue;
+
+ if (!ownedRepositoriesByRepository.has(repository))
+ ownedRepositoriesByRepository.set(repository, new Set);
+ const ownedRepositorySet = ownedRepositoriesByRepository.get(repository);
+ for (const ownedRepository of ownedRepositories)
+ ownedRepositorySet.add(ownedRepository)
+ }
commitSetLabels.push(label);
- this._revisionEditorMap[label] = new Map;
+ this._revisionEditorMap.set(label, new Map);
+ this._ownerRevisionMap.set(label, new Map);
}
const repositoryList = Repository.sortByNamePreferringOnesWithURL(Array.from(repositorySet.values()));
@@ -92,34 +132,145 @@
this.renderReplace(this.content('custom-table'), [
element('thead',
element('tr',
- [element('td', 'Repository'), commitSetLabels.map((label) => element('td', {colspan: commitSetLabels.length + 1}, label))])),
- element('tbody',
- repositoryList.map((repository) => {
- const cells = [element('th', repository.label())];
- for (const label in commitSetMap)
- cells.push(this._constructRevisionRadioButtons(commitSetMap, repository, label));
- return element('tr', cells);
- }))]);
+ [element('td', {colspan: 2}, 'Repository'), commitSetLabels.map((label) => element('td', {colspan: commitSetLabels.length + 1}, label)), element('td')])),
+ this._constructTableBodyList(repositoryList, commitSetMap, ownedRepositoriesByRepository, this._hasIncompleteOwnedRepository)]);
+ }
- return repositoryList;
+ _constructTableBodyList(repositoryList, commitSetMap, ownedRepositoriesByRepository, hasIncompleteOwnedRepository)
+ {
+ const element = ComponentBase.createElement;
+ const tableBodyList = [];
+ for(const repository of repositoryList) {
+ const rows = [];
+ const allCommitSetSpecifiesOwnerCommit = Array.from(commitSetMap.values()).every((commitSet) => commitSet.ownsCommitsForRepository(repository));
+ const hasIncompleteRow = hasIncompleteOwnedRepository.get(repository);
+ const ownedRepositories = ownedRepositoriesByRepository.get(repository);
+
+ rows.push(this._constructTableRowForCommitsWithoutOwner(commitSetMap, repository, allCommitSetSpecifiesOwnerCommit, hasIncompleteOwnedRepository));
+
+ if ((!ownedRepositories || !ownedRepositories.size) && !hasIncompleteRow) {
+ tableBodyList.push(element('tbody', rows));
+ continue;
+ }
+
+ if (ownedRepositories) {
+ for (const ownedRepository of ownedRepositories)
+ rows.push(this._constructTableRowForCommitsWithOwner(commitSetMap, ownedRepository, repository));
+ }
+
+ if (hasIncompleteRow) {
+ const commits = Array.from(commitSetMap.values()).map((commitSet) => commitSet.commitForRepository(repository));
+ const commitDiff = CommitLog.ownedCommitDifferenceForOwnerCommits(...commits);
+ rows.push(this._constructTableRowForIncompleteOwnedCommits(commitSetMap, repository, commitDiff));
+ }
+ tableBodyList.push(element('tbody', rows));
+ }
+ return tableBodyList;
}
- _constructRevisionRadioButtons(commitSetMap, repository, rowLabel)
+ _constructTableRowForCommitsWithoutOwner(commitSetMap, repository, ownsRepositories, hasIncompleteOwnedRepository)
{
const element = ComponentBase.createElement;
- const revisionEditor = element('input');
+ const cells = [element('th', {colspan: 2}, repository.label())];
- this._revisionEditorMap[rowLabel].set(repository, revisionEditor);
+ for (const label of commitSetMap.keys())
+ cells.push(this._constructRevisionRadioButtons(commitSetMap, repository, label, null, ownsRepositories));
+ if (ownsRepositories) {
+ const plusButton = new PlusButton();
+ plusButton.setDisabled(hasIncompleteOwnedRepository.get(repository));
+ plusButton.listenToAction('activate', () => {
+ this._hasIncompleteOwnedRepository.set(repository, true);
+ this.enqueueToRender();
+ });
+ cells.push(element('td', plusButton));
+ } else
+ cells.push(element('td'));
+
+ return element('tr', cells);
+ }
+
+ _constructTableRowForCommitsWithOwner(commitSetMap, repository, ownerRepository)
+ {
+ const element = ComponentBase.createElement;
+ const cells = [element('td', {class: 'owner-repository-label'}), element('th', repository.label())];
+ const minusButton = new MinusButton();
+
+ for (const label of commitSetMap.keys())
+ cells.push(this._constructRevisionRadioButtons(commitSetMap, repository, label, ownerRepository, false));
+
+ minusButton.listenToAction('activate', () => {
+ for (const commitSet of commitSetMap.values())
+ commitSet.removeCommitForRepository(repository)
+ this.enqueueToRender();
+ });
+ cells.push(element('td', minusButton));
+ return element('tr', cells);
+ }
+
+ _constructTableRowForIncompleteOwnedCommits(commitSetMap, ownerRepository, commitDiff)
+ {
+ const element = ComponentBase.createElement;
+ const configurationCount = commitSetMap.size;
+ const numberOfCellsPerConfiguration = configurationCount + 1;
+ const changedRepositories = Array.from(commitDiff.keys());
+ const minusButton = new MinusButton();
+ const comboBox = new ComboBox(changedRepositories.map((repository) => repository.name()).sort());
+
+ comboBox.listenToAction('update', (repositoryName) => {
+ const targetRepository = changedRepositories.find((repository) => repositoryName === repository.name());
+ const ownedCommitDifferenceForRepository = Array.from(commitDiff.get(targetRepository).values());
+ const commitSetList = Array.from(commitSetMap.values());
+
+ console.assert(ownedCommitDifferenceForRepository.length === commitSetList.length);
+ for (let i = 0; i < commitSetList.length; i++)
+ commitSetList[i].setCommitForRepository(targetRepository, ownedCommitDifferenceForRepository[i]);
+ this._hasIncompleteOwnedRepository.set(ownerRepository, false);
+ this.enqueueToRender();
+ });
+
+ const cells = [element('td', {class: 'owner-repository-label'}), element('th', comboBox), element('td', {colspan: configurationCount * numberOfCellsPerConfiguration})];
+
+ minusButton.listenToAction('activate', () => {
+ this._hasIncompleteOwnedRepository.set(ownerRepository, false);
+ this.enqueueToRender();
+ });
+ cells.push(element('td', minusButton));
+
+ return element('tr', cells);
+ }
+
+ _constructRevisionRadioButtons(commitSetMap, repository, rowLabel, ownerRepository, ownsRepositories)
+ {
+ const element = ComponentBase.createElement;
+ const revisionEditor = element('input', {disabled: !!ownerRepository,
+ onchange: () => {
+ if (!ownsRepositories)
+ return;
+ commitSetMap.get(rowLabel).updateRevisionForOwnerRepository(repository, revisionEditor.value).catch(
+ () => revisionEditor.value = '');
+ }});
+
+ this._revisionEditorMap.get(rowLabel).set(repository, revisionEditor);
+
const nodes = [];
- for (let labelToChoose in commitSetMap) {
- const commit = commitSetMap[labelToChoose].commitForRepository(repository);
- const checked = labelToChoose == rowLabel;
+ for (const labelToChoose of commitSetMap.keys()) {
+ const commit = commitSetMap.get(labelToChoose).commitForRepository(repository);
+ const checkedLabel = this._checkedLabelByPosition.get(rowLabel).get(repository) || rowLabel;
+ const checked = labelToChoose == checkedLabel;
const radioButton = element('input', {type: 'radio', name: `${rowLabel}-${repository.id()}-radio`, checked,
- onchange: () => { revisionEditor.value = commit ? commit.revision() : ''; }});
+ onchange: () => {
+ this._checkedLabelByPosition.get(rowLabel).set(repository, labelToChoose);
+ revisionEditor.value = commit ? commit.revision() : '';
+ if (commit && commit.ownerCommit())
+ this._ownerRevisionMap.get(rowLabel).set(repository, commit.ownerCommit().revision());
+ }});
- if (checked)
+ if (checked) {
revisionEditor.value = commit ? commit.revision() : '';
+ if (commit && commit.ownerCommit())
+ this._ownerRevisionMap.get(rowLabel).set(repository, commit.ownerCommit().revision());
+ }
nodes.push(element('td', element('label', [radioButton, labelToChoose])));
}
nodes.push(element('td', revisionEditor));
@@ -139,6 +290,21 @@
margin: 1rem 0;
}
+ #custom-table td.owner-repository-label {
+ border-top: solid 2px transparent;
+ border-bottom: solid 1px transparent;
+ min-width: 2rem;
+ text-align: right;
+ }
+
+ #custom-table tr:last-child td.owner-repository-label {
+ border-bottom: solid 1px #ddd;
+ }
+
+ #custom-table th {
+ min-width: 12rem;
+ }
+
#custom-table,
#custom-table td,
#custom-table th {
Added: trunk/Websites/perf.webkit.org/public/v3/components/minus-button.js (0 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/components/minus-button.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/minus-button.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -0,0 +1,17 @@
+
+class MinusButton extends ButtonBase {
+ constructor()
+ {
+ super('minus-button');
+ }
+
+ static buttonContent()
+ {
+ return `<g stroke="black" stroke-width="10" id="icon">
+ <circle cx="50" cy="50" r="40" fill="transparent"/>
+ <polygon points="25,50 75,50" />
+ </g>`;
+ }
+}
+
+ComponentBase.defineElement('minus-button', MinusButton);
Modified: trunk/Websites/perf.webkit.org/public/v3/components/owned-commit-viewer.js (226258 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/components/owned-commit-viewer.js 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/public/v3/components/owned-commit-viewer.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -43,7 +43,7 @@
if (!previousOwnedCommits || !currentOwnedCommits)
return;
- const difference = CommitLog.diffOwnedCommits(this._previousCommit, this._currentCommit);
+ const difference = CommitLog.ownedCommitDifferenceForOwnerCommits(this._previousCommit, this._currentCommit);
const sortedRepositories = Repository.sortByName([...difference.keys()]);
const element = ComponentBase.createElement;
Added: trunk/Websites/perf.webkit.org/public/v3/components/plus-button.js (0 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/components/plus-button.js (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/plus-button.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -0,0 +1,18 @@
+
+class PlusButton extends ButtonBase {
+ constructor()
+ {
+ super('plus-button');
+ }
+
+ static buttonContent()
+ {
+ return `<g stroke="black" stroke-width="10" id="icon">
+ <circle cx="50" cy="50" r="40" fill="transparent"/>
+ <polygon points="50,25 50,75" />
+ <polygon points="25,50 75,50" />
+ </g>`;
+ }
+}
+
+ComponentBase.defineElement('plus-button', PlusButton);
Modified: trunk/Websites/perf.webkit.org/public/v3/index.html (226258 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/index.html 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/public/v3/index.html 2017-12-22 06:25:24 UTC (rev 226259)
@@ -99,6 +99,9 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
+ <script src=""
+ <script src=""
<script src=""
<script src=""
Modified: trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js (226258 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -12,6 +12,8 @@
if (this._remoteId)
this.ensureNamedStaticMap('remoteId')[this._remoteId] = this;
this._ownedCommits = null;
+ this._ownerCommit = null;
+ this._ownedCommitByOwnedRepository = new Map;
}
updateSingleton(rawData)
@@ -36,13 +38,17 @@
message() { return this._rawData['message']; }
url() { return this._repository.urlForRevision(this._rawData['revision']); }
ownsCommits() { return this._rawData['ownsCommits']; }
+ ownedCommits() { return this._ownedCommits; }
+ ownerCommit() { return this._ownerCommit; }
+ setOwnerCommits(ownerCommit) { this._ownerCommit = ownerCommit; }
+
label()
{
- var revision = this.revision();
+ const revision = this.revision();
if (parseInt(revision) == revision) // e.g. r12345
return 'r' + revision;
- else if (revision.length == 40) // e.g. git hash
+ if (revision.length == 40) // e.g. git hash
return revision.substring(0, 8);
return revision;
}
@@ -59,12 +65,10 @@
const to = this.revision();
const from = previousCommit.revision();
- let fromRevisionForURL = from;
let label = null;
- if (parseInt(from) == from) { // e.g. r12345.
- fromRevisionForURL = (parseInt(from) + 1).toString;
+ if (parseInt(from) == from)// e.g. r12345.
label = `r${from}-r${this.revision()}`;
- } else if (to.length == 40) // e.g. git hash
+ else if (to.length == 40) // e.g. git hash
label = `${from.substring(0, 8)}..${to.substring(0, 8)}`;
else
label = `${from} - ${to}`;
@@ -86,6 +90,8 @@
});
}
+ ownedCommitForOwnedRepository(ownedRepository) { return this._ownedCommitByOwnedRepository.get(ownedRepository); }
+
fetchOwnedCommits()
{
if (!this.repository().ownedRepositories())
@@ -99,6 +105,10 @@
return CommitLog.cachedFetch(`../api/commits/${this.repository().id()}/owned-commits?owner-revision=${escape(this.revision())}`).then((data) => {
this._ownedCommits = CommitLog._constructFromRawData(data);
+ this._ownedCommits.forEach((ownedCommit) => {
+ ownedCommit.setOwnerCommits(this);
+ this._ownedCommitByOwnedRepository.set(ownedCommit.repository(), ownedCommit);
+ });
return this._ownedCommits;
});
}
@@ -111,25 +121,27 @@
return ownedCommitMap;
}
- static diffOwnedCommits(previousCommit, currentCommit)
+ static ownedCommitDifferenceForOwnerCommits(...commits)
{
- console.assert(previousCommit);
- console.assert(currentCommit);
- console.assert(previousCommit._ownedCommits);
- console.assert(currentCommit._ownedCommits);
+ console.assert(commits.length >= 2);
- const previousOwnedCommitMap = previousCommit._buildOwnedCommitMap();
- const currentOwnedCommitMap = currentCommit._buildOwnedCommitMap();
- const ownedCommitRepositories = new Set([...currentOwnedCommitMap.keys(), ...previousOwnedCommitMap.keys()]);
+ const ownedCommitRepositories = new Set;
+ const ownedCommitMapList = commits.map((commit) => {
+ console.assert(commit);
+ console.assert(commit._ownedCommits);
+ const ownedCommitMap = commit._buildOwnedCommitMap();
+ for (const repository of ownedCommitMap.keys())
+ ownedCommitRepositories.add(repository);
+ return ownedCommitMap;
+ });
+
const difference = new Map;
-
ownedCommitRepositories.forEach((ownedCommitRepository) => {
- const currentRevision = currentOwnedCommitMap.get(ownedCommitRepository);
- const previousRevision = previousOwnedCommitMap.get(ownedCommitRepository);
- if (currentRevision != previousRevision)
- difference.set(ownedCommitRepository, [previousRevision, currentRevision]);
+ const ownedCommits = ownedCommitMapList.map((ownedCommitMap) => ownedCommitMap.get(ownedCommitRepository));
+ const uniqueOwnedCommits = new Set(ownedCommits);
+ if (uniqueOwnedCommits.size > 1)
+ difference.set(ownedCommitRepository, ownedCommits);
});
-
return difference;
}
Modified: trunk/Websites/perf.webkit.org/public/v3/models/commit-set.js (226258 => 226259)
--- trunk/Websites/perf.webkit.org/public/v3/models/commit-set.js 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/public/v3/models/commit-set.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -70,8 +70,8 @@
commitForRepository(repository) { return this._repositoryToCommitMap.get(repository); }
ownerCommitForRepository(repository) { return this._repositoryToCommitOwnerMap.get(repository); }
topLevelRepositories() { return Repository.sortByNamePreferringOnesWithURL(this._repositories.filter((repository) => !this.ownerRevisionForRepository(repository))); }
-
ownedRepositoriesForOwnerRepository(repository) { return this._ownerRepositoryToOwnedRepositoriesMap.get(repository); }
+ commitForRepository(repository) { return this._repositoryToCommitMap.get(repository); }
revisionForRepository(repository)
{
@@ -248,8 +248,104 @@
}
}
+class IntermediateCommitSet {
+
+ constructor(commitSet)
+ {
+ console.assert(commitSet instanceof CommitSet);
+ this._commitByRepository = new Map;
+ this._ownerToOwnedRepositories = new Map;
+ this._fetchingPromiseByRepository = new Map;
+
+ for (const repository of commitSet.repositories())
+ this.setCommitForRepository(repository, commitSet.commitForRepository(repository), commitSet.ownerCommitForRepository(repository));
+ }
+
+ fetchCommitLogs()
+ {
+ const fetchingPromises = [];
+ for (const [repository, commit] of this._commitByRepository)
+ fetchingPromises.push(this._fetchCommitLogAndOwnedCommits(repository, commit.revision()));
+ return Promise.all(fetchingPromises);
+ }
+
+ _fetchCommitLogAndOwnedCommits(repository, revision)
+ {
+ return CommitLog.fetchForSingleRevision(repository, revision).then((commits) => {
+ console.assert(commits.length === 1);
+ const commit = commits[0];
+ if (!commit.ownsCommits())
+ return commit;
+ return commit.fetchOwnedCommits().then(() => commit);
+ });
+ }
+
+ updateRevisionForOwnerRepository(repository, revision)
+ {
+ const fetchingPromise = this._fetchCommitLogAndOwnedCommits(repository, revision);
+ this._fetchingPromiseByRepository.set(repository, fetchingPromise);
+ return fetchingPromise.then((commit) => {
+ const currentFetchingPromise = this._fetchingPromiseByRepository.get(repository);
+ if (currentFetchingPromise !== fetchingPromise)
+ return;
+ this._fetchingPromiseByRepository.set(repository, null);
+ this.setCommitForRepository(repository, commit);
+ });
+ }
+
+ setCommitForRepository(repository, commit, ownerCommit = null)
+ {
+ console.assert(repository instanceof Repository);
+ console.assert(commit instanceof CommitLog);
+ this._commitByRepository.set(repository, commit);
+ if (!ownerCommit)
+ ownerCommit = commit.ownerCommit();
+ if (ownerCommit) {
+ const ownerRepository = ownerCommit.repository();
+ if (!this._ownerToOwnedRepositories.has(ownerRepository))
+ this._ownerToOwnedRepositories.set(ownerRepository, new Set);
+ const repositorySet = this._ownerToOwnedRepositories.get(ownerRepository);
+ repositorySet.add(repository);
+ }
+ }
+
+ removeCommitForRepository(repository)
+ {
+ console.assert(repository instanceof Repository);
+ this._fetchingPromiseByRepository.set(repository, null);
+ const ownerCommit = this.ownerCommitForRepository(repository);
+ if (ownerCommit) {
+ const repositorySet = this._ownerToOwnedRepositories.get(ownerCommit.repository());
+ console.assert(repositorySet.has(repository));
+ repositorySet.delete(repository);
+ } else if (this._ownerToOwnedRepositories.has(repository)) {
+ const ownedRepositories = this._ownerToOwnedRepositories.get(repository);
+ for (const ownedRepository of ownedRepositories)
+ this._commitByRepository.delete(ownedRepository);
+ this._ownerToOwnedRepositories.delete(repository);
+ }
+ this._commitByRepository.delete(repository);
+ }
+
+ ownsCommitsForRepository(repository) { return this.commitForRepository(repository).ownsCommits(); }
+
+ repositories() { return Array.from(this._commitByRepository.keys()); }
+ highestLevelRepositories() { return Repository.sortByNamePreferringOnesWithURL(this.repositories().filter((repository) => !this.ownerCommitForRepository(repository))); }
+ commitForRepository(repository) { return this._commitByRepository.get(repository); }
+ ownedRepositoriesForOwnerRepository(repository) { return this._ownerToOwnedRepositories.get(repository); }
+
+ ownerCommitForRepository(repository)
+ {
+ const commit = this._commitByRepository.get(repository);
+ if (!commit)
+ return null;
+ return commit.ownerCommit();
+ }
+}
+
if (typeof module != 'undefined') {
module.exports.CommitSet = CommitSet;
module.exports.MeasurementCommitSet = MeasurementCommitSet;
module.exports.CustomCommitSet = CustomCommitSet;
+ module.exports.IntermediateCommitSet = IntermediateCommitSet;
}
Modified: trunk/Websites/perf.webkit.org/tools/js/v3-models.js (226258 => 226259)
--- trunk/Websites/perf.webkit.org/tools/js/v3-models.js 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/tools/js/v3-models.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -16,7 +16,6 @@
importFromV3('models/builder.js', 'Build');
importFromV3('models/builder.js', 'Builder');
importFromV3('models/commit-log.js', 'CommitLog');
-importFromV3('models/commit-set.js', 'CustomCommitSet')
importFromV3('models/manifest.js', 'Manifest');
importFromV3('models/measurement-adaptor.js', 'MeasurementAdaptor');
importFromV3('models/measurement-cluster.js', 'MeasurementCluster');
@@ -27,6 +26,7 @@
importFromV3('models/commit-set.js', 'MeasurementCommitSet');
importFromV3('models/commit-set.js', 'CommitSet');
importFromV3('models/commit-set.js', 'CustomCommitSet');
+importFromV3('models/commit-set.js', 'IntermediateCommitSet');
importFromV3('models/test.js', 'Test');
importFromV3('models/test-group.js', 'TestGroup');
importFromV3('models/time-series.js', 'TimeSeries');
Modified: trunk/Websites/perf.webkit.org/unit-tests/commit-log-tests.js (226258 => 226259)
--- trunk/Websites/perf.webkit.org/unit-tests/commit-log-tests.js 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/unit-tests/commit-log-tests.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -191,7 +191,8 @@
});
it('should return owned-commit for a valid commit revision', () => {
- const fetchingPromise = ownerCommit().fetchOwnedCommits();
+ const commit = ownerCommit();
+ const fetchingPromise = commit.fetchOwnedCommits();
const requests = MockRemoteAPI.requests;
assert.equal(requests.length, 1);
assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=10.11.4%2015E65');
@@ -208,6 +209,7 @@
assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository);
assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb');
assert.equal(ownedCommits[0].id(), 233);
+ assert.equal(ownedCommits[0].ownerCommit(), commit);
});
});
@@ -233,6 +235,7 @@
assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository);
assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb');
assert.equal(ownedCommits[0].id(), 233);
+ assert.equal(ownedCommits[0].ownerCommit(), commit);
return commit.fetchOwnedCommits();
}).then((ownedCommits) => {
assert.equal(requests.length, 1);
@@ -241,12 +244,12 @@
});
});
- describe('diffOwnedCommits', () => {
+ describe('ownedCommitDifferenceForOwnerCommits', () => {
beforeEach(() => {
MockRemoteAPI.reset();
});
- it('should return difference between 2 owned-commits', () => {
+ it('should return difference between owned-commits of 2 owner commits', () => {
const _oneCommit_ = ownerCommit();
const otherCommit = otherOwnerCommit();
const fetchingPromise = oneCommit.fetchOwnedCommits();
@@ -267,7 +270,17 @@
time: +(new Date('2016-05-13T00:55:57.841344Z')),
}]});
- return fetchingPromise.then(() => {
+ return fetchingPromise.then((ownedCommits) => {
+ assert.equal(ownedCommits.length, 2);
+ assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository);
+ assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb');
+ assert.equal(ownedCommits[0].id(), 233);
+ assert.equal(ownedCommits[0].ownerCommit(), oneCommit);
+ assert.equal(ownedCommits[1].repository(), MockModels.webkitGit);
+ assert.equal(ownedCommits[1].revision(), '04a6c72038f0b771a19248ca2549e1258617b5fc');
+ assert.equal(ownedCommits[1].id(), 299);
+ assert.equal(ownedCommits[1].ownerCommit(), oneCommit);
+
const otherFetchingPromise = otherCommit.fetchOwnedCommits();
assert.equal(requests.length, 2);
assert.equal(requests[1].url, '../api/commits/111/owned-commits?owner-revision=10.11.4%2015E66');
@@ -286,8 +299,17 @@
}]});
return otherFetchingPromise;
- }).then(() => {
- const difference = CommitLog.diffOwnedCommits(oneCommit, otherCommit);
+ }).then((ownedCommits) => {
+ assert.equal(ownedCommits.length, 2);
+ assert.equal(ownedCommits[0].repository(), MockModels.ownedRepository);
+ assert.equal(ownedCommits[0].revision(), 'd5099e03b482abdd77f6c4dcb875afd05bda5ab8');
+ assert.equal(ownedCommits[0].id(), 234);
+ assert.equal(ownedCommits[0].ownerCommit(), otherCommit);
+ assert.equal(ownedCommits[1].repository(), MockModels.webkitGit);
+ assert.equal(ownedCommits[1].revision(), '04a6c72038f0b771a19248ca2549e1258617b5fc');
+ assert.equal(ownedCommits[1].id(), 299);
+ assert.equal(ownedCommits[1].ownerCommit(), otherCommit);
+ const difference = CommitLog.ownedCommitDifferenceForOwnerCommits(oneCommit, otherCommit);
assert.equal(difference.size, 1);
assert.equal(difference.keys().next().value, MockModels.ownedRepository);
});
Modified: trunk/Websites/perf.webkit.org/unit-tests/commit-set-tests.js (226258 => 226259)
--- trunk/Websites/perf.webkit.org/unit-tests/commit-set-tests.js 2017-12-22 05:37:47 UTC (rev 226258)
+++ trunk/Websites/perf.webkit.org/unit-tests/commit-set-tests.js 2017-12-22 06:25:24 UTC (rev 226259)
@@ -3,6 +3,7 @@
const assert = require('assert');
require('../tools/js/v3-models.js');
const MockModels = require('./resources/mock-v3-models.js').MockModels;
+const MockRemoteAPI = require('../unit-tests/resources/mock-remote-api.js').MockRemoteAPI;
function createPatch()
{
@@ -51,6 +52,272 @@
return customCommitSet;
}
+function ownerCommit()
+{
+ return new CommitLog(5, {
+ repository: MockModels.ownerRepository,
+ revision: 'owner-commit-0',
+ ownsCommits: true,
+ time: null,
+ });
+}
+
+function partialOwnerCommit()
+{
+ return new CommitLog(5, {
+ repository: MockModels.ownerRepository,
+ revision: 'owner-commit-0',
+ ownsCommits: null,
+ time: +(new Date('2016-05-13T00:55:57.841344Z')),
+ });
+}
+
+function ownedCommit()
+{
+ return new CommitLog(6, {
+ repository: MockModels.ownedRepository,
+ revision: 'owned-commit-0',
+ ownsCommits: null,
+ time: 1456932774000
+ });
+}
+
+function webkitCommit()
+{
+ return new CommitLog(2017, {
+ repository: MockModels.webkit,
+ revision: 'webkit-commit-0',
+ ownsCommits: false,
+ time: 1456932773000
+ });
+}
+
+describe('IntermediateCommitSet', () => {
+ MockRemoteAPI.inject();
+ MockModels.inject();
+
+ describe('setCommitForRepository', () => {
+ it('should allow set commit for owner repository', () => {
+ const commitSet = new IntermediateCommitSet(new CommitSet);
+ const commit = ownerCommit();
+ commitSet.setCommitForRepository(MockModels.ownerRepository, commit);
+ assert.equal(commit, commitSet.commitForRepository(MockModels.ownerRepository));
+ });
+
+ it('should allow set commit for owned repository', () => {
+ const commitSet = new IntermediateCommitSet(new CommitSet);
+ const commit = ownerCommit();
+
+ const fetchingPromise = commit.fetchOwnedCommits();
+ const requests = MockRemoteAPI.requests;
+ assert.equal(requests.length, 1);
+ assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=owner-commit-0');
+ assert.equal(requests[0].method, 'GET');
+
+ requests[0].resolve({commits: [{
+ id: 233,
+ repository: MockModels.ownedRepository.id(),
+ revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb',
+ time: +(new Date('2016-05-13T00:55:57.841344Z')),
+ }]});
+
+ return fetchingPromise.then(() => {
+ const ownedCommit = commit.ownedCommits()[0];
+ commitSet.setCommitForRepository(MockModels.ownerRepository, commit);
+ commitSet.setCommitForRepository(MockModels.ownedRepository, ownedCommit);
+ assert.equal(commit, commitSet.commitForRepository(MockModels.ownerRepository));
+ assert.equal(ownedCommit, commitSet.commitForRepository(MockModels.ownedRepository));
+ assert.deepEqual(commitSet.repositories(), [MockModels.ownerRepository, MockModels.ownedRepository]);
+ });
+ });
+ });
+
+ describe('fetchCommitLogs', () => {
+
+ it('should fetch CommitLog object with owned commits information', () => {
+ const commit = partialOwnerCommit();
+ assert.equal(commit.ownsCommits(), null);
+ const owned = ownedCommit();
+
+ const commitSet = CommitSet.ensureSingleton('53246456', {revisionItems: [{commit}, {commit: owned, ownerCommit: commit}]});
+ const intermediateCommitSet =new IntermediateCommitSet(commitSet);
+ const fetchingPromise = intermediateCommitSet.fetchCommitLogs();
+
+ const requests = MockRemoteAPI.requests;
+ assert.equal(requests.length, 2);
+ assert.equal(requests[0].url, '/api/commits/111/owner-commit-0');
+ assert.equal(requests[0].method, 'GET');
+ assert.equal(requests[1].url, '/api/commits/112/owned-commit-0');
+ assert.equal(requests[1].method, 'GET');
+
+ requests[0].resolve({commits: [{
+ id: 5,
+ repository: MockModels.ownerRepository,
+ revision: 'owner-commit-0',
+ ownsCommits: true,
+ time: +(new Date('2016-05-13T00:55:57.841344Z')),
+ }]});
+ requests[1].resolve({commits: [{
+ id: 6,
+ repository: MockModels.ownedRepository,
+ revision: 'owned-commit-0',
+ ownsCommits: false,
+ time: 1456932774000,
+ }]});
+
+ return MockRemoteAPI.waitForRequest().then(() => {
+ assert.equal(requests.length, 3);
+ assert.equal(requests[2].url, '../api/commits/111/owned-commits?owner-revision=owner-commit-0');
+ assert.equal(requests[2].method, 'GET');
+
+ requests[2].resolve({commits: [{
+ id: 6,
+ repository: MockModels.ownedRepository.id(),
+ revision: 'owned-commit-0',
+ ownsCommits: false,
+ time: 1456932774000,
+ }]});
+ return fetchingPromise;
+ }).then(() => {
+ assert(commit.ownsCommits());
+ assert.equal(commit.ownedCommits().length, 1);
+ assert.equal(commit.ownedCommits()[0], owned);
+ assert.equal(owned.ownerCommit(), commit);
+ assert.equal(owned.repository(), MockModels.ownedRepository);
+ assert.equal(intermediateCommitSet.commitForRepository(MockModels.ownedRepository), owned);
+ assert.equal(intermediateCommitSet.ownerCommitForRepository(MockModels.ownedRepository), commit);
+ assert.deepEqual(intermediateCommitSet.repositories(), [MockModels.ownerRepository, MockModels.ownedRepository]);
+ });
+ });
+ });
+
+ describe('updateRevisionForOwnerRepository', () => {
+
+ it('should update CommitSet based on the latest invocation', () => {
+ const commitSet = new IntermediateCommitSet(new CommitSet);
+ const firstUpdatePromise = commitSet.updateRevisionForOwnerRepository(MockModels.webkit, 'webkit-commit-0');
+ const secondUpdatePromise = commitSet.updateRevisionForOwnerRepository(MockModels.webkit, 'webkit-commit-1');
+ const requests = MockRemoteAPI.requests;
+
+ assert(requests.length, 2);
+ assert.equal(requests[0].url, '/api/commits/11/webkit-commit-0');
+ assert.equal(requests[0].method, 'GET');
+ assert.equal(requests[1].url, '/api/commits/11/webkit-commit-1');
+ assert.equal(requests[1].method, 'GET');
+
+ requests[1].resolve({commits: [{
+ id: 2018,
+ repository: MockModels.webkit.id(),
+ revision: 'webkit-commit-1',
+ ownsCommits: false,
+ time: 1456932774000,
+ }]});
+
+ let commit = null;
+ return secondUpdatePromise.then(() => {
+ commit = commitSet.commitForRepository(MockModels.webkit);
+
+ requests[0].resolve({commits: [{
+ id: 2017,
+ repository: MockModels.webkit.id(),
+ revision: 'webkit-commit-0',
+ ownsCommits: false,
+ time: 1456932773000,
+ }]});
+
+ assert.equal(commit.revision(), 'webkit-commit-1');
+ assert.equal(commit.id(), 2018);
+
+ return firstUpdatePromise;
+ }).then(() => {
+ const currentCommit = commitSet.commitForRepository(MockModels.webkit);
+ assert.equal(commit, currentCommit);
+ });
+ });
+
+ });
+
+ describe('removeCommitForRepository', () => {
+ it('should remove owned commits when owner commit is removed', () => {
+ const commitSet = new IntermediateCommitSet(new CommitSet);
+ const commit = ownerCommit();
+
+ const fetchingPromise = commit.fetchOwnedCommits();
+ const requests = MockRemoteAPI.requests;
+ assert.equal(requests.length, 1);
+ assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=owner-commit-0');
+ assert.equal(requests[0].method, 'GET');
+
+ requests[0].resolve({commits: [{
+ id: 233,
+ repository: MockModels.ownedRepository.id(),
+ revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb',
+ ownsCommits: true
+ }]});
+
+ return fetchingPromise.then(() => {
+ commitSet.setCommitForRepository(MockModels.ownerRepository, commit);
+ commitSet.setCommitForRepository(MockModels.ownedRepository, commit.ownedCommits()[0]);
+ commitSet.removeCommitForRepository(MockModels.ownerRepository);
+ assert.deepEqual(commitSet.repositories(), []);
+ });
+ });
+
+ it('should not remove owner commits when owned commit is removed', () => {
+ const commitSet = new IntermediateCommitSet(new CommitSet);
+ const commit = ownerCommit();
+
+ const fetchingPromise = commit.fetchOwnedCommits();
+ const requests = MockRemoteAPI.requests;
+ assert.equal(requests.length, 1);
+ assert.equal(requests[0].url, '../api/commits/111/owned-commits?owner-revision=owner-commit-0');
+ assert.equal(requests[0].method, 'GET');
+
+ requests[0].resolve({commits: [{
+ id: 233,
+ repository: MockModels.ownedRepository.id(),
+ revision: '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb',
+ time: +(new Date('2016-05-13T00:55:57.841344Z')),
+ }]});
+
+ return fetchingPromise.then(() => {
+ commitSet.setCommitForRepository(MockModels.ownerRepository, commit);
+ commitSet.setCommitForRepository(MockModels.ownedRepository, commit.ownedCommits()[0]);
+ commitSet.removeCommitForRepository(MockModels.ownedRepository);
+ assert.deepEqual(commitSet.repositories(), [MockModels.ownerRepository]);
+ });
+ });
+
+ it('should not update commit set for repository if removeCommitForRepository called before updateRevisionForOwnerRepository finishes', () => {
+ const commitSet = new IntermediateCommitSet(new CommitSet);
+ const commit = webkitCommit();
+ commitSet.setCommitForRepository(MockModels.webkit, commit);
+ const updatePromise = commitSet.updateRevisionForOwnerRepository(MockModels.webkit, 'webkit-commit-1');
+
+ commitSet.removeCommitForRepository(MockModels.webkit);
+
+ const requests = MockRemoteAPI.requests;
+ assert.equal(requests[0].url, '/api/commits/11/webkit-commit-1');
+ assert.equal(requests[0].method, 'GET');
+
+ requests[0].resolve({commits: [{
+ id: 2018,
+ repository: MockModels.webkit.id(),
+ revision: 'webkit-commit-1',
+ ownsCommits: false,
+ time: 1456932774000,
+ }]});
+
+ return updatePromise.then(() => {
+ assert.deepEqual(commitSet.repositories(), []);
+ assert(!commitSet.commitForRepository(MockModels.webkit));
+ });
+ });
+
+ });
+
+});
+
describe('CustomCommitSet', () => {
MockModels.inject();