blob: 37860becaa8e58a9e0e2819e95813601a6e87f20 [file] [log] [blame]
class CustomAnalysisTaskConfigurator extends ComponentBase {
constructor()
{
super('custom-analysis-task-configurator');
this._selectedTests = [];
this._triggerablePlatforms = [];
this._selectedPlatform = null;
this._showComparison = false;
this._commitSetMap = {};
this._specifiedRevisions = {'Baseline': new Map, 'Comparison': new Map};
this._patchUploaders = {'Baseline': new Map, 'Comparison': new Map};
this._customRootUploaders = {'Baseline': null, 'Comparison': null};
this._fetchedCommits = {'Baseline': new Map, 'Comparison': new Map};
this._repositoryGroupByConfiguration = {'Baseline': null, 'Comparison': null};
this._invalidRevisionsByConfiguration = {'Baseline': new Map, 'Comparison': new Map};
this._updateTriggerableLazily = new LazilyEvaluatedFunction(this._updateTriggerable.bind(this));
this._renderTriggerableTestsLazily = new LazilyEvaluatedFunction(this._renderTriggerableTests.bind(this));
this._renderTriggerablePlatformsLazily = new LazilyEvaluatedFunction(this._renderTriggerablePlatforms.bind(this));
this._renderRepositoryPanesLazily = new LazilyEvaluatedFunction(this._renderRepositoryPanes.bind(this));
}
tests() { return this._selectedTests; }
platform() { return this._selectedPlatform; }
commitSets()
{
const map = this._commitSetMap;
if (!map['Baseline'] || !map['Comparison'])
return null;
return [map['Baseline'], map['Comparison']];
}
selectTests(selectedTests)
{
this._selectedTests = selectedTests;
this._triggerablePlatforms = Triggerable.triggerablePlatformsForTests(this._selectedTests);
if (this._selectedTests.length && !this._triggerablePlatforms.includes(this._selectedPlatform))
this._selectedPlatform = null;
this._didUpdateSelectedPlatforms();
}
selectPlatform(selectedPlatform)
{
this._selectedPlatform = selectedPlatform;
const [triggerable, error] = this._updateTriggerableLazily.evaluate(this._selectedTests, this._selectedPlatform);
this._updateRepositoryGroups(triggerable);
this._didUpdateSelectedPlatforms();
}
_didUpdateSelectedPlatforms()
{
for (const configuration of ['Baseline', 'Comparison']) {
this._updateMapFromSpecifiedRevisionsForConfiguration(this._fetchedCommits, configuration);
this._updateMapFromSpecifiedRevisionsForConfiguration(this._invalidRevisionsByConfiguration, configuration);
}
this._updateCommitSetMap();
this.dispatchAction('testConfigChange');
this.enqueueToRender();
}
_updateMapFromSpecifiedRevisionsForConfiguration(map, configuration)
{
const referenceMap = this._specifiedRevisions[configuration];
const newValue = new Map;
for (const [key, value] of map[configuration].entries()) {
if (!referenceMap.has(key))
continue;
newValue.set(key, value);
}
if (newValue.size !== map[configuration].size)
map[configuration] = newValue;
}
setCommitSets(baselineCommitSet, comparisonCommitSet)
{
const [triggerable, error] = this._updateTriggerableLazily.evaluate(this._selectedTests, this._selectedPlatform);
if (!triggerable)
return;
const baselineRepositoryGroup = triggerable.repositoryGroups().find((repositoryGroup) => repositoryGroup.accepts(baselineCommitSet));
if (baselineRepositoryGroup) {
this._repositoryGroupByConfiguration['Baseline'] = baselineRepositoryGroup;
this._setUploadedFilesToUploader(this._customRootUploaders['Baseline'], baselineCommitSet.customRoots());
this._specifiedRevisions['Baseline'] = this._revisionMapFromCommitSet(baselineCommitSet);
this._setPatchFiles('Baseline', baselineCommitSet);
}
const comparisonRepositoryGroup = triggerable.repositoryGroups().find((repositoryGroup) => repositoryGroup.accepts(baselineCommitSet));
if (comparisonRepositoryGroup) {
this._repositoryGroupByConfiguration['Comparison'] = comparisonRepositoryGroup;
this._setUploadedFilesToUploader(this._customRootUploaders['Comparison'], comparisonCommitSet.customRoots());
this._specifiedRevisions['Comparison'] = this._revisionMapFromCommitSet(comparisonCommitSet);
this._setPatchFiles('Comparison', comparisonCommitSet);
}
this._showComparison = true;
this._updateCommitSetMap();
}
_setUploadedFilesToUploader(uploader, files)
{
if (!uploader || uploader.hasFileToUpload() || uploader.uploadedFiles().length)
return;
uploader.clearUploads();
for (const uploadedFile of files)
uploader.addUploadedFile(uploadedFile);
}
_setPatchFiles(configurationName, commitSet)
{
for (const repository of commitSet.repositories()) {
const patch = commitSet.patchForRepository(repository);
if (patch)
this._setUploadedFilesToUploader(this._ensurePatchUploader(configurationName, repository), [patch]);
}
}
_revisionMapFromCommitSet(commitSet)
{
const revisionMap = new Map;
for (const repository of commitSet.repositories())
revisionMap.set(repository, commitSet.revisionForRepository(repository));
return revisionMap;
}
didConstructShadowTree()
{
this.content('specify-comparison-button').onclick = this.createEventHandler(() => this._configureComparison());
const createRootUploader = () => {
const uploader = new InstantFileUploader;
uploader.allowMultipleFiles();
uploader.element().textContent = 'Add a new root';
uploader.listenToAction('removedFile', () => this._updateCommitSetMap());
return uploader;
}
const baselineRootsUploader = createRootUploader();
baselineRootsUploader.listenToAction('uploadedFile', (uploadedFile) => this._updateCommitSetMap());
this._customRootUploaders['Baseline'] = baselineRootsUploader;
const comparisonRootsUploader = createRootUploader();
comparisonRootsUploader.listenToAction('uploadedFile', () => this._updateCommitSetMap());
this._customRootUploaders['Comparison'] = comparisonRootsUploader;
}
_ensurePatchUploader(configurationName, repository)
{
const uploaderMap = this._patchUploaders[configurationName];
let uploader = uploaderMap.get(repository);
if (uploader)
return uploader;
uploader = new InstantFileUploader;
uploader.element().textContent = 'Apply a patch';
uploader.listenToAction('uploadedFile', () => this._updateCommitSetMap());
uploader.listenToAction('removedFile', () => this._updateCommitSetMap());
uploaderMap.set(repository, uploader);
return uploader;
}
_configureComparison()
{
this._showComparison = true;
this._repositoryGroupByConfiguration['Comparison'] = this._repositoryGroupByConfiguration['Baseline'];
const specifiedBaselineRevisions = this._specifiedRevisions['Baseline'];
const specifiedComparisonRevisions = new Map;
for (let key of specifiedBaselineRevisions.keys())
specifiedComparisonRevisions.set(key, specifiedBaselineRevisions.get(key));
this._specifiedRevisions['Comparison'] = specifiedComparisonRevisions;
for (const [repository, patchUploader] of this._patchUploaders['Baseline']) {
const files = patchUploader.uploadedFiles();
if (!files.length)
continue;
const comparisonPatchUploader = this._ensurePatchUploader('Comparison', repository);
for (const uploadedFile of files)
comparisonPatchUploader.addUploadedFile(uploadedFile);
}
const comparisonRootUploader = this._customRootUploaders['Comparison'];
for (const uploadedFile of this._customRootUploaders['Baseline'].uploadedFiles())
comparisonRootUploader.addUploadedFile(uploadedFile);
this.enqueueToRender();
}
render()
{
super.render();
const updateSelectedTestsLazily = this._renderTriggerableTestsLazily.evaluate();
updateSelectedTestsLazily.evaluate(...this._selectedTests);
const updateSelectedPlatformsLazily = this._renderTriggerablePlatformsLazily.evaluate(this._selectedTests, this._triggerablePlatforms);
if (updateSelectedPlatformsLazily)
updateSelectedPlatformsLazily.evaluate(this._selectedPlatform);
const [triggerable, error] = this._updateTriggerableLazily.evaluate(this._selectedTests, this._selectedPlatform);
this._renderRepositoryPanesLazily.evaluate(triggerable, error, this._selectedPlatform, this._repositoryGroupByConfiguration, this._showComparison);
this.renderReplace(this.content('baseline-testability'), this._buildTestabilityList(this._commitSetMap['Baseline'],
'Baseline', this._invalidRevisionsByConfiguration['Baseline']));
this.renderReplace(this.content('comparison-testability'), !this._showComparison ? null :
this._buildTestabilityList(this._commitSetMap['Comparison'], 'Comparison', this._invalidRevisionsByConfiguration['Comparison']));
}
_renderTriggerableTests()
{
const enabledTriggerables = Triggerable.all().filter((triggerable) => !triggerable.isDisabled());
const acceptedTests = new Set;
for (const triggerable of enabledTriggerables) {
for (const test of triggerable.acceptedTests())
acceptedTests.add(test);
}
const tests = [...acceptedTests].sort((testA, testB) => {
if (testA.fullName() == testB.fullName())
return 0;
return testA.fullName() < testB.fullName() ? -1 : 1;
});
return this._renderRadioButtonList(this.content('test-list'), 'test', tests, this.selectTests.bind(this), (test) => test.fullName());
}
_renderTriggerablePlatforms(selectedTests, triggerablePlatforms)
{
if (!selectedTests.length) {
this.content('platform-pane').style.display = 'none';
return null;
}
this.content('platform-pane').style.display = null;
return this._renderRadioButtonList(this.content('platform-list'), 'platform', triggerablePlatforms, (selectedPlatforms) => {
this.selectPlatform(selectedPlatforms.length ? selectedPlatforms[0] : null);
});
}
_renderRadioButtonList(listContainer, name, objects, callback, labelForObject = (object) => object.label())
{
const listItems = [];
let selectedListItems = [];
const checkSelectedRadioButtons = (newSelectedListItems) => {
selectedListItems.forEach((item) => {
item.label.classList.remove('selected');
item.radioButton.checked = false;
});
selectedListItems = newSelectedListItems;
selectedListItems.forEach((item) => {
item.label.classList.add('selected');
item.radioButton.checked = true;
});
}
const element = ComponentBase.createElement;
this.renderReplace(listContainer, objects.map((object) => {
const radioButton = element('input', {type: 'radio', name: name, onchange: () => {
checkSelectedRadioButtons(listItems.filter((item) => item.radioButton.checked));
callback(selectedListItems.map((item) => item.object));
this.enqueueToRender();
}});
const label = element('label', [radioButton, labelForObject(object)]);
listItems.push({radioButton, label, object});
return element('li', label);
}));
return new LazilyEvaluatedFunction((...selectedObjects) => {
const objects = new Set(selectedObjects);
checkSelectedRadioButtons(listItems.filter((item) => objects.has(item.object)));
});
}
_updateTriggerable(tests, platform)
{
let triggerable = null;
let error = null;
if (tests.length && platform) {
triggerable = Triggerable.findByTestConfiguration(tests[0], platform);
let matchingTests = new Set;
let mismatchingTests = new Set;
for (let test of tests) {
if (Triggerable.findByTestConfiguration(test, platform) == triggerable)
matchingTests.add(test);
else
mismatchingTests.add(test);
}
if (matchingTests.size < tests.length) {
const matchingTestNames = [...matchingTests].map((test) => test.fullName()).sort().join('", "');
const mismathingTestNames = [...mismatchingTests].map((test) => test.fullName()).sort().join('", "');
error = `Tests "${matchingTestNames}" and "${mismathingTestNames}" cannot be scheduled
simultenosuly on "${platform.name()}". Please select one of them at a time.`;
}
}
return [triggerable, error];
}
_updateRepositoryGroups(triggerable)
{
const repositoryGroups = triggerable ? TriggerableRepositoryGroup.sortByNamePreferringSmallerRepositories(triggerable.repositoryGroups()) : [];
for (let name in this._repositoryGroupByConfiguration) {
const currentGroup = this._repositoryGroupByConfiguration[name];
let matchingGroup = null;
if (currentGroup) {
if (repositoryGroups.includes(currentGroup))
matchingGroup = currentGroup;
else
matchingGroup = repositoryGroups.find((group) => group.name() == currentGroup.name());
}
if (!matchingGroup && repositoryGroups.length)
matchingGroup = repositoryGroups[0];
this._repositoryGroupByConfiguration[name] = matchingGroup;
}
}
_updateCommitSetMap()
{
const newBaseline = this._computeCommitSet('Baseline');
let newComparison = this._computeCommitSet('Comparison');
if (newBaseline && newComparison && newBaseline.equals(newComparison))
newComparison = null;
const currentBaseline = this._commitSetMap['Baseline'];
const currentComparison = this._commitSetMap['Comparison'];
const areCommitSetsEqual = (commitSetA, commitSetB) => commitSetA == commitSetB || (commitSetA && commitSetB && commitSetA.equals(commitSetB));
const sameBaselineConfig = areCommitSetsEqual(currentBaseline, newBaseline);
const sameComparisionConfig = areCommitSetsEqual(currentComparison, newComparison);
if (sameBaselineConfig && sameComparisionConfig)
return;
this._commitSetMap = {'Baseline': newBaseline, 'Comparison': newComparison};
this.dispatchAction('testConfigChange');
this.enqueueToRender();
}
_computeCommitSet(configurationName)
{
const repositoryGroup = this._repositoryGroupByConfiguration[configurationName];
if (!repositoryGroup)
return null;
const fileUploader = this._customRootUploaders[configurationName];
if (!fileUploader || fileUploader.hasFileToUpload())
return null;
const commitSet = new CustomCommitSet;
for (let repository of repositoryGroup.repositories()) {
let revision = this._specifiedRevisions[configurationName].get(repository);
const commit = this._fetchedCommits[configurationName].get(repository);
if (commit) {
const commitLabel = commit.label();
if (!revision || commit.revision().startsWith(revision) || commitLabel.startsWith(revision) || revision.startsWith(commitLabel))
revision = commit.revision();
}
if (!revision)
return null;
let patch = null;
if (repositoryGroup.acceptsPatchForRepository(repository)) {
const uploaderMap = this._patchUploaders[configurationName];
const uploader = uploaderMap.get(repository);
if (uploader) {
const files = uploader.uploadedFiles();
console.assert(files.length <= 1);
if (files.length)
patch = files[0];
}
}
commitSet.setRevisionForRepository(repository, revision, patch);
}
for (let uploadedFile of fileUploader.uploadedFiles())
commitSet.addCustomRoot(uploadedFile);
return commitSet;
}
async _fetchCommitsForConfiguration(configurationName)
{
const commitSet = this._commitSetMap[configurationName];
if (!commitSet)
return;
const specifiedRevisions = this._specifiedRevisions[configurationName];
const fetchedCommits = this._fetchedCommits[configurationName];
const invalidRevisionForRepository = this._invalidRevisionsByConfiguration[configurationName];
await Promise.all(Array.from(commitSet.repositories()).map((repository) => {
const revision = commitSet.revisionForRepository(repository);
return this._resolveRevision(repository, revision, specifiedRevisions, invalidRevisionForRepository, fetchedCommits);
}));
const latestCommitSet = this._commitSetMap[configurationName];
if (commitSet != latestCommitSet)
return;
this.enqueueToRender();
}
async _resolveRevision(repository, revision, specifiedRevisions, invalidRevisionForRepository, fetchedCommits)
{
const fetchedCommit = fetchedCommits.get(repository);
const specifiedRevision = specifiedRevisions.get(repository);
if (fetchedCommit && fetchedCommit.revision() == revision && (!specifiedRevision || specifiedRevision == revision))
return;
fetchedCommits.delete(repository);
let commits = [];
const revisionToFetch = specifiedRevision || revision;
try {
commits = await CommitLog.fetchForSingleRevision(repository, revisionToFetch, true);
} catch (error) {
console.assert(error == 'UnknownCommit' || error == 'AmbiguousRevisionPrefix');
if (revisionToFetch != specifiedRevisions.get(repository))
return;
invalidRevisionForRepository.set(repository, `"${revisionToFetch}": ${error == 'UnknownCommit' ? 'Invalid revision' : 'Ambiguous revision prefix'}`);
return;
}
console.assert(commits.length, 1);
if (revisionToFetch != specifiedRevisions.get(repository))
return;
invalidRevisionForRepository.delete(repository);
fetchedCommits.set(repository, commits[0]);
if (revisionToFetch != commits[0].revision())
this._updateCommitSetMap();
}
_renderRepositoryPanes(triggerable, error, platform, repositoryGroupByConfiguration, showComparison)
{
this.content('repository-configuration-error-pane').style.display = error ? null : 'none';
this.content('error').textContent = error;
this.content('baseline-configuration-pane').style.display = triggerable ? null : 'none';
this.content('specify-comparison-pane').style.display = triggerable && !showComparison ? null : 'none';
this.content('comparison-configuration-pane').style.display = triggerable && showComparison ? null : 'none';
if (!triggerable)
return;
const visibleRepositoryGroups = triggerable.repositoryGroups().filter((group) => !group.isHidden());
const repositoryGroups = TriggerableRepositoryGroup.sortByNamePreferringSmallerRepositories(visibleRepositoryGroups);
const repositorySet = new Set;
for (let group of repositoryGroups) {
for (let repository of group.repositories())
repositorySet.add(repository);
}
const repositories = Repository.sortByNamePreferringOnesWithURL([...repositorySet]);
const requiredRepositories = repositories.filter((repository) => {
return repositoryGroups.every((group) => group.repositories().includes(repository));
});
const alwaysAcceptsCustomRoots = repositoryGroups.every((group) => group.acceptsCustomRoots());
this._renderBaselineRevisionTable(platform, repositoryGroups, requiredRepositories, repositoryGroupByConfiguration, alwaysAcceptsCustomRoots);
if (showComparison)
this._renderComparisonRevisionTable(platform, repositoryGroups, requiredRepositories, repositoryGroupByConfiguration, alwaysAcceptsCustomRoots);
}
_renderBaselineRevisionTable(platform, repositoryGroups, requiredRepositories, repositoryGroupByConfiguration, alwaysAcceptsCustomRoots)
{
let currentGroup = repositoryGroupByConfiguration['Baseline'];
const optionalRepositoryList = this._optionalRepositoryList(currentGroup, requiredRepositories);
this.renderReplace(this.content('baseline-revision-table'),
this._buildRevisionTable('Baseline', repositoryGroups, currentGroup, platform, requiredRepositories, optionalRepositoryList, alwaysAcceptsCustomRoots));
}
_renderComparisonRevisionTable(platform, repositoryGroups, requiredRepositories, repositoryGroupByConfiguration, alwaysAcceptsCustomRoots)
{
let currentGroup = repositoryGroupByConfiguration['Comparison'];
const optionalRepositoryList = this._optionalRepositoryList(currentGroup, requiredRepositories);
this.renderReplace(this.content('comparison-revision-table'),
this._buildRevisionTable('Comparison', repositoryGroups, currentGroup, platform, requiredRepositories, optionalRepositoryList, alwaysAcceptsCustomRoots));
}
_optionalRepositoryList(currentGroup, requiredRepositories)
{
if (!currentGroup)
return [];
return Repository.sortByNamePreferringOnesWithURL(currentGroup.repositories().filter((repository) => !requiredRepositories.includes(repository)));
}
_buildRevisionTable(configurationName, repositoryGroups, currentGroup, platform, requiredRepositories, optionalRepositoryList, alwaysAcceptsCustomRoots)
{
const element = ComponentBase.createElement;
const customRootsTBody = element('tbody', [
element('tr', [
element('th', 'Roots'),
element('td', this._customRootUploaders[configurationName]),
]),
]);
return [
element('tbody',
requiredRepositories.map((repository) => {
return element('tr', [
element('th', repository.name()),
element('td', this._buildRevisionInput(configurationName, repository, platform))
]);
})),
alwaysAcceptsCustomRoots ? customRootsTBody : [],
element('tbody', [
element('tr', {'class': 'group-row'},
element('td', {colspan: 2}, this._buildRepositoryGroupList(repositoryGroups, currentGroup, configurationName))),
]),
!alwaysAcceptsCustomRoots && currentGroup && currentGroup.acceptsCustomRoots() ? customRootsTBody : [],
element('tbody',
optionalRepositoryList.map((repository) => {
let uploader = currentGroup.acceptsPatchForRepository(repository)
? this._ensurePatchUploader(configurationName, repository) : null;
return element('tr',[
element('th', repository.name()),
element('td', [
this._buildRevisionInput(configurationName, repository, platform),
uploader || [],
])
]);
})
)];
}
_buildTestabilityList(commitSet, configurationName, invalidRevisionForRepository)
{
const element = ComponentBase.createElement;
const entries = [];
if (!commitSet || !commitSet.repositories().length)
return [];
for (const repository of commitSet.repositories()) {
const commit = this._fetchedCommits[configurationName].get(repository);
if (commit && commit.testability() && !invalidRevisionForRepository.has(repository))
entries.push(element('li', `${commit.repository().name()} - "${commit.label()}": ${commit.testability()}`));
if (invalidRevisionForRepository.has(repository))
entries.push(element('li', `${repository.name()} - ${invalidRevisionForRepository.get(repository)}`));
}
return entries;
}
_buildRepositoryGroupList(repositoryGroups, currentGroup, configurationName)
{
const element = ComponentBase.createElement;
return repositoryGroups.map((group) => {
const input = element('input', {
type: 'radio',
name: 'repositoryGroup-for-' + configurationName.toLowerCase(),
checked: currentGroup == group,
onchange: () => this._selectRepositoryGroup(configurationName, group)
});
return [element('label', [input, group.description()])];
});
}
_selectRepositoryGroup(configurationName, group)
{
const source = this._repositoryGroupByConfiguration;
const clone = {};
for (let key in source)
clone[key] = source[key];
clone[configurationName] = group;
this._repositoryGroupByConfiguration = clone;
this._updateCommitSetMap();
this._fetchCommitsForConfiguration(configurationName);
this.enqueueToRender();
}
_buildRevisionInput(configurationName, repository, platform)
{
const revision = this._specifiedRevisions[configurationName].get(repository) || '';
const element = ComponentBase.createElement;
let scheduledUpdate = null;
const input = element('input', {value: revision, oninput: () => {
unmodifiedInput = null;
const revisionToFetch = input.value;
this._specifiedRevisions[configurationName].set(repository, revisionToFetch);
this._updateCommitSetMap();
if (scheduledUpdate)
clearTimeout(scheduledUpdate);
scheduledUpdate = setTimeout(() => {
if (revisionToFetch == input.value)
this._fetchCommitsForConfiguration(configurationName);
scheduledUpdate = null;
}, CustomAnalysisTaskConfigurator.commitFetchInterval);
}});
let unmodifiedInput = input;
if (!revision) {
CommitLog.fetchLatestCommitForPlatform(repository, platform).then((commit) => {
if (commit && unmodifiedInput) {
unmodifiedInput.value = commit.revision();
this._fetchedCommits[configurationName].set(repository, commit);
this._updateCommitSetMap();
}
});
}
return input;
}
static htmlTemplate()
{
return `
<section id="test-pane" class="pane">
<h2>1. Select a Test</h2>
<ul id="test-list" class="config-list"></ul>
</section>
<section id="platform-pane" class="pane">
<h2>2. Select a Platform</h2>
<ul id="platform-list" class="config-list"></ul>
</section>
<section id="repository-configuration-error-pane" class="pane">
<h2>Incompatible tests</h2>
<p id="error"></p>
</section>
<section id="baseline-configuration-pane" class="pane">
<h2>3. Configure Baseline</h2>
<table id="baseline-revision-table" class="revision-table"></table>
<ul id="baseline-testability"></ul>
</section>
<section id="specify-comparison-pane" class="pane">
<button id="specify-comparison-button">Configure to Compare</button>
</section>
<section id="comparison-configuration-pane" class="pane">
<h2>4. Configure Comparison</h2>
<table id="comparison-revision-table" class="revision-table"></table>
<ul id="comparison-testability"></ul>
</section>`;
}
static cssTemplate()
{
return `
:host {
display: flex !important;
flex-direction: row !important;
}
.pane {
margin-right: 1rem;
padding: 0;
}
.pane h2 {
padding: 0;
margin: 0;
margin-bottom: 0.5rem;
font-size: 1.2rem;
font-weight: inherit;
text-align: center;
white-space: nowrap;
}
.config-list {
height: 20rem;
overflow: scroll;
display: block;
margin: 0;
padding: 0;
list-style: none;
font-size: inherit;
font-weight: inherit;
border: none;
border-top: solid 1px #ddd;
border-bottom: solid 1px #ddd;
white-space: nowrap;
}
#platform-list:empty:before {
content: "No matching platform";
display: block;
margin: 1rem 0.5rem;
text-align: center;
}
.config-list label {
display: block;
padding: 0.1rem 0.2rem;
}
.config-list label:hover,
.config-list a:hover {
background: rgba(204, 153, 51, 0.1);
}
.config-list label.selected,
.config-list a.selected {
background: #eee;
}
.config-list a {
display: block;
padding: 0.1rem 0.2rem;
text-decoration: none;
color: inherit;
}
#repository-configuration-pane {
position: relative;
}
#repository-configuration-pane > button {
margin-left: 19.5rem;
}
.revision-table {
border: none;
border-collapse: collapse;
font-size: 1rem;
}
.revision-table thead {
font-size: 1.2rem;
}
.revision-table tbody:empty {
display: none;
}
.revision-table tbody td,
.revision-table tbody th {
border-top: solid 1px #ddd;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.revision-table td,
.revision-table th {
width: 15rem;
height: 1.8rem;
padding: 0 0.2rem;
border: none;
font-weight: inherit;
}
.revision-table thead th {
text-align: center;
}
.revision-table th close-button {
vertical-align: bottom;
}
.revision-table td:first-child,
.revision-table th:first-child {
width: 6rem;
}
.revision-table tr.group-row td {
padding-left: 5rem;
}
label {
white-space: nowrap;
display: block;
}
input:not([type=radio]) {
width: calc(100% - 0.6rem);
padding: 0.1rem 0.2rem;
font-size: 0.9rem;
font-weight: inherit;
}
#specify-comparison-pane button {
margin-top: 1.5rem;
font-size: 1.1rem;
font-weight: inherit;
}
#start-pane button {
margin-top: 1.5rem;
font-size: 1.2rem;
font-weight: inherit;
}
#baseline-testability li,
#comparison-testability li {
color: #c33;
width: 20rem;
}
`;
}
}
CustomAnalysisTaskConfigurator.commitFetchInterval = 100;
ComponentBase.defineElement('custom-analysis-task-configurator', CustomAnalysisTaskConfigurator);