| class CustomizableTestGroupForm extends TestGroupForm { |
| |
| constructor() |
| { |
| super('customizable-test-group-form'); |
| this._commitSetMap = null; |
| this._uncustomizedCommitSetMap = null; |
| this._name = null; |
| this._isCustomized = false; |
| this._revisionEditorMap = null; |
| this._ownerRevisionMap = null; |
| this._checkedLabelByPosition = null; |
| this._hasIncompleteOwnedRepository = null; |
| this._fetchingCommitPromises = []; |
| } |
| |
| setCommitSetMap(map) |
| { |
| this._commitSetMap = map; |
| this._uncustomizedCommitSetMap = map; |
| this._isCustomized = false; |
| this._fetchingCommitPromises = []; |
| this._checkedLabelByPosition = new Map; |
| this._hasIncompleteOwnedRepository = new Map; |
| this.enqueueToRender(); |
| } |
| |
| startTesting() |
| { |
| this.dispatchAction('startTesting', this._repetitionCount, this._name, this._computeCommitSetMap(), this._notifyOnCompletion); |
| } |
| |
| didConstructShadowTree() |
| { |
| super.didConstructShadowTree(); |
| |
| const nameControl = this.content('name'); |
| nameControl.oninput = () => { |
| this._name = nameControl.value; |
| this.enqueueToRender(); |
| }; |
| |
| this.content('customize-link').onclick = this.createEventHandler(() => { |
| if (!this._isCustomized) { |
| const uncustomizedCommitSetMap = this._commitSetMap; |
| const fetchingCommitPromises = []; |
| this._commitSetMap = new Map; |
| for (const label in uncustomizedCommitSetMap) { |
| const intermediateCommitSet = new IntermediateCommitSet(uncustomizedCommitSetMap[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 uncustomizedCommitSetMap) |
| this._checkedLabelByPosition.set(label, new Map); |
| this.enqueueToRender(); |
| }); |
| } |
| this._isCustomized = true; |
| this.enqueueToRender(); |
| }); |
| } |
| |
| _computeCommitSetMap() |
| { |
| console.assert(this._commitSetMap); |
| if (!this._isCustomized) |
| return this._commitSetMap; |
| |
| const map = {}; |
| for (const [label, commitSet] of this._commitSetMap) { |
| const customCommitSet = new CustomCommitSet; |
| for (const repository of commitSet.repositories()) { |
| const revisionEditor = this._revisionEditorMap.get(label).get(repository); |
| console.assert(revisionEditor); |
| const ownerRevision = this._ownerRevisionMap.get(label).get(repository) || null; |
| |
| customCommitSet.setRevisionForRepository(repository, revisionEditor.value, null, ownerRevision); |
| } |
| map[label] = customCommitSet; |
| } |
| return map; |
| } |
| |
| render() |
| { |
| super.render(); |
| |
| this.content('start-button').disabled = !(this._commitSetMap && this._name); |
| this.content('customize-link-container').style.display = !this._commitSetMap ? 'none' : null; |
| |
| this._renderCustomRevisionTable(this._commitSetMap, this._isCustomized, this._uncustomizedCommitSetMap); |
| } |
| |
| _renderCustomRevisionTable(commitSetMap, isCustomized, uncustomizedCommitSetMap) |
| { |
| if (!commitSetMap || !isCustomized) { |
| this.renderReplace(this.content('custom-table'), []); |
| return; |
| } |
| |
| const repositorySet = new Set; |
| const ownedRepositoriesByRepository = new Map; |
| const commitSetLabels = []; |
| 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.set(label, new Map); |
| this._ownerRevisionMap.set(label, new Map); |
| } |
| |
| const repositoryList = Repository.sortByNamePreferringOnesWithURL(Array.from(repositorySet.values())); |
| const element = ComponentBase.createElement; |
| this.renderReplace(this.content('custom-table'), [ |
| element('thead', |
| element('tr', |
| [element('td', {colspan: 2}, 'Repository'), commitSetLabels.map((label) => element('td', {colspan: commitSetLabels.length + 1}, label)), element('td')])), |
| this._constructTableBodyList(repositoryList, commitSetMap, ownedRepositoriesByRepository, this._hasIncompleteOwnedRepository, uncustomizedCommitSetMap), |
| this._constructTestabilityRows(commitSetMap)]); |
| } |
| |
| _constructTestabilityRows(commitSetMap) |
| { |
| const element = ComponentBase.createElement; |
| |
| const commitSets = Array.from(commitSetMap.values()); |
| const hasCommitWithTestability = commitSets.some((commitSet) => !!commitSet.commitsWithTestability().length); |
| for (const c of commitSets) { |
| if (c.commitsWithTestability().length) |
| console.log(c); |
| } |
| console.log({hasCommitWithTestability}); |
| console.log('aaaa'); |
| if (!hasCommitWithTestability) |
| return []; |
| |
| const testabilityCells = []; |
| for (const commitSet of commitSetMap.values()) { |
| const entries = commitSet.commitsWithTestability().map((commit) => |
| element('li', `${commit.title()}: ${commit.testability()}`)); |
| testabilityCells.push(element('td', {colspan: commitSetMap.size + 1, class: 'testability'}, element('ul', entries))); |
| } |
| |
| return element('tbody', element('tr', [element('td', {colspan: 2}), testabilityCells, element('td')])); |
| } |
| |
| _constructTableBodyList(repositoryList, commitSetMap, ownedRepositoriesByRepository, hasIncompleteOwnedRepository, uncustomizedCommitSetMap) |
| { |
| 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, uncustomizedCommitSetMap)); |
| |
| 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, uncustomizedCommitSetMap)); |
| } |
| |
| 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; |
| } |
| |
| _constructTableRowForCommitsWithoutOwner(commitSetMap, repository, ownsRepositories, hasIncompleteOwnedRepository, uncustomizedCommitSetMap) |
| { |
| const element = ComponentBase.createElement; |
| const cells = [element('th', {colspan: 2}, repository.label())]; |
| |
| for (const label of commitSetMap.keys()) |
| cells.push(this._constructRevisionRadioButtons(commitSetMap, repository, label, null, uncustomizedCommitSetMap)); |
| |
| 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, uncustomizedCommitSetMap) |
| { |
| 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, uncustomizedCommitSetMap)); |
| |
| 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, columnLabel, ownerRepository, uncustomizedCommitSetMap) |
| { |
| const element = ComponentBase.createElement; |
| |
| const commitForColumn = commitSetMap.get(columnLabel).commitForRepository(repository); |
| const revision = commitForColumn ? commitForColumn.revision() : ''; |
| if (commitForColumn && commitForColumn.ownerCommit()) |
| this._ownerRevisionMap.get(columnLabel).set(repository, commitForColumn.ownerCommit().revision()); |
| |
| const revisionEditor = element('input', {disabled: !!ownerRepository, value: revision, |
| onchange: () => { |
| if (ownerRepository) |
| return; |
| commitSetMap.get(columnLabel).updateRevisionForOwnerRepository(repository, revisionEditor.value).then( |
| () => this.enqueueToRender(), |
| () => { |
| alert(`"${revisionEditor.value}" does not exist in "${repository.name()}".`); |
| revisionEditor.value = revision; |
| }); |
| }}); |
| |
| this._revisionEditorMap.get(columnLabel).set(repository, revisionEditor); |
| |
| const nodes = []; |
| for (const [labelToChoose, commitSet] of commitSetMap) { |
| const commit = commitSet.commitForRepository(repository); |
| const checkedLabel = this._checkedLabelByPosition.get(columnLabel).get(repository) || columnLabel; |
| const checked = labelToChoose == checkedLabel; |
| const radioButton = element('input', {type: 'radio', name: `${columnLabel}-${repository.id()}-radio`, checked, |
| onchange: () => { |
| const uncustomizedCommit = uncustomizedCommitSetMap[labelToChoose].commitForRepository(repository) || commit; |
| commitSetMap.get(columnLabel).setCommitForRepository(repository, uncustomizedCommit); |
| this._checkedLabelByPosition.get(columnLabel).set(repository, labelToChoose); |
| revisionEditor.value = uncustomizedCommit ? uncustomizedCommit.revision() : ''; |
| if (uncustomizedCommit && uncustomizedCommit.ownerCommit()) |
| this._ownerRevisionMap.get(columnLabel).set(repository, uncustomizedCommit.ownerCommit().revision()); |
| this.enqueueToRender(); |
| }}); |
| nodes.push(element('td', element('label', [radioButton, labelToChoose]))); |
| } |
| nodes.push(element('td', revisionEditor)); |
| |
| return nodes; |
| } |
| |
| static cssTemplate() |
| { |
| return ` |
| #customize-link-container, |
| #customize-link { |
| color: #333; |
| } |
| |
| #customize-link-container { |
| margin-left: 0.4rem; |
| } |
| |
| #custom-table:not(:empty) { |
| 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 { |
| font-weight: inherit; |
| border-collapse: collapse; |
| border-top: solid 1px #ddd; |
| border-bottom: solid 1px #ddd; |
| padding: 0.4rem 0.2rem; |
| font-size: 0.9rem; |
| } |
| |
| #custom-table thead td, |
| #custom-table th { |
| text-align: center; |
| } |
| |
| #notify-on-completion-checkbox { |
| margin-left: 0.4rem; |
| } |
| |
| #custom-table td.testability { |
| vertical-align: top; |
| } |
| |
| #custom-table td.testability ul { |
| text-align: left; |
| color: #c33; |
| max-width: 13rem; |
| margin: 0 0 0 1rem; |
| padding: 0; |
| } |
| `; |
| } |
| |
| static formContent() |
| { |
| return ` |
| <input id="name" type="text" placeholder="Test group name"> |
| ${super.formContent()} |
| <span id="customize-link-container">(<a id="customize-link" href="#">Customize</a>)</span> |
| <table id="custom-table"></table> |
| `; |
| } |
| } |
| |
| ComponentBase.defineElement('customizable-test-group-form', CustomizableTestGroupForm); |