@@ -23,8 +23,12 @@ import com.sonar.orchestrator.Orchestrator; | |||
import com.sonar.orchestrator.build.SonarScanner; | |||
import com.sonar.orchestrator.selenium.Selenese; | |||
import it.Category1Suite; | |||
import org.junit.BeforeClass; | |||
import org.junit.ClassRule; | |||
import org.junit.Test; | |||
import pageobjects.BackgroundTaskItem; | |||
import pageobjects.BackgroundTasksPage; | |||
import pageobjects.Navigation; | |||
import util.selenium.SeleneseTest; | |||
import static util.ItUtils.projectDir; | |||
@@ -34,18 +38,34 @@ public class BackgroundTasksTest { | |||
@ClassRule | |||
public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; | |||
@Test | |||
public void should_not_display_failing_and_search_and_filter_elements_on_project_level_page() throws Exception { | |||
@BeforeClass | |||
public static void beforeClass() { | |||
executeBuild("test-project", "Test Project"); | |||
executeBuild("test-project-2", "Another Test Project"); | |||
} | |||
@Test | |||
public void should_not_display_failing_and_search_and_filter_elements_on_project_level_page() throws Exception { | |||
Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("should_not_display_failing_and_search_and_filter_elements_on_project_level_page", | |||
"/projectAdministration/BackgroundTasksTest/should_not_display_failing_and_search_and_filter_elements_on_project_level_page.html" | |||
).build(); | |||
new SeleneseTest(selenese).runOn(orchestrator); | |||
} | |||
private void executeBuild(String projectKey, String projectName) { | |||
@Test | |||
public void display_scanner_context() { | |||
Navigation nav = Navigation.get(orchestrator); | |||
nav.openHomepage().logIn().submitCredentials("admin", "admin"); | |||
BackgroundTasksPage page = nav.openBackgroundTasksPage(); | |||
BackgroundTaskItem task = page.getTasksAsItems().get(0); | |||
task.openActions() | |||
.openScannerContext() | |||
.assertScannerContextContains("SonarQube plugins:") | |||
.assertScannerContextContains("Global properties:"); | |||
} | |||
private static void executeBuild(String projectKey, String projectName) { | |||
orchestrator.executeBuild( | |||
SonarScanner.create(projectDir("shared/xoo-sample")) | |||
.setProjectKey(projectKey) |
@@ -0,0 +1,56 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package pageobjects; | |||
import com.codeborne.selenide.SelenideElement; | |||
import static com.codeborne.selenide.Condition.hasText; | |||
import static com.codeborne.selenide.Condition.visible; | |||
import static com.codeborne.selenide.Selenide.$; | |||
public class BackgroundTaskItem { | |||
private final SelenideElement elt; | |||
public BackgroundTaskItem(SelenideElement elt) { | |||
this.elt = elt; | |||
} | |||
public SelenideElement getComponent() { | |||
return elt.$("td:nth-child(2)"); | |||
} | |||
public BackgroundTaskItem openActions() { | |||
elt.$(".js-task-action > .dropdown-toggle").click(); | |||
elt.$(".js-task-action > .dropdown-menu").shouldBe(visible); | |||
return this; | |||
} | |||
public BackgroundTaskItem openScannerContext () { | |||
elt.$(".js-task-show-scanner-context").click(); | |||
$(".js-task-scanner-context").shouldBe(visible); | |||
return this; | |||
} | |||
public BackgroundTaskItem assertScannerContextContains(String text) { | |||
$(".js-task-scanner-context").should(hasText(text)); | |||
return this; | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package pageobjects; | |||
import com.codeborne.selenide.ElementsCollection; | |||
import java.util.List; | |||
import java.util.stream.Collectors; | |||
import org.openqa.selenium.By; | |||
import static com.codeborne.selenide.Condition.exist; | |||
import static com.codeborne.selenide.Selenide.$; | |||
import static com.codeborne.selenide.Selenide.$$; | |||
public class BackgroundTasksPage { | |||
public BackgroundTasksPage() { | |||
$(By.cssSelector(".background-tasks")).should(exist); | |||
} | |||
public ElementsCollection getTasks() { | |||
return $$(".background-tasks > tbody > tr"); | |||
} | |||
public List<BackgroundTaskItem> getTasksAsItems() { | |||
return getTasks() | |||
.stream() | |||
.map(BackgroundTaskItem::new) | |||
.collect(Collectors.toList()); | |||
} | |||
} |
@@ -80,6 +80,10 @@ public class Navigation extends ExternalResource { | |||
return open(url, ProjectKeyPage.class); | |||
} | |||
public BackgroundTasksPage openBackgroundTasksPage() { | |||
return open("/background_tasks", BackgroundTasksPage.class); | |||
} | |||
public void open(String relativeUrl) { | |||
Selenide.open(relativeUrl); | |||
} |
@@ -39,9 +39,9 @@ export function getStatus (componentId) { | |||
return getJSON(url, data); | |||
} | |||
export function getTask (id) { | |||
export function getTask (id, additionalFields) { | |||
const url = '/api/ce/task'; | |||
return getJSON(url, { id }).then(r => r.task); | |||
return getJSON(url, { id, additionalFields }).then(r => r.task); | |||
} | |||
export function cancelTask (id) { |
@@ -19,15 +19,13 @@ | |||
*/ | |||
import React from 'react'; | |||
import shallowCompare from 'react-addons-shallow-compare'; | |||
import TaskStatus from './TaskStatus'; | |||
import TaskComponent from './TaskComponent'; | |||
import TaskId from './TaskId'; | |||
import TaskDay from './TaskDay'; | |||
import TaskDate from './TaskDate'; | |||
import TaskExecutionTime from './TaskExecutionTime'; | |||
import TaskCancelButton from './TaskCancelButton'; | |||
import { STATUSES } from './../constants'; | |||
import TaskActions from './TaskActions'; | |||
export default class Task extends React.Component { | |||
static propTypes = { | |||
@@ -44,13 +42,8 @@ export default class Task extends React.Component { | |||
return shallowCompare(this, nextProps, nextState); | |||
} | |||
handleFilterTask (task, e) { | |||
e.preventDefault(); | |||
this.props.onFilterTask(task); | |||
} | |||
render () { | |||
const { task, index, tasks, component, types, onCancelTask } = this.props; | |||
const { task, index, tasks, component, types, onCancelTask, onFilterTask } = this.props; | |||
const prevTask = index > 0 ? tasks[index - 1] : null; | |||
@@ -64,20 +57,11 @@ export default class Task extends React.Component { | |||
<TaskDate date={task.startedAt} baseDate={task.submittedAt} format="LTS"/> | |||
<TaskDate date={task.executedAt} baseDate={task.submittedAt} format="LTS"/> | |||
<TaskExecutionTime task={task}/> | |||
<td className="thin nowrap"> | |||
{!component && ( | |||
<a | |||
onClick={this.handleFilterTask.bind(this, task)} | |||
className="icon-filter icon-half-transparent spacer-left" | |||
href="#" | |||
title={`Show only "${task.componentName}" tasks`} | |||
data-toggle="tooltip"/> | |||
)} | |||
{task.status === STATUSES.PENDING && ( | |||
<TaskCancelButton task={task} onCancelTask={onCancelTask}/> | |||
)} | |||
</td> | |||
<TaskActions | |||
component={component} | |||
task={task} | |||
onFilterTask={onFilterTask} | |||
onCancelTask={onCancelTask}/> | |||
</tr> | |||
); | |||
} |
@@ -0,0 +1,86 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import shallowCompare from 'react-addons-shallow-compare'; | |||
import ScannerContextView from '../views/ScannerContextView'; | |||
import { STATUSES } from './../constants'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class TaskActions extends React.Component { | |||
shouldComponentUpdate (nextProps, nextState) { | |||
return shallowCompare(this, nextProps, nextState); | |||
} | |||
handleFilterClick (e) { | |||
e.preventDefault(); | |||
this.props.onFilterTask(this.props.task); | |||
} | |||
handleCancelClick (e) { | |||
e.preventDefault(); | |||
this.props.onCancelTask(this.props.task); | |||
} | |||
handleShowScannerContextClick (e) { | |||
e.preventDefault(); | |||
new ScannerContextView({ task: this.props.task }).render(); | |||
} | |||
render () { | |||
const { component, task } = this.props; | |||
return ( | |||
<td className="thin nowrap"> | |||
<div className="dropdown js-task-action"> | |||
<button className="dropdown-toggle" data-toggle="dropdown"> | |||
<i className="icon-dropdown"/> | |||
</button> | |||
<ul className="dropdown-menu dropdown-menu-right"> | |||
{!component && ( | |||
<li> | |||
<a className="js-task-filter" href="#" onClick={this.handleFilterClick.bind(this)}> | |||
<i className="spacer-right icon-filter icon-half-transparent"/> | |||
Show only {task.componentName} tasks | |||
</a> | |||
</li> | |||
)} | |||
{task.status === STATUSES.PENDING && ( | |||
<li> | |||
<a className="js-task-cancel" href="#" onClick={this.handleCancelClick.bind(this)}> | |||
<i className="spacer-right icon-delete"/> | |||
{translate('background_tasks.cancel_task')} | |||
</a> | |||
</li> | |||
)} | |||
{task.hasScannerContext && ( | |||
<li> | |||
<a className="js-task-show-scanner-context" | |||
href="#" | |||
onClick={this.handleShowScannerContextClick.bind(this)}> | |||
{translate('background_tasks.show_scanner_context')} | |||
</a> | |||
</li> | |||
)} | |||
</ul> | |||
</div> | |||
</td> | |||
); | |||
} | |||
} |
@@ -0,0 +1,17 @@ | |||
<form id="deactivate-user-form" autocomplete="off"> | |||
<div class="modal-head"> | |||
<h2>{{t 'background_tasks.scanner_context'}}: {{task.componentName}} [{{t 'background_task.type' task.type}}]</h2> | |||
</div> | |||
<div class="modal-body modal-container"> | |||
<div class="js-modal-messages"></div> | |||
{{#if scannerContext}} | |||
<pre class="js-task-scanner-context">{{scannerContext}}</pre> | |||
{{else}} | |||
<i class="spinner"></i> | |||
{{/if}} | |||
</div> | |||
<div class="modal-foot"> | |||
<a href="#" class="js-modal-close">{{t 'close'}}</a> | |||
</div> | |||
</form> |
@@ -17,23 +17,31 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import Modal from '../../../components/common/modals'; | |||
import Template from './ScannerContextView.hbs'; | |||
import { getTask } from '../../../api/ce'; | |||
const TaskCancelButton = ({ task, onCancelTask }) => { | |||
function handleClick (e) { | |||
e.preventDefault(); | |||
onCancelTask(task); | |||
} | |||
export default Modal.extend({ | |||
template: Template, | |||
className: 'modal modal-large', | |||
initialize () { | |||
this.scannerContext = null; | |||
this.loadScannerContext(); | |||
}, | |||
return ( | |||
<a | |||
onClick={handleClick} | |||
className="spacer-left icon-delete" | |||
title={translate('background_tasks.cancel_task')} | |||
data-toggle="tooltip" | |||
href="#"/> | |||
); | |||
}; | |||
loadScannerContext() { | |||
getTask(this.options.task.id, ['scannerContext']).then(task => { | |||
this.scannerContext = task.scannerContext; | |||
this.render(); | |||
}); | |||
}, | |||
serializeData() { | |||
return { | |||
task: this.options.task, | |||
scannerContext: this.scannerContext | |||
}; | |||
} | |||
}); | |||
export default TaskCancelButton; |
@@ -2974,6 +2974,8 @@ background_tasks.table.duration=Duration | |||
background_tasks.logs=Logs | |||
background_tasks.cancel_task=Cancel Task | |||
background_tasks.cancel_all_tasks=Cancel All Pending Tasks | |||
background_tasks.scanner_context=Scanner Context | |||
background_tasks.show_scanner_context=Show Scanner Context | |||
background_tasks.pending=pending | |||
background_tasks.failures=still failing | |||
background_tasks.in_progress_duration=Duration of the current task in progress. |