from projects p
where
p.enabled=${_true}
+ and p.main_branch_project_uuid is null
and p.kee in
<foreach collection="keys" open="(" close=")" item="key" separator=",">
#{key,jdbcType=VARCHAR}
import org.sonar.api.notifications.Notification;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor;
import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
private final MeasureRepository measureRepository;
private final EventRepository eventRepository;
private final NotificationService notificationService;
+ private final AnalysisMetadataHolder analysisMetadataHolder;
public QualityGateEventsStep(TreeRootHolder treeRootHolder,
MetricRepository metricRepository, MeasureRepository measureRepository, EventRepository eventRepository,
- NotificationService notificationService) {
+ NotificationService notificationService, AnalysisMetadataHolder analysisMetadataHolder) {
this.treeRootHolder = treeRootHolder;
this.metricRepository = metricRepository;
this.measureRepository = measureRepository;
this.eventRepository = eventRepository;
this.notificationService = notificationService;
+ this.analysisMetadataHolder = analysisMetadataHolder;
}
@Override
.setFieldValue("alertText", rawStatus.getText())
.setFieldValue("alertLevel", rawStatus.getStatus().toString())
.setFieldValue("isNewAlert", Boolean.toString(isNewAlert));
+ analysisMetadataHolder.getBranch().ifPresent(branch -> notification.setFieldValue("branch", branch.getName().orElse(null)));
notificationService.deliver(notification);
}
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.util.CloseableIterator;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
import org.sonar.server.computation.task.projectanalysis.issue.IssueCache;
IssueChangeNotification changeNotification = new IssueChangeNotification();
changeNotification.setRuleName(rules.getByKey(issue.ruleKey()).getName());
changeNotification.setIssue(issue);
- changeNotification.setProject(project.getKey(), project.getName());
+ String branchName = analysisMetadataHolder.getBranch().flatMap(Branch::getName).orElse(null);
+ changeNotification.setProject(project.getKey(), project.getName(), branchName);
service.deliver(changeNotification);
}
.setIssue(issue)
.setChangeAuthorLogin(context.login())
.setRuleName(rule.map(RuleDefinitionDto::getName).orElse(null))
- .setProject(project.getDbKey(), project.name())
+ .setProject(project.getKey(), project.name(), project.getBranch())
.setComponent(component)
.setComment(comment));
return issueDto;
static final String FIELD_PROJECT_DATE = "projectDate";
static final String FIELD_PROJECT_UUID = "projectUuid";
static final String FIELD_ASSIGNEE = "assignee";
+ static final String FIELD_BRANCH = "branch";
protected final EmailSettings settings;
protected final I18n i18n;
String dateString = notification.getFieldValue(FIELD_PROJECT_DATE);
if (projectKey != null && dateString != null) {
Date date = DateUtils.parseDateTime(dateString);
- String url = String.format("%s/project/issues?id=%s&createdAt=%s",
- settings.getServerBaseURL(), encode(projectKey), encode(DateUtils.formatDateTime(date)));
+ String url = String.format("%s/project/issues?id=%s",
+ settings.getServerBaseURL(), encode(projectKey));
+ String branchName = notification.getFieldValue("branch");
+ if (branchName != null) {
+ url += "&branch=" + encode(branchName);
+ }
+ url += "&createdAt=" + encode(DateUtils.formatDateTime(date));
message
.append("See it in SonarQube: ")
.append(url)
}
public IssueChangeNotification setProject(ComponentDto project) {
- setFieldValue("projectName", project.longName());
- setFieldValue("projectKey", project.getDbKey());
- return this;
+ return setProject(project.getKey(), project.longName(), project.getBranch());
}
- public IssueChangeNotification setProject(String projectKey, String projectName) {
+ public IssueChangeNotification setProject(String projectKey, String projectName, @Nullable String branch) {
setFieldValue("projectName", projectName);
setFieldValue("projectKey", projectKey);
+ if (branch != null) {
+ setFieldValue("branch", branch);
+ }
return this;
}
sb.append("See it in SonarQube: ").append(settings.getServerBaseURL())
.append("/project/issues?id=").append(encode(notification.getFieldValue("projectKey"), "UTF-8"))
.append("&issues=").append(issueKey)
- .append("&open=").append(issueKey)
- .append(NEW_LINE);
+ .append("&open=").append(issueKey);
+ String branchName = notification.getFieldValue("branch");
+ if (branchName != null) {
+ sb.append("&branch=").append(branchName);
+ }
+ sb.append(NEW_LINE);
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Encoding not supported", e);
}
String projectUuid = notification.getFieldValue(FIELD_PROJECT_UUID);
String dateString = notification.getFieldValue(FIELD_PROJECT_DATE);
String assignee = notification.getFieldValue(FIELD_ASSIGNEE);
+ String branch = notification.getFieldValue(FIELD_BRANCH);
if (projectUuid != null && dateString != null && assignee != null) {
Date date = DateUtils.parseDateTime(dateString);
- String url = String.format("%s/issues?projectUuids=%s&assignees=%s&createdAt=%s",
+ String url = String.format("%s/issues?projectUuids=%s&assignees=%s",
settings.getServerBaseURL(),
encode(projectUuid),
- encode(assignee),
- encode(DateUtils.formatDateTime(date)));
+ encode(assignee));
+ if (branch != null) {
+ url += "&branch=" + encode(branch);
+ }
+ url += "&createdAt=" + encode(DateUtils.formatDateTime(date));
message.append("See it in SonarQube: ").append(url).append(NEW_LINE);
}
}
protected List<ComponentDto> doKeepAuthorizedComponents(String permission, Collection<ComponentDto> components) {
boolean allowPublicComponent = ProjectPermissions.PUBLIC_PERMISSIONS.contains(permission);
return components.stream()
- .filter(c -> (allowPublicComponent && !c.isPrivate()) || hasProjectUuidPermission(permission, c.projectUuid()))
+ .filter(c -> (allowPublicComponent && !c.isPrivate()) || hasComponentPermission(permission, c))
.collect(MoreCollectors.toList());
}
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.sonar.api.notifications.Notification;
+import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
import org.sonar.server.computation.task.projectanalysis.component.Component;
+import org.sonar.server.computation.task.projectanalysis.component.DefaultBranchImpl;
import org.sonar.server.computation.task.projectanalysis.component.ReportComponent;
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderRule;
import org.sonar.server.computation.task.projectanalysis.event.Event;
@Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+ @Rule
+ public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
private ArgumentCaptor<Event> eventArgumentCaptor = ArgumentCaptor.forClass(Event.class);
private ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class);
private MeasureRepository measureRepository = mock(MeasureRepository.class);
private EventRepository eventRepository = mock(EventRepository.class);
private NotificationService notificationService = mock(NotificationService.class);
- private QualityGateEventsStep underTest = new QualityGateEventsStep(treeRootHolder, metricRepository, measureRepository, eventRepository, notificationService);
+ private QualityGateEventsStep underTest = new QualityGateEventsStep(treeRootHolder, metricRepository, measureRepository, eventRepository, notificationService, analysisMetadataHolder);
@Before
public void setUp() {
when(metricRepository.getByKey(ALERT_STATUS_KEY)).thenReturn(alertStatusMetric);
+ analysisMetadataHolder.setBranch(null);
treeRootHolder.setRoot(PROJECT_COMPONENT);
}
assertThat(notification.getFieldValue("projectKey")).isEqualTo(PROJECT_COMPONENT.getKey());
assertThat(notification.getFieldValue("projectUuid")).isEqualTo(PROJECT_COMPONENT.getUuid());
assertThat(notification.getFieldValue("projectName")).isEqualTo(PROJECT_COMPONENT.getName());
+ assertThat(notification.getFieldValue("branch")).isNull();
assertThat(notification.getFieldValue("alertLevel")).isEqualTo(rawAlterStatus.name());
assertThat(notification.getFieldValue("alertName")).isEqualTo(expectedLabel);
}
assertThat(notification.getFieldValue("projectKey")).isEqualTo(PROJECT_COMPONENT.getKey());
assertThat(notification.getFieldValue("projectUuid")).isEqualTo(PROJECT_COMPONENT.getUuid());
assertThat(notification.getFieldValue("projectName")).isEqualTo(PROJECT_COMPONENT.getName());
+ assertThat(notification.getFieldValue("branch")).isNull();
assertThat(notification.getFieldValue("alertLevel")).isEqualTo(newQualityGateStatus.getStatus().name());
assertThat(notification.getFieldValue("alertName")).isEqualTo(expectedLabel);
reset(measureRepository, eventRepository, notificationService);
}
+ @Test
+ public void verify_branch_name_is_set_in_notification() {
+ String branchName = "feature1";
+ analysisMetadataHolder.setBranch(new DefaultBranchImpl(branchName));
+
+ when(measureRepository.getRawMeasure(PROJECT_COMPONENT, alertStatusMetric)).thenReturn(of(Measure.newMeasureBuilder().setQualityGateStatus(WARN_QUALITY_GATE_STATUS).createNoValue()));
+ when(measureRepository.getBaseMeasure(PROJECT_COMPONENT, alertStatusMetric)).thenReturn(
+ of(Measure.newMeasureBuilder().setQualityGateStatus(new QualityGateStatus(ERROR)).createNoValue()));
+
+ underTest.execute();
+
+ verify(notificationService).deliver(notificationArgumentCaptor.capture());
+ Notification notification = notificationArgumentCaptor.getValue();
+ assertThat(notification.getType()).isEqualTo("alerts");
+ assertThat(notification.getFieldValue("projectKey")).isEqualTo(PROJECT_COMPONENT.getKey());
+ assertThat(notification.getFieldValue("projectUuid")).isEqualTo(PROJECT_COMPONENT.getUuid());
+ assertThat(notification.getFieldValue("projectName")).isEqualTo(PROJECT_COMPONENT.getName());
+ assertThat(notification.getFieldValue("branch")).isEqualTo(branchName);
+
+ reset(measureRepository, eventRepository, notificationService);
+ }
+
}
assertThat(issueChangeNotification.getFieldValue("comment")).isEqualTo("increase severity");
}
+ @Test
+ public void verify_notification_on_branch() throws Exception {
+ RuleDto rule = ruleDbTester.insertRule(newRuleDto());
+ ComponentDto project = componentDbTester.insertMainBranch();
+ ComponentDto branch = componentDbTester.insertProjectBranch(project);
+ ComponentDto file = componentDbTester.insertComponent(newFileDto(branch));
+ DefaultIssue issue = issueDbTester.insertIssue(IssueTesting.newIssue(rule.getDefinition(), branch, file)).setSeverity(MAJOR).toDefaultIssue();
+ IssueChangeContext context = IssueChangeContext.createUser(new Date(), "john");
+ issueFieldsSetter.setSeverity(issue, BLOCKER, context);
+
+ underTest.saveIssue(dbTester.getSession(), issue, context, "increase severity");
+
+ verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
+ IssueChangeNotification issueChangeNotification = notificationArgumentCaptor.getValue();
+ assertThat(issueChangeNotification.getFieldValue("key")).isEqualTo(issue.key());
+ assertThat(issueChangeNotification.getFieldValue("projectKey")).isEqualTo(project.getDbKey());
+ assertThat(issueChangeNotification.getFieldValue("projectName")).isEqualTo(project.name());
+ assertThat(issueChangeNotification.getFieldValue("branch")).isEqualTo(branch.getBranch());
+ }
+
@Test
public void verify_notification_when_issue_is_linked_on_removed_rule() throws Exception {
RuleDto rule = ruleDbTester.insertRule(newRuleDto().setStatus(RuleStatus.REMOVED));
}
@Test
- public void set_project() {
- IssueChangeNotification result = notification.setProject("MyService", "My Service");
+ public void set_project_without_branch() {
+ IssueChangeNotification result = notification.setProject("MyService", "My Service", null);
assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService");
assertThat(result.getFieldValue("projectName")).isEqualTo("My Service");
+ assertThat(result.getFieldValue("branch")).isNull();
+ }
+
+ @Test
+ public void set_project_with_branch() {
+ IssueChangeNotification result = notification.setProject("MyService", "My Service", "feature1");
+ assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService");
+ assertThat(result.getFieldValue("projectName")).isEqualTo("My Service");
+ assertThat(result.getFieldValue("branch")).isEqualTo("feature1");
}
@Test
assertThat(email.getFrom()).isNull();
}
+ @Test
+ public void test_email_with_issue_on_branch() throws Exception {
+ Notification notification = generateNotification()
+ .setFieldValue("branch", "feature1");
+
+ EmailMessage email = underTest.format(notification);
+ assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
+ assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
+
+ String message = email.getMessage();
+ String expected = Resources.toString(Resources.getResource(
+ "org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_issue_on_branch.txt"),
+ StandardCharsets.UTF_8);
+ expected = StringUtils.remove(expected, '\r');
+ assertThat(message).isEqualTo(expected);
+ }
+
@Test
public void notification_sender_should_be_the_author_of_change() {
db.users().insertUser(newUserDto().setLogin("simon").setName("Simon"));
Notification notification = new IssueChangeNotification()
.setChangeAuthorLogin("simon")
- .setProject("Struts", "org.apache:struts");
+ .setProject("Struts", "org.apache:struts", null);
EmailMessage message = underTest.format(notification);
assertThat(message.getFrom()).isEqualTo("Simon");
Notification notification = new IssueChangeNotification()
.setChangeAuthorLogin("simon")
- .setProject("Struts", "org.apache:struts");
+ .setProject("Struts", "org.apache:struts", null);
EmailMessage message = underTest.format(notification);
assertThat(message.getFrom()).isEqualTo("simon");
EmailMessage message = underTest.format(notification);
// TODO datetime to be completed when test is isolated from JVM timezone
- assertStartsWithFile(message.getMessage(), getClass().getResource("MyNewIssuesEmailTemplateTest/email_with_all_details.txt"));
+ assertStartsWithFile(message.getMessage(), "MyNewIssuesEmailTemplateTest/email_with_all_details.txt");
}
@Test
EmailMessage message = underTest.format(notification);
// TODO datetime to be completed when test is isolated from JVM timezone
- assertStartsWithFile(message.getMessage(), getClass().getResource("MyNewIssuesEmailTemplateTest/email_with_no_assignee_tags_components.txt"));
+ assertStartsWithFile(message.getMessage(), "MyNewIssuesEmailTemplateTest/email_with_no_assignee_tags_components.txt");
+ }
+
+ @Test
+ public void format_email_with_issue_on_branch() throws Exception {
+ Notification notification = newNotification()
+ .setFieldValue("branch", "feature1");
+
+ EmailMessage message = underTest.format(notification);
+
+ // TODO datetime to be completed when test is isolated from JVM timezone
+ assertStartsWithFile(message.getMessage(), "MyNewIssuesEmailTemplateTest/email_with_issue_on_branch.txt");
}
@Test
.setFieldValue(RULE + ".2.count", "5");
}
- private static void assertStartsWithFile(String message, URL file) throws IOException {
- String fileContent = IOUtils.toString(file, StandardCharsets.UTF_8);
+ private void assertStartsWithFile(String message, String resourcePath) throws IOException {
+ String fileContent = IOUtils.toString(getClass().getResource(resourcePath), StandardCharsets.UTF_8);
assertThat(sanitizeString(message)).startsWith(sanitizeString(fileContent));
}
*/
package org.sonar.server.issue.notification;
+import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
EmailMessage message = template.format(notification);
// TODO datetime to be completed when test is isolated from JVM timezone
- String expectedContent = IOUtils.toString(getClass().getResource("NewIssuesEmailTemplateTest/email_with_all_details.txt"), StandardCharsets.UTF_8);
- assertThat(message.getMessage()).startsWith(StringUtils.remove(expectedContent, '\r'));
+ assertStartsWithFile(message.getMessage(), "NewIssuesEmailTemplateTest/email_with_all_details.txt");
}
@Test
EmailMessage message = template.format(notification);
// TODO datetime to be completed when test is isolated from JVM timezone
- String expectedContent = IOUtils.toString(getClass().getResource("NewIssuesEmailTemplateTest/email_with_partial_details.txt"), StandardCharsets.UTF_8);
- assertThat(message.getMessage()).startsWith(StringUtils.remove(expectedContent, '\r'));
+ assertStartsWithFile(message.getMessage(), "NewIssuesEmailTemplateTest/email_with_partial_details.txt");
+ }
+
+ @Test
+ public void format_email_with_issue_on_branch() throws Exception {
+ Notification notification = newNotification()
+ .setFieldValue("branch", "feature1");
+
+ EmailMessage message = template.format(notification);
+
+ // TODO datetime to be completed when test is isolated from JVM timezone
+ assertStartsWithFile(message.getMessage(), "NewIssuesEmailTemplateTest/email_with_issue_on_branch.txt");
}
@Test
.setFieldValue(RULE + ".2.label", "Rule the World (Java)")
.setFieldValue(RULE + ".2.count", "5");
}
+
+ private void assertStartsWithFile(String message, String resourcePath) throws IOException {
+ String fileContent = IOUtils.toString(getClass().getResource(resourcePath), StandardCharsets.UTF_8);
+ assertThat(sanitizeString(message)).startsWith(sanitizeString(fileContent));
+ }
+
+ /**
+ * sanitize EOL and tabs if git clone is badly configured
+ */
+ private static String sanitizeString(String s) {
+ return s.replaceAll("\\r\\n|\\r|\\s+", "");
+ }
}
issueWorkflow.start();
rule = db.rules().insertRule(newRuleDto());
organization = db.organizations().insert();
- project = db.components().insertPrivateProject(organization);
+ project = db.components().insertMainBranch(organization);
file = db.components().insertComponent(newFileDto(project));
user = db.users().insertUser("john");
when(system2.now()).thenReturn(NOW);
public void send_notification() throws Exception {
setUserProjectPermissions(USER);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
- ArgumentCaptor<IssueChangeNotification> issueChangeNotificationCaptor = ArgumentCaptor.forClass(IssueChangeNotification.class);
BulkChangeWsResponse response = call(BulkChangeRequest.builder()
.setIssues(singletonList(issueDto.getKey()))
.build());
checkResponse(response, 1, 1, 0, 0);
+
+ ArgumentCaptor<IssueChangeNotification> issueChangeNotificationCaptor = ArgumentCaptor.forClass(IssueChangeNotification.class);
verify(notificationManager).scheduleForSending(issueChangeNotificationCaptor.capture());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("key")).isEqualTo(issueDto.getKey());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("componentName")).isEqualTo(file.longName());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectKey")).isEqualTo(project.getDbKey());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("ruleName")).isEqualTo(rule.getName());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("changeAuthor")).isEqualTo(user.getLogin());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("branch")).isNull();
+ }
+
+ @Test
+ public void send_notification_on_branch() throws Exception {
+ setUserProjectPermissions(USER);
+
+ String branchName = "feature1";
+ ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(branchName));
+ ComponentDto fileOnBranch = db.components().insertComponent(newFileDto(branch));
+ IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue(rule, fileOnBranch, branch).setType(BUG));
+
+ BulkChangeWsResponse response = call(BulkChangeRequest.builder()
+ .setIssues(singletonList(issueDto.getKey()))
+ .setDoTransition("confirm")
+ .setSendNotifications(true)
+ .build());
+
+ checkResponse(response, 1, 1, 0, 0);
+
+ ArgumentCaptor<IssueChangeNotification> issueChangeNotificationCaptor = ArgumentCaptor.forClass(IssueChangeNotification.class);
+ verify(notificationManager).scheduleForSending(issueChangeNotificationCaptor.capture());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("key")).isEqualTo(issueDto.getKey());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("componentName")).isEqualTo(fileOnBranch.longName());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectName")).isEqualTo(project.longName());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectKey")).isEqualTo(project.getDbKey());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("ruleName")).isEqualTo(rule.getName());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("changeAuthor")).isEqualTo(user.getLogin());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("branch")).isEqualTo(branchName);
}
@Test
--- /dev/null
+Action
+Rule: Avoid Cycles
+Message: Has 3 cycles
+
+
+See it in SonarQube: http://nemo.sonarsource.org/project/issues?id=org.apache%3Astruts&issues=ABCDE&open=ABCDE&branch=feature1
--- /dev/null
+Project: Struts
+
+32 new issues (new debt: 1d3h)
+
+ Severity
+ Blocker: 0 Critical: 5 Major: 10 Minor: 3 Info: 1
+
+See it in SonarQube: http://nemo.sonarsource.org/issues?projectUuids=ABCDE&assignees=lo.gin&branch=feature1&createdAt=2010-05-1
--- /dev/null
+Project: Struts
+
+32 new issues (new debt: 1d3h)
+
+ Severity
+ Blocker: 0 Critical: 5 Major: 10 Minor: 3 Info: 1
+
+See it in SonarQube: http://nemo.sonarsource.org/project/issues?id=org.apache%3Astruts&branch=feature1&createdAt=2010-05-1