resources/perf.webkit.org/public/v3/pages/analysis-task-page.js (945 lines of code) (raw):

class AnalysisTaskChartPane extends ChartPaneBase { constructor() { super('analysis-task-chart-pane'); this._page = null; this._showForm = false; } setPage(page) { this._page = page; } setShowForm(show) { this._showForm = show; this.enqueueToRender(); } router() { return this._page.router(); } _mainSelectionDidChange(selection, didEndDrag) { super._mainSelectionDidChange(selection); if (didEndDrag) this.enqueueToRender(); } didConstructShadowTree() { this.part('form').listenToAction('startTesting', (name, repetitionCount, repetitionType, commitSetMap, notifyOnCompletion) => { this.dispatchAction('newTestGroup', name, repetitionCount, repetitionType, commitSetMap, notifyOnCompletion); }); } render() { super.render(); const points = this._mainChart ? this._mainChart.selectedPoints('current') : null; this.content('form').style.display = this._showForm ? null : 'none'; if (this._showForm) { const form = this.part('form'); form.setCommitSetMap(points && points.length() >= 2 ? {'A': points.firstPoint().commitSet(), 'B': points.lastPoint().commitSet()} : null); form.enqueueToRender(); } } static paneFooterTemplate() { return '<customizable-test-group-form id="form"></customizable-test-group-form>'; } static cssTemplate() { return super.cssTemplate() + ` #form { margin: 0.5rem; } `; } } ComponentBase.defineElement('analysis-task-chart-pane', AnalysisTaskChartPane); class AnalysisTaskResultsPane extends ComponentBase { constructor() { super('analysis-task-results-pane'); this._showForm = false; this._repositoryList = []; this._renderRepositoryListLazily = new LazilyEvaluatedFunction(this._renderRepositoryList.bind(this)); this._updateCommitViewerLazily = new LazilyEvaluatedFunction(this._updateCommitViewer.bind(this)); } setPoints(startPoint, endPoint, metric) { const resultsViewer = this.part('results-viewer'); this._repositoryList = startPoint ? Repository.sortByNamePreferringOnesWithURL(startPoint.commitSet().repositories()) : []; resultsViewer.setPoints(startPoint, endPoint, metric); resultsViewer.enqueueToRender(); } setTestGroups(testGroups, currentGroup) { this.part('results-viewer').setTestGroups(testGroups, currentGroup); if (currentGroup) this.part('form').updateWithTestGroup(currentGroup); this.enqueueToRender(); } setAnalysisResultsView(analysisResultsView) { this.part('results-viewer').setAnalysisResultsView(analysisResultsView); this.enqueueToRender(); } setShowForm(show) { this._showForm = show; this.enqueueToRender(); } didConstructShadowTree() { const resultsViewer = this.part('results-viewer'); resultsViewer.listenToAction('testGroupClick', (testGroup) => { this.enqueueToRender(); this.dispatchAction('showTestGroup', testGroup) }); resultsViewer.setRangeSelectorLabels(['A', 'B']); resultsViewer.listenToAction('rangeSelectorClick', () => this.enqueueToRender()); const repositoryPicker = this.content('commit-viewer-repository'); repositoryPicker.addEventListener('change', () => this.enqueueToRender()); this.part('commit-viewer').setShowRepositoryName(false); this.part('form').listenToAction('startTesting', (name, repetitionCount, repetitionType, commitSetMap, notifyOnCompletion) => { this.dispatchAction('newTestGroup', name, repetitionCount, repetitionType, commitSetMap, notifyOnCompletion); }); } render() { const resultsViewer = this.part('results-viewer'); const repositoryPicker = this._renderRepositoryListLazily.evaluate(this._repositoryList); const repository = Repository.findById(repositoryPicker.value); const range = resultsViewer.selectedRange(); this._updateCommitViewerLazily.evaluate(repository, range['A'], range['B']); this.content('form').style.display = this._showForm ? null : 'none'; if (!this._showForm) return; const selectedRange = this.part('results-viewer').selectedRange(); const firstCommitSet = selectedRange['A']; const secondCommitSet = selectedRange['B']; const form = this.part('form'); form.setCommitSetMap(firstCommitSet && secondCommitSet ? {'A': firstCommitSet, 'B': secondCommitSet} : null); form.enqueueToRender(); } _renderRepositoryList(repositoryList) { const element = ComponentBase.createElement; const selectElement = this.content('commit-viewer-repository'); this.renderReplace(selectElement, repositoryList.map((repository) => { return element('option', {value: repository.id()}, repository.label()); })); return selectElement; } _updateCommitViewer(repository, preceedingCommitSet, lastCommitSet) { if (repository && preceedingCommitSet && lastCommitSet && !preceedingCommitSet.equals(lastCommitSet)) { const precedingRevision = preceedingCommitSet.revisionForRepository(repository); const lastRevision = lastCommitSet.revisionForRepository(repository); if (precedingRevision && lastRevision && precedingRevision != lastRevision) { this.part('commit-viewer').view(repository, precedingRevision, lastRevision); return; } } this.part('commit-viewer').view(null, null, null); } static htmlTemplate() { return ` <div id="results-container"> <div id="results-viewer-container"> <analysis-results-viewer id="results-viewer"></analysis-results-viewer> </div> <div id="commit-pane"> <select id="commit-viewer-repository"></select> <commit-log-viewer id="commit-viewer"></commit-log-viewer> </div> </div> <customizable-test-group-form id="form"></customizable-test-group-form> `; } static cssTemplate() { return ` #results-container { position: relative; text-align: center; padding-right: 20rem; } #results-viewer-container { overflow-x: scroll; overflow-y: hidden; } #commit-pane { position: absolute; width: 20rem; height: 100%; top: 0; right: 0; } #form { margin: 0.5rem; } `; } } ComponentBase.defineElement('analysis-task-results-pane', AnalysisTaskResultsPane); class AnalysisTaskConfiguratorPane extends ComponentBase { constructor() { super('analysis-task-configurator-pane'); this._currentGroup = null; } didConstructShadowTree() { const form = this.part('form'); form.setHasTask(true); form.listenToAction('startTesting', (...args) => { this.dispatchAction('createCustomTestGroup', ...args); }); } setTestGroups(testGroups, currentGroup) { this._currentGroup = currentGroup; const form = this.part('form'); if (!form.hasCommitSets() && currentGroup) form.setConfigurations(currentGroup.test(), currentGroup.platform(), currentGroup.initialRepetitionCount(), currentGroup.requestedCommitSets()); this.enqueueToRender(); } render() { super.render(); } static htmlTemplate() { return `<custom-configuration-test-group-form id="form"></custom-configuration-test-group-form>`; } static cssTemplate() { return ` #form { margin: 1rem; } `; } } ComponentBase.defineElement('analysis-task-configurator-pane', AnalysisTaskConfiguratorPane); class AnalysisTaskTestGroupPane extends ComponentBase { constructor() { super('analysis-task-test-group-pane'); this._renderTestGroupsLazily = new LazilyEvaluatedFunction(this._renderTestGroups.bind(this)); this._renderTestGroupVisibilityLazily = new LazilyEvaluatedFunction(this._renderTestGroupVisibility.bind(this)); this._renderTestGroupNamesLazily = new LazilyEvaluatedFunction(this._renderTestGroupNames.bind(this)); this._renderCurrentTestGroupLazily = new LazilyEvaluatedFunction(this._renderCurrentTestGroup.bind(this)); this._testGroupMap = new Map; this._testGroups = []; this._bisectingCommitSetByTestGroup = null; this._currentTestGroup = null; this._showHiddenGroups = false; this._allTestGroupIdSetForCurrentTask = null; } didConstructShadowTree() { this.content('hide-button').onclick = () => this.dispatchAction('toggleTestGroupVisibility', this._currentTestGroup); this.part('retry-form').listenToAction('startTesting', (repetitionCount, repetitionType, notifyOnCompletion) => { this.dispatchAction('retryTestGroup', this._currentTestGroup, repetitionCount, repetitionType, notifyOnCompletion); }); this.part('bisect-form').listenToAction('startTesting', (repetitionCount, repetitionType, notifyOnCompletion) => { const bisectingCommitSet = this._bisectingCommitSetByTestGroup.get(this._currentTestGroup); const [oneCommitSet, anotherCommitSet] = this._currentTestGroup.requestedCommitSets(); const commitSets = [oneCommitSet, bisectingCommitSet, anotherCommitSet]; this.dispatchAction('bisectTestGroup', this._currentTestGroup, commitSets, repetitionCount, repetitionType, notifyOnCompletion); }); } setTestGroups(testGroups, currentTestGroup, showHiddenGroups) { this._testGroups = testGroups; this._currentTestGroup = currentTestGroup; this._showHiddenGroups = showHiddenGroups; this.part('revision-table').setTestGroup(currentTestGroup); this.part('results-viewer').setTestGroup(currentTestGroup); const analysisTask = currentTestGroup.task(); const allTestGroupIdsForCurrentTask = TestGroup.findAllByTask(analysisTask.id()).map((testGroup) => testGroup.id()); const testGroupChanged = !this._allTestGroupIdSetForCurrentTask || this._allTestGroupIdSetForCurrentTask.size !== allTestGroupIdsForCurrentTask.length || !allTestGroupIdsForCurrentTask.every((testGroupId) => this._allTestGroupIdSetForCurrentTask.has(testGroupId)); const computedForCurrentTestGroup = this._bisectingCommitSetByTestGroup && this._bisectingCommitSetByTestGroup.has(currentTestGroup); if (!testGroupChanged && computedForCurrentTestGroup) { this.enqueueToRender(); return; } if (testGroupChanged) { this._bisectingCommitSetByTestGroup = new Map; this._allTestGroupIdSetForCurrentTask = new Set(allTestGroupIdsForCurrentTask); } analysisTask.commitSetsFromTestGroupsAndMeasurementSet().then(async (availableCommitSets) => { const commitSetClosestToMiddle = await CommitSetRangeBisector.commitSetClosestToMiddleOfAllCommits(currentTestGroup.requestedCommitSets(), availableCommitSets); this._bisectingCommitSetByTestGroup.set(currentTestGroup, commitSetClosestToMiddle); this.enqueueToRender(); }); } setAnalysisResults(analysisResults, metric) { this.part('revision-table').setAnalysisResults(analysisResults); this.part('results-viewer').setAnalysisResults(analysisResults, metric); this.enqueueToRender(); } render() { this._renderTestGroupsLazily.evaluate(this._showHiddenGroups, ...this._testGroups); this._renderTestGroupVisibilityLazily.evaluate(...this._testGroups.map((group) => group.isHidden() ? 'hidden' : 'visible')); this._renderTestGroupNamesLazily.evaluate(...this._testGroups.map((group) => group.label())); this._renderCurrentTestGroup(this._currentTestGroup); } _renderTestGroups(showHiddenGroups, ...testGroups) { const element = ComponentBase.createElement; const link = ComponentBase.createLink; this._testGroupMap = new Map; const testGroupItems = testGroups.map((group) => { const text = new EditableText(group.label()); text.listenToAction('update', () => this.dispatchAction('renameTestGroup', group, text.editedText())); const listItem = element('li', link(text, group.label(), () => this.dispatchAction('showTestGroup', group))); this._testGroupMap.set(group, {text, listItem}); return listItem; }); this.renderReplace(this.content('test-group-list'), [testGroupItems, showHiddenGroups ? [] : element('li', {class: 'test-group-list-show-all'}, link('Show hidden tests', () => { this.dispatchAction('showHiddenTestGroups'); }))]); } _renderTestGroupVisibility(...groupVisibilities) { for (let i = 0; i < groupVisibilities.length; i++) this._testGroupMap.get(this._testGroups[i]).listItem.className = groupVisibilities[i]; } _renderTestGroupNames(...groupNames) { for (let i = 0; i < groupNames.length; i++) this._testGroupMap.get(this._testGroups[i]).text.setText(groupNames[i]); } _renderCurrentTestGroup(currentGroup) { const selected = this.content('test-group-list').querySelector('.selected'); if (selected) selected.classList.remove('selected'); if (currentGroup) { this.part('retry-form').updateWithTestGroup(currentGroup); this.part('bisect-form').updateWithTestGroup(currentGroup); this._testGroupMap.get(currentGroup).listItem.classList.add('selected'); if (currentGroup.hasRetries()) this.renderReplace(this.content('retry-summary'), this._retrySummary(currentGroup)); const authoredBy = currentGroup.author() ? `by "${currentGroup.author()}"` : ''; this.renderReplace(this.content('request-summary'), `Scheduled ${authoredBy} at ${currentGroup.createdAt()}`) this.renderReplace(this.content('hide-button'), currentGroup.isHidden() ? 'Unhide' : 'Hide'); this.renderReplace(this.content('pending-request-cancel-warning'), currentGroup.hasPending() ? '(cancels pending requests)' : []); } this.content('retry-form').style.display = currentGroup ? null : 'none'; this.content('bisect-form').style.display = currentGroup && this._bisectingCommitSetByTestGroup.get(currentGroup) ? null : 'none'; } _retrySummary(testGroup) { if (!testGroup.hasRetries()) return ''; if (testGroup.retryCountsAreSameForAllCommitSets()) { const retryCount = testGroup.retryCountForCommitSet(testGroup.requestedCommitSets()[0]); return `${testGroup.initialRepetitionCount()} requested, ${retryCount} added due to failures.`; } const retrySummaryParts = testGroup.requestedCommitSets() .map((commitSet) => ({commitSet, retryCount: testGroup.retryCountForCommitSet(commitSet)})) .filter((item) => item.retryCount) .map((item) => `${item.retryCount} added to ${testGroup.labelForCommitSet(item.commitSet)}`); const retrySummary = new Intl.ListFormat('en', {style: 'long', type: 'conjunction'}).format(retrySummaryParts); return `${testGroup.initialRepetitionCount()} requested, ${retrySummary} due to failures.`; } static htmlTemplate() { return ` <ul id="test-group-list"></ul> <div id="test-group-details"> <test-group-results-viewer id="results-viewer"></test-group-results-viewer> <test-group-revision-table id="revision-table"></test-group-revision-table> <div id="retry-summary" class="summary"></div> <div id="request-summary" class="summary"></div> <test-group-form id="retry-form">Retry</test-group-form> <test-group-form id="bisect-form">Bisect</test-group-form> <button id="hide-button">Hide</button> <span id="pending-request-cancel-warning">(cancels pending requests)</span> </div>`; } static cssTemplate() { return ` :host { display: flex !important; font-size: 0.9rem; } #test-group-list { flex: none; margin: 0; padding: 0.2rem 0; list-style: none; border-right: solid 1px #ccc; white-space: nowrap; min-width: 8rem; } li { display: block; font-size: 0.9rem; } li > a { display: block; color: inherit; text-decoration: none; margin: 0; padding: 0.2rem; } li.test-group-list-show-all { font-size: 0.8rem; margin-top: 0.5rem; padding-right: 1rem; text-align: center; color: #999; } li.test-group-list-show-all:not(.selected) a:hover { background: inherit; } li.selected > a { background: rgba(204, 153, 51, 0.1); } li.hidden { color: #999; } li:not(.selected) > a:hover { background: #eee; } div.summary { padding-left: 1rem; } #test-group-details { display: table-cell; margin-bottom: 1rem; padding: 0 0.5rem; margin: 0; } #retry-form, #bisect-form { display: block; margin: 0.5rem; } #hide-button { margin: 0.5rem; }`; } } ComponentBase.defineElement('analysis-task-test-group-pane', AnalysisTaskTestGroupPane); class AnalysisTaskPage extends PageWithHeading { constructor() { super('Analysis Task'); this._renderTaskNameAndStatusLazily = new LazilyEvaluatedFunction(this._renderTaskNameAndStatus.bind(this)); this._renderCauseAndFixesLazily = new LazilyEvaluatedFunction(this._renderCauseAndFixes.bind(this)); this._renderRelatedTasksLazily = new LazilyEvaluatedFunction(this._renderRelatedTasks.bind(this)); this._resetVariables(); } title() { return this._task ? this._task.label() : 'Analysis Task'; } routeName() { return 'analysis/task'; } _resetVariables() { this._task = null; this._metric = null; this._triggerable = null; this._relatedTasks = null; this._testGroups = null; this._testGroupLabelMap = new Map; this._analysisResults = null; this._measurementSet = null; this._startPoint = null; this._endPoint = null; this._errorMessage = null; this._currentTestGroup = null; this._filteredTestGroups = null; this._showHiddenTestGroups = false; } updateFromSerializedState(state) { this._resetVariables(); if (state.remainingRoute) { const taskId = parseInt(state.remainingRoute); AnalysisTask.fetchById(taskId).then(this._didFetchTask.bind(this)).then(() => { this._fetchRelatedInfoForTaskId(taskId); }, (error) => { this._errorMessage = `Failed to fetch the analysis task ${state.remainingRoute}: ${error}`; this.enqueueToRender(); }); } else if (state.buildRequest) { const buildRequestId = parseInt(state.buildRequest); AnalysisTask.fetchByBuildRequestId(buildRequestId).then(this._didFetchTask.bind(this)).then((task) => { this._fetchRelatedInfoForTaskId(task.id()); }, (error) => { this._errorMessage = `Failed to fetch the analysis task for the build request ${buildRequestId}: ${error}`; this.enqueueToRender(); }); } } didConstructShadowTree() { this.part('analysis-task-name').listenToAction('update', () => this._updateTaskName(this.part('analysis-task-name').editedText())); this.content('change-type-form').onsubmit = ComponentBase.createEventHandler((event) => this._updateChangeType(event)); this.part('chart-pane').listenToAction('newTestGroup', this._createTestGroupAfterVerifyingCommitSetList.bind(this)); const resultsPane = this.part('results-pane'); resultsPane.listenToAction('showTestGroup', (testGroup) => this._showTestGroup(testGroup)); resultsPane.listenToAction('newTestGroup', this._createTestGroupAfterVerifyingCommitSetList.bind(this)); this.part('configurator-pane').listenToAction('createCustomTestGroup', this._createCustomTestGroup.bind(this)); const groupPane = this.part('group-pane'); groupPane.listenToAction('showTestGroup', (testGroup) => this._showTestGroup(testGroup)); groupPane.listenToAction('showHiddenTestGroups', () => this._showAllTestGroups()); groupPane.listenToAction('renameTestGroup', (testGroup, newName) => this._updateTestGroupName(testGroup, newName)); groupPane.listenToAction('toggleTestGroupVisibility', (testGroup) => this._hideCurrentTestGroup(testGroup)); groupPane.listenToAction('retryTestGroup', (testGroup, repetitionCount, repetitionType, notifyOnCompletion) => this._retryCurrentTestGroup(testGroup, repetitionCount, repetitionType, notifyOnCompletion)); groupPane.listenToAction('bisectTestGroup', (testGroup, commitSets, repetitionCount, repetitionType, notifyOnCompletion) => this._bisectCurrentTestGroup(testGroup, commitSets, repetitionCount, repetitionType, notifyOnCompletion)); this.part('cause-list').listenToAction('addItem', (repository, revision) => { this._associateCommit('cause', repository, revision); }); this.part('fix-list').listenToAction('addItem', (repository, revision) => { this._associateCommit('fix', repository, revision); }); } _fetchRelatedInfoForTaskId(taskId) { TestGroup.fetchForTask(taskId).then(this._didFetchTestGroups.bind(this)); AnalysisResults.fetch(taskId).then(this._didFetchAnalysisResults.bind(this)); AnalysisTask.fetchRelatedTasks(taskId).then((relatedTasks) => { this._relatedTasks = relatedTasks; this.enqueueToRender(); }); } _didFetchTask(task) { console.assert(!this._task); this._task = task; const bugList = this.part('bug-list'); this.part('bug-list').setTask(this._task); this.enqueueToRender(); if (task.isCustom()) return task; const platform = task.platform(); const metric = task.metric(); const lastModified = platform.lastModified(metric); const triggerable = Triggerable.findByTestConfiguration(metric.test(), platform); this._triggerable = triggerable && !triggerable.isDisabled() ? triggerable : null; this._metric = metric; this._measurementSet = MeasurementSet.findSet(platform.id(), metric.id(), lastModified); this._measurementSet.fetchBetween(task.startTime(), task.endTime(), this._didFetchMeasurement.bind(this)); const chart = this.part('chart-pane'); const domain = ChartsPage.createDomainForAnalysisTask(task); chart.configure(platform.id(), metric.id()); chart.setOverviewDomain(domain[0], domain[1]); chart.setMainDomain(domain[0], domain[1]); return task; } _didFetchMeasurement() { console.assert(this._task); console.assert(this._measurementSet); var series = this._measurementSet.fetchedTimeSeries('current', false, false); var startPoint = series.findById(this._task.startMeasurementId()); var endPoint = series.findById(this._task.endMeasurementId()); if (!startPoint || !endPoint || !this._measurementSet.hasFetchedRange(startPoint.time, endPoint.time)) return; this.part('results-pane').setPoints(startPoint, endPoint, this._task.metric()); this._startPoint = startPoint; this._endPoint = endPoint; this.enqueueToRender(); } _didFetchTestGroups(testGroups) { this._testGroups = testGroups.sort(function (a, b) { return +a.createdAt() - b.createdAt(); }); this._didUpdateTestGroupHiddenState(); this._assignTestResultsIfPossible(); this.enqueueToRender(); } _showAllTestGroups() { this._showHiddenTestGroups = true; this._didUpdateTestGroupHiddenState(); this.enqueueToRender(); } _didUpdateTestGroupHiddenState() { if (!this._showHiddenTestGroups) this._filteredTestGroups = this._testGroups.filter(function (group) { return !group.isHidden(); }); else this._filteredTestGroups = this._testGroups; this._showTestGroup(this._filteredTestGroups ? this._filteredTestGroups[this._filteredTestGroups.length - 1] : null); } _didFetchAnalysisResults(results) { this._analysisResults = results; if (this._assignTestResultsIfPossible()) this.enqueueToRender(); } _assignTestResultsIfPossible() { if (!this._task || !this._testGroups || !this._analysisResults) return false; let metric = this._metric; this.part('group-pane').setAnalysisResults(this._analysisResults, metric); if (metric) { const view = this._analysisResults.viewForMetric(metric); this.part('results-pane').setAnalysisResultsView(view); } return true; } render() { super.render(); Instrumentation.startMeasuringTime('AnalysisTaskPage', 'render'); this.content().querySelector('.error-message').textContent = this._errorMessage || ''; this._renderTaskNameAndStatusLazily.evaluate(this._task, this._task ? this._task.name() : null, this._task ? this._task.changeType() : null); this._renderCauseAndFixesLazily.evaluate(this._startPoint, this._task, this.part('cause-list'), this._task ? this._task.causes() : []); this._renderCauseAndFixesLazily.evaluate(this._startPoint, this._task, this.part('fix-list'), this._task ? this._task.fixes() : []); this._renderRelatedTasksLazily.evaluate(this._task, this._relatedTasks); this.content('chart-pane').style.display = this._task && !this._task.isCustom() ? null : 'none'; this.part('chart-pane').setShowForm(!!this._triggerable); this.content('results-pane').style.display = this._task && !this._task.isCustom() ? null : 'none'; this.part('results-pane').setShowForm(!!this._triggerable); this.content('configurator-pane').style.display = this._task && this._task.isCustom() ? null : 'none'; Instrumentation.endMeasuringTime('AnalysisTaskPage', 'render'); } _renderTaskNameAndStatus(task, taskName, changeType) { this.part('analysis-task-name').setText(taskName); if (task && !task.isCustom()) { const link = ComponentBase.createLink; const platform = task.platform(); const metric = task.metric(); const subtitle = `${metric.fullName()} on ${platform.label()}`; this.renderReplace(this.content('platform-metric-names'), link(subtitle, this.router().url('charts', ChartsPage.createStateForAnalysisTask(task)))); } this.content('change-type').value = changeType || 'unconfirmed'; } _renderRelatedTasks(task, relatedTasks) { const element = ComponentBase.createElement; const link = ComponentBase.createLink; this.renderReplace(this.content('related-tasks-list'), (task && relatedTasks ? relatedTasks : []).map((otherTask) => { let suffix = ''; const taskLabel = otherTask.label(); if (otherTask.metric() && otherTask.metric() != task.metric() && taskLabel.indexOf(otherTask.metric().label()) < 0) suffix += ` with "${otherTask.metric().label()}"`; if (otherTask.platform() && otherTask.platform() != task.platform() && taskLabel.indexOf(otherTask.platform().label()) < 0) suffix += ` on ${otherTask.platform().label()}`; return element('li', [link(taskLabel, this.router().url(`analysis/task/${otherTask.id()}`)), suffix]); })); } _renderCauseAndFixes(startPoint, task, list, commits) { const hasData = startPoint && task; this.content('cause-fix').style.display = hasData ? null : 'none'; if (!hasData) return; const commitSet = startPoint.commitSet(); const repositoryList = Repository.sortByNamePreferringOnesWithURL(commitSet.repositories()); const makeItem = (commit) => { return new MutableListItem(commit.repository(), commit.label(), commit.title(), commit.url(), 'Disassociate this commit', this._dissociateCommit.bind(this, commit)); } list.setKindList(repositoryList); list.setList(commits.map((commit) => makeItem(commit))); } _showTestGroup(testGroup) { this._currentTestGroup = testGroup; this.part('configurator-pane').setTestGroups(this._filteredTestGroups, this._currentTestGroup); this.part('results-pane').setTestGroups(this._filteredTestGroups, this._currentTestGroup); const groupsInReverseChronology = this._filteredTestGroups.slice(0).reverse(); const showHiddenGroups = !this._testGroups.some((group) => group.isHidden()) || this._showHiddenTestGroups; this.part('group-pane').setTestGroups(groupsInReverseChronology, this._currentTestGroup, showHiddenGroups); this.enqueueToRender(); } _updateTaskName(newName) { console.assert(this._task); return this._task.updateName(newName).then(() => { this.enqueueToRender(); }, (error) => { this.enqueueToRender(); alert('Failed to update the name: ' + error); }); } _updateTestGroupName(testGroup, newName) { return testGroup.updateName(newName).then(() => { this._showTestGroup(this._currentTestGroup); this.enqueueToRender(); }, (error) => { this.enqueueToRender(); alert('Failed to hide the test name: ' + error); }); } _hideCurrentTestGroup(testGroup) { return testGroup.updateHiddenFlag(!testGroup.isHidden()).then(() => { this._didUpdateTestGroupHiddenState(); this.enqueueToRender(); }, function (error) { this._mayHaveMutatedTestGroupHiddenState(); this.enqueueToRender(); alert('Failed to update the group: ' + error); }); } _updateChangeType(event) { event.preventDefault(); console.assert(this._task); let newChangeType = this.content('change-type').value; if (newChangeType == 'unconfirmed') newChangeType = null; const updateRendering = () => { this.part('chart-pane').didUpdateAnnotations(); this.enqueueToRender(); }; return this._task.updateChangeType(newChangeType).then(updateRendering, (error) => { updateRendering(); alert('Failed to update the status: ' + error); }); } _associateCommit(kind, repository, revision) { const updateRendering = () => { this.enqueueToRender(); }; return this._task.associateCommit(kind, repository, revision).then(updateRendering, (error) => { updateRendering(); if (error == 'AmbiguousRevision') alert('There are multiple revisions that match the specified string: ' + revision); else if (error == 'CommitNotFound') alert('There are no revisions that match the specified string:' + revision); else alert('Failed to associate the commit: ' + error); }); } _dissociateCommit(commit) { const updateRendering = () => { this.enqueueToRender(); }; return this._task.dissociateCommit(commit).then(updateRendering, (error) => { updateRendering(); alert('Failed to dissociate the commit: ' + error); }); } async _retryCurrentTestGroup(testGroup, repetitionCount, repetitionType, notifyOnCompletion) { const existingNames = (this._testGroups || []).map((group) => group.name()); const newName = CommitSet.createNameWithoutCollision(testGroup.name(), new Set(existingNames)); const commitSetList = testGroup.requestedCommitSets(); const platform = this._task.platform() || testGroup.platform(); try { const testGroups = await TestGroup.createWithCustomConfiguration(this._task, platform, testGroup.test(), newName, repetitionCount, repetitionType, commitSetList, notifyOnCompletion); this._didFetchTestGroups(testGroups); } catch (error) { alert('Failed to create a new test group: ' + error); } } async _bisectCurrentTestGroup(testGroup, commitSets, repetitionCount, repetitionType, notifyOnCompletion) { console.assert(testGroup.task()); const existingTestGroupNames = new Set((this._testGroups || []).map((testGroup) => testGroup.name())); for (let i = 1; i < commitSets.length; i++) { const previousCommitSet = commitSets[i - 1]; const currentCommitSet = commitSets[i]; const testGroupName = CommitSet.createNameWithoutCollision(CommitSet.diff(previousCommitSet, currentCommitSet), existingTestGroupNames); try { const testGroups = await TestGroup.createAndRefetchTestGroups(testGroup.task(), testGroupName, repetitionCount, repetitionType, [previousCommitSet, currentCommitSet], notifyOnCompletion); this._didFetchTestGroups(testGroups); } catch(error) { alert('Failed to create a new test group: ' + error); break; } } } async _createTestGroupAfterVerifyingCommitSetList(testGroupName, repetitionCount, repetitionType, commitSetMap, notifyOnCompletion) { if (this._hasDuplicateTestGroupName(testGroupName)) { alert(`There is already a test group named "${testGroupName}"`); return; } const firstLabel = Object.keys(commitSetMap)[0]; const firstCommitSet = commitSetMap[firstLabel]; for (let currentLabel in commitSetMap) { const commitSet = commitSetMap[currentLabel]; for (let repository of commitSet.repositories()) { if (!firstCommitSet.revisionForRepository(repository)) return alert(`Set ${currentLabel} specifies ${repository.label()} but set ${firstLabel} does not.`); } for (let repository of firstCommitSet.repositories()) { if (!commitSet.revisionForRepository(repository)) return alert(`Set ${firstLabel} specifies ${repository.label()} but set ${currentLabel} does not.`); } } const commitSets = []; for (let label in commitSetMap) commitSets.push(commitSetMap[label]); try { const testGroups = await TestGroup.createAndRefetchTestGroups(this._task, testGroupName, repetitionCount, repetitionType, commitSets, notifyOnCompletion); return this._didFetchTestGroups(testGroups); } catch (error) { alert('Failed to create a new test group: ' + error); } } async _createCustomTestGroup(testGroupName, repetitionCount, repetitionType, commitSets, platform, test, notifyOnCompletion) { console.assert(this._task.isCustom()); if (this._hasDuplicateTestGroupName(testGroupName)) { alert(`There is already a test group named "${testGroupName}"`); return; } try { const testGroups = await TestGroup.createWithCustomConfiguration(this._task, platform, test, testGroupName, repetitionCount, repetitionType, commitSets, notifyOnCompletion); this._didFetchTestGroups(testGroups); } catch (error) { alert('Failed to create a new test group: ' + error); } } _hasDuplicateTestGroupName(name) { console.assert(this._testGroups); for (var group of this._testGroups) { if (group.name() == name) return true; } return false; } static htmlTemplate() { return ` <div class="analysis-task-page"> <h2 class="analysis-task-name"><editable-text id="analysis-task-name"></editable-text></h2> <h3 id="platform-metric-names"></h3> <p class="error-message"></p> <div class="analysis-task-status"> <section> <h3>Status</h3> <form id="change-type-form"> <select id="change-type"> <option value="unconfirmed">Unconfirmed</option> <option value="regression">Definite regression</option> <option value="progression">Definite progression</option> <option value="inconclusive">Inconclusive (Closed)</option> <option value="unchanged">No change (Closed)</option> </select> <button type="submit">Save</button> </form> </section> <section class="associated-bugs"> <h3>Associated Bugs</h3> <analysis-task-bug-list id="bug-list"></analysis-task-bug-list> </section> <section id="cause-fix"> <h3>Caused by</h3> <mutable-list-view id="cause-list"></mutable-list-view> <h3>Fixed by</h3> <mutable-list-view id="fix-list"></mutable-list-view> </section> <section class="related-tasks"> <h3>Related Tasks</h3> <ul id="related-tasks-list"></ul> </section> </div> <analysis-task-chart-pane id="chart-pane"></analysis-task-chart-pane> <analysis-task-results-pane id="results-pane"></analysis-task-results-pane> <analysis-task-configurator-pane id="configurator-pane"></analysis-task-configurator-pane> <analysis-task-test-group-pane id="group-pane"></analysis-task-test-group-pane> </div> `; } static cssTemplate() { return ` .analysis-task-page { } .analysis-task-name { font-size: 1.2rem; font-weight: inherit; color: #c93; margin: 0 1rem; padding: 0; } #platform-metric-names { font-size: 1rem; font-weight: inherit; color: #c93; margin: 0 1rem; padding: 0; } #platform-metric-names a { text-decoration: none; color: inherit; } #platform-metric-names:empty { visibility: hidden; height: 0; width: 0; /* FIXME: Use display: none instead once r214290 is shipped everywhere */ } .error-message:empty { visibility: hidden; height: 0; width: 0; /* FIXME: Use display: none instead once r214290 is shipped everywhere */ } .error-message:not(:empty) { margin: 1rem; padding: 0; } #chart-pane, #results-pane { display: block; padding: 0 1rem; border-bottom: solid 1px #ccc; } #results-pane { margin-top: 1rem; } #group-pane { margin: 1rem; margin-bottom: 2rem; } .analysis-task-status { margin: 0; display: flex; padding-bottom: 1rem; margin-bottom: 1rem; border-bottom: solid 1px #ccc; } .analysis-task-status > section { flex-grow: 1; flex-shrink: 0; border-left: solid 1px #eee; padding-left: 1rem; padding-right: 1rem; } .analysis-task-status > section.related-tasks { flex-shrink: 1; } .analysis-task-status > section:first-child { border-left: none; } .analysis-task-status h3 { font-size: 1rem; font-weight: inherit; color: #c93; } .analysis-task-status ul, .analysis-task-status li { list-style: none; padding: 0; margin: 0; } .related-tasks-list { max-height: 10rem; overflow-y: scroll; } .test-configuration h3 { font-size: 1rem; font-weight: inherit; color: inherit; margin: 0 1rem; padding: 0; }`; } }