Changeset 226259 in webkit


Ignore:
Timestamp:
Dec 21, 2017 10:25:24 PM (6 years ago)
Author:
Dewei Zhu
Message:

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.
Location:
trunk/Websites/perf.webkit.org
Files:
3 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/Websites/perf.webkit.org/ChangeLog

    r225898 r226259  
     12017-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
    1922017-12-13  Dewei Zhu  <dewei_zhu@apple.com>
    293
  • trunk/Websites/perf.webkit.org/public/v3/components/button-base.js

    r210938 r226259  
    11
    22class 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
    316    didConstructShadowTree()
    417    {
     
    619            this.dispatchAction('activate');
    720        }));
     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');
    830    }
    931
     
    3658            }
    3759
     60            a[disabled] {
     61                pointer-events: none;
     62                cursor: default;
     63                opacity: 0.2;
     64            }
     65
    3866            svg {
    3967                display: block;
  • trunk/Websites/perf.webkit.org/public/v3/components/customizable-test-group-form.js

    r214502 r226259  
    1 
    21class CustomizableTestGroupForm extends TestGroupForm {
    32
     
    87        this._name = null;
    98        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 = [];
    1314    }
    1415
     
    1718        this._commitSetMap = map;
    1819        this._isCustomized = false;
     20        this._fetchingCommitPromises = [];
     21        this._checkedLabelByPosition = new Map;
     22        this._hasIncompleteOwnedRepository = new Map;
    1923        this.enqueueToRender();
    2024    }
     
    3337            this._name = nameControl.value;
    3438            this.enqueueToRender();
    35         }
     39        };
    3640
    3741        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            }
    3863            this._isCustomized = true;
    3964            this.enqueueToRender();
     
    4873
    4974        const map = {};
    50         for (const label in this._commitSetMap) {
    51             const originalCommitSet = this._commitSetMap;
     75        for (const [label, commitSet] of this._commitSetMap) {
    5276            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);
    5579                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);
    5783            }
    5884            map[label] = customCommitSet;
     
    6894        this.content('customize-link-container').style.display = !this._commitSetMap ? 'none' : null;
    6995
    70         this._renderCustomRevisionTableLazily.evaluate(this._commitSetMap, this._isCustomized);
     96        this._renderCustomRevisionTable(this._commitSetMap, this._isCustomized);
    7197    }
    7298
     
    75101        if (!commitSetMap || !isCustomized) {
    76102            this.renderReplace(this.content('custom-table'), []);
    77             return null;
     103            return;
    78104        }
    79105
    80106        const repositorySet = new Set;
     107        const ownedRepositoriesByRepository = new Map;
    81108        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()) {
    85113                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            }
    86125            commitSetLabels.push(label);
    87             this._revisionEditorMap[label] = new Map;
     126            this._revisionEditorMap.set(label, new Map);
     127            this._ownerRevisionMap.set(label, new Map);
    88128        }
    89129
     
    93133            element('thead',
    94134                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);
    113255
    114256        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;
    118261            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) {
    122270                revisionEditor.value = commit ? commit.revision() : '';
     271                if (commit && commit.ownerCommit())
     272                    this._ownerRevisionMap.get(rowLabel).set(repository, commit.ownerCommit().revision());
     273            }
    123274            nodes.push(element('td', element('label', [radioButton, labelToChoose])));
    124275        }
     
    138289            #custom-table:not(:empty) {
    139290                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;
    140306            }
    141307
  • trunk/Websites/perf.webkit.org/public/v3/components/owned-commit-viewer.js

    r222227 r226259  
    4444            return;
    4545
    46         const difference = CommitLog.diffOwnedCommits(this._previousCommit, this._currentCommit);
     46        const difference = CommitLog.ownedCommitDifferenceForOwnerCommits(this._previousCommit, this._currentCommit);
    4747        const sortedRepositories = Repository.sortByName([...difference.keys()]);
    4848        const element = ComponentBase.createElement;
  • trunk/Websites/perf.webkit.org/public/v3/index.html

    r225898 r226259  
    100100        <script src="components/instant-file-uploader.js"></script>
    101101        <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>
    102105
    103106        <script src="pages/page.js"></script>
  • trunk/Websites/perf.webkit.org/public/v3/models/commit-log.js

    r222227 r226259  
    1313            this.ensureNamedStaticMap('remoteId')[this._remoteId] = this;
    1414        this._ownedCommits = null;
     15        this._ownerCommit = null;
     16        this._ownedCommitByOwnedRepository = new Map;
    1517    }
    1618
     
    3739    url() { return this._repository.urlForRevision(this._rawData['revision']); }
    3840    ownsCommits() { return this._rawData['ownsCommits']; }
     41    ownedCommits() { return this._ownedCommits; }
     42    ownerCommit() { return this._ownerCommit; }
     43
     44    setOwnerCommits(ownerCommit) { this._ownerCommit = ownerCommit; }
    3945
    4046    label()
    4147    {
    42         var revision = this.revision();
     48        const revision = this.revision();
    4349        if (parseInt(revision) == revision) // e.g. r12345
    4450            return 'r' + revision;
    45         else if (revision.length == 40) // e.g. git hash
     51        if (revision.length == 40) // e.g. git hash
    4652            return revision.substring(0, 8);
    4753        return revision;
     
    6066        const to = this.revision();
    6167        const from = previousCommit.revision();
    62         let fromRevisionForURL = from;
    6368        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.
    6670            label = `r${from}-r${this.revision()}`;
    67         } else if (to.length == 40) // e.g. git hash
     71        else if (to.length == 40) // e.g. git hash
    6872            label = `${from.substring(0, 8)}..${to.substring(0, 8)}`;
    6973        else
     
    8791    }
    8892
     93    ownedCommitForOwnedRepository(ownedRepository) { return this._ownedCommitByOwnedRepository.get(ownedRepository); }
     94
    8995    fetchOwnedCommits()
    9096    {
     
    100106        return CommitLog.cachedFetch(`../api/commits/${this.repository().id()}/owned-commits?owner-revision=${escape(this.revision())}`).then((data) => {
    101107            this._ownedCommits = CommitLog._constructFromRawData(data);
     108            this._ownedCommits.forEach((ownedCommit) => {
     109                ownedCommit.setOwnerCommits(this);
     110                this._ownedCommitByOwnedRepository.set(ownedCommit.repository(), ownedCommit);
     111            });
    102112            return this._ownedCommits;
    103113        });
     
    112122    }
    113123
    114     static diffOwnedCommits(previousCommit, currentCommit)
     124    static ownedCommitDifferenceForOwnerCommits(...commits)
    115125    {
    116         console.assert(previousCommit);
    117         console.assert(currentCommit);
    118         console.assert(previousCommit._ownedCommits);
    119         console.assert(currentCommit._ownedCommits);
     126        console.assert(commits.length >= 2);
    120127
    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;
    131136        });
    132137
     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        });
    133145        return difference;
    134146    }
  • trunk/Websites/perf.webkit.org/public/v3/models/commit-set.js

    r222648 r226259  
    7171    ownerCommitForRepository(repository) { return this._repositoryToCommitOwnerMap.get(repository); }
    7272    topLevelRepositories() { return Repository.sortByNamePreferringOnesWithURL(this._repositories.filter((repository) => !this.ownerRevisionForRepository(repository))); }
    73 
    7473    ownedRepositoriesForOwnerRepository(repository) { return this._ownerRepositoryToOwnedRepositoriesMap.get(repository); }
     74    commitForRepository(repository) { return this._repositoryToCommitMap.get(repository); }
    7575
    7676    revisionForRepository(repository)
     
    249249}
    250250
     251class 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
    251346if (typeof module != 'undefined') {
    252347    module.exports.CommitSet = CommitSet;
    253348    module.exports.MeasurementCommitSet = MeasurementCommitSet;
    254349    module.exports.CustomCommitSet = CustomCommitSet;
    255 }
     350    module.exports.IntermediateCommitSet = IntermediateCommitSet;
     351}
  • trunk/Websites/perf.webkit.org/tools/js/v3-models.js

    r222219 r226259  
    1717importFromV3('models/builder.js', 'Builder');
    1818importFromV3('models/commit-log.js', 'CommitLog');
    19 importFromV3('models/commit-set.js', 'CustomCommitSet')
    2019importFromV3('models/manifest.js', 'Manifest');
    2120importFromV3('models/measurement-adaptor.js', 'MeasurementAdaptor');
     
    2827importFromV3('models/commit-set.js', 'CommitSet');
    2928importFromV3('models/commit-set.js', 'CustomCommitSet');
     29importFromV3('models/commit-set.js', 'IntermediateCommitSet');
    3030importFromV3('models/test.js', 'Test');
    3131importFromV3('models/test-group.js', 'TestGroup');
  • trunk/Websites/perf.webkit.org/unit-tests/commit-log-tests.js

    r222227 r226259  
    192192
    193193        it('should return owned-commit for a valid commit revision', () => {
    194             const fetchingPromise = ownerCommit().fetchOwnedCommits();
     194            const commit = ownerCommit();
     195            const fetchingPromise = commit.fetchOwnedCommits();
    195196            const requests = MockRemoteAPI.requests;
    196197            assert.equal(requests.length, 1);
     
    209210                assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb');
    210211                assert.equal(ownedCommits[0].id(), 233);
     212                assert.equal(ownedCommits[0].ownerCommit(), commit);
    211213            });
    212214        });
     
    234236                assert.equal(ownedCommits[0].revision(), '6f8b0dbbda95a440503b88db1dd03dad3a7b07fb');
    235237                assert.equal(ownedCommits[0].id(), 233);
     238                assert.equal(ownedCommits[0].ownerCommit(), commit);
    236239                return commit.fetchOwnedCommits();
    237240            }).then((ownedCommits) => {
     
    242245    });
    243246
    244     describe('diffOwnedCommits', () => {
     247    describe('ownedCommitDifferenceForOwnerCommits', () => {
    245248        beforeEach(() => {
    246249            MockRemoteAPI.reset();
    247250        });
    248251
    249         it('should return difference between 2 owned-commits', () => {
     252        it('should return difference between owned-commits of 2 owner commits', () => {
    250253            const oneCommit = ownerCommit();
    251254            const otherCommit = otherOwnerCommit();
     
    268271            }]});
    269272
    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
    271284                const otherFetchingPromise = otherCommit.fetchOwnedCommits();
    272285                assert.equal(requests.length, 2);
     
    287300
    288301                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);
    291313                assert.equal(difference.size, 1);
    292314                assert.equal(difference.keys().next().value, MockModels.ownedRepository);
  • trunk/Websites/perf.webkit.org/unit-tests/commit-set-tests.js

    r222648 r226259  
    44require('../tools/js/v3-models.js');
    55const MockModels = require('./resources/mock-v3-models.js').MockModels;
     6const MockRemoteAPI = require('../unit-tests/resources/mock-remote-api.js').MockRemoteAPI;
    67
    78function createPatch()
     
    5152    return customCommitSet;
    5253}
     54
     55function 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
     65function 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
     75function 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
     85function 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
     95describe('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});
    53320
    54321describe('CustomCommitSet', () => {
Note: See TracChangeset for help on using the changeset viewer.