blob: 3f4fefff27a438364305bbb83d7b16b32405ec71 [file] [log] [blame]
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', (repetitionCount, name, commitSetMap, notifyOnCompletion) => {
this.dispatchAction('newTestGroup', name, repetitionCount, 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);
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', (repetitionCount, name, commitSetMap, notifyOnCompletion) => {
this.dispatchAction('newTestGroup', name, repetitionCount, 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.repetitionCount(), 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, notifyOnCompletion) => {
this.dispatchAction('retryTestGroup', this._currentTestGroup, repetitionCount, notifyOnCompletion);
});
this.part('bisect-form').listenToAction('startTesting', (repetitionCount, 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, 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._testGroupMap.get(currentGroup).listItem.classList.add('selected');
if (currentGroup) {
this.part('retry-form').setRepetitionCount(currentGroup.initialRepetitionCount());
this.part('bisect-form').setRepetitionCount(currentGroup.initialRepetitionCount());
const statusSummary = `${currentGroup.initialRepetitionCount()} requested, ${currentGroup.repetitionCount() - currentGroup.initialRepetitionCount()} added due to failures.`;
this.content('status-summary').innerHTML = statusSummary;
const authoredBy = currentGroup.author() ? `by "${currentGroup.author()}"` : '';
this.content('request-summary').innerHTML = `Scheduled ${authoredBy} at ${currentGroup.createdAt()}`
}
this.content('retry-form').style.display = currentGroup ? null : 'none';
this.content('bisect-form').style.display = currentGroup && this._bisectingCommitSetByTestGroup.get(currentGroup) ? null : 'none';
this.content('status-summary').style.display = currentGroup && currentGroup.repetitionCount() > currentGroup.initialRepetitionCount() ? null : 'none';
this.content('request-summary').style.display = currentGroup ? null : 'none';
const hideButton = this.content('hide-button');
hideButton.textContent = currentGroup && currentGroup.isHidden() ? 'Unhide' : 'Hide';
hideButton.style.display = currentGroup ? null : 'none';
this.content('pending-request-cancel-warning').style.display = currentGroup && currentGroup.hasPending() ? null : 'none';
}
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="status-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, notifyOnCompletion) => this._retryCurrentTestGroup(testGroup, repetitionCount, notifyOnCompletion));
groupPane.listenToAction('bisectTestGroup', (testGroup, commitSets, repetitionCount, notifyOnCompletion) => this._bisectCurrentTestGroup(testGroup, commitSets, repetitionCount, 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);
});
}
_retryCurrentTestGroup(testGroup, repetitionCount, 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();
return TestGroup.createWithCustomConfiguration(this._task, platform, testGroup.test(), newName, repetitionCount, commitSetList, notifyOnCompletion)
.then(this._didFetchTestGroups.bind(this), function (error) {
alert('Failed to create a new test group: ' + error);
});
}
async _bisectCurrentTestGroup(testGroup, commitSets, repetitionCount, 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, [previousCommitSet, currentCommitSet], notifyOnCompletion);
await this._didFetchTestGroups(testGroups);
} catch(error) {
alert('Failed to create a new test group: ' + error);
break;
}
}
}
_createTestGroupAfterVerifyingCommitSetList(testGroupName, repetitionCount, 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]);
return TestGroup.createAndRefetchTestGroups(this._task, testGroupName, repetitionCount, commitSets, notifyOnCompletion)
.then(this._didFetchTestGroups.bind(this), function (error) {
alert('Failed to create a new test group: ' + error);
});
}
_createCustomTestGroup(repetitionCount, testGroupName, commitSets, platform, test, notifyOnCompletion)
{
console.assert(this._task.isCustom());
if (this._hasDuplicateTestGroupName(testGroupName)) {
alert(`There is already a test group named "${testGroupName}"`);
return;
}
TestGroup.createWithCustomConfiguration(this._task, platform, test, testGroupName, repetitionCount, commitSets, notifyOnCompletion)
.then(this._didFetchTestGroups.bind(this), function (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;
}`;
}
}