/*
* Copyright 2016 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.thoughtworks.go.server.service;
import java.net.URISyntaxException;
import java.util.List;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.domain.MaterialRevision;
import com.thoughtworks.go.domain.MaterialRevisions;
import com.thoughtworks.go.domain.ModificationVisitor;
import com.thoughtworks.go.domain.Pipeline;
import com.thoughtworks.go.domain.Stage;
import com.thoughtworks.go.domain.StageEvent;
import com.thoughtworks.go.domain.StageIdentifier;
import com.thoughtworks.go.domain.User;
import com.thoughtworks.go.domain.Users;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.Modification;
import com.thoughtworks.go.domain.materials.ModifiedFile;
import com.thoughtworks.go.domain.materials.Revision;
import com.thoughtworks.go.domain.testinfo.TestInformation;
import com.thoughtworks.go.domain.testinfo.TestStatus;
import com.thoughtworks.go.domain.testinfo.TestSuite;
import com.thoughtworks.go.server.dao.sparql.ShineDao;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.messaging.EmailNotificationTopic;
import com.thoughtworks.go.server.messaging.SendEmailMessage;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.SystemUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
@Service
public class StageNotificationService {
private static final Log LOGGER = LogFactory.getLog(StageNotificationService.class);
private final PipelineService pipelineService;
private final UserService userService;
private EmailNotificationTopic emailNotificationTopic;
private final SystemEnvironment systemEnvironment;
private StageService stageService;
private ServerConfigService serverConfigService;
private final ShineDao shineDao;
protected static final String MATERIAL_SECTION_HEADER = "-- CHECK-INS --";
public static final String FAILED_TEST_SECTION = "-- FAILED TESTS --";
@Autowired
public StageNotificationService(PipelineService pipelineService, UserService userService, EmailNotificationTopic emailNotificationTopic,
SystemEnvironment systemEnvironment, StageService stageService, ServerConfigService serverConfigService,
ShineDao shineDao) {
this.pipelineService = pipelineService;
this.userService = userService;
this.emailNotificationTopic = emailNotificationTopic;
this.systemEnvironment = systemEnvironment;
this.stageService = stageService;
this.serverConfigService = serverConfigService;
this.shineDao = shineDao;
}
public void sendNotifications(StageIdentifier stageIdentifier, StageEvent event, Username cancelledBy) {
Users users = userService.findValidSubscribers(stageIdentifier.stageConfigIdentifier());
if (users.isEmpty()) {
return;
}
Stage stage = stageService.findStageWithIdentifier(stageIdentifier);
Pipeline pipeline = pipelineService.fullPipelineById(stage.getPipelineId());
MaterialRevisions materialRevisions = pipeline.getMaterialRevisions();
List<TestSuite> failedTestSuites = null;
if (systemEnvironment.isShineEnabled()) {
failedTestSuites = shineDao.failedTestsFor(stageIdentifier);
}
String emailBody = new EmailBodyGenerator(materialRevisions, cancelledBy, systemEnvironment, stageIdentifier, failedTestSuites).getContent();
String subject = "Stage [" + stageIdentifier.stageLocator() + "]" + event.describe();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Processing notification titled [%s]", subject));
}
for (User user : users) {
if (user.matchNotification(stageIdentifier.stageConfigIdentifier(), event, materialRevisions)) {
StringBuilder emailWithSignature = new StringBuilder(emailBody)
.append("\n\n")
.append("Sent by Go on behalf of ")
.append(user.getName());
SendEmailMessage sendEmailMessage
= new SendEmailMessage(subject, emailWithSignature.toString(), user.getEmail());
emailNotificationTopic.post(sendEmailMessage);
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Finished processing notification titled [%s]", subject));
}
}
//only for test
void setEmailNotificationTopic(EmailNotificationTopic emailNotificationTopic) {
this.emailNotificationTopic = emailNotificationTopic;
}
private class EmailBodyGenerator implements ModificationVisitor {
private final StringBuilder emailBody;
private Material material;
private final SystemEnvironment systemEnvironment;
private final StageIdentifier stageIdentifier;
private List<TestSuite> failedTestSuites;
protected static final String SECTION_SEPERATOR = "\n\n";
private static final String SUITE_NAME_PREFIX = "* ";
public EmailBodyGenerator(MaterialRevisions materialRevisions, Username cancelledBy, SystemEnvironment systemEnvironment, StageIdentifier stageIdentifier, List<TestSuite> failedTestSuites) {
this.systemEnvironment = systemEnvironment;
this.stageIdentifier = stageIdentifier;
this.failedTestSuites = failedTestSuites;
emailBody = new StringBuilder();
if (!Username.BLANK.equals(cancelledBy)) {
emailBody.append("The stage was cancelled by ").append(CaseInsensitiveString.str(cancelledBy.getUsername())).append(".\n");
}
addStageLink();
addFailedTests();
addMaterialRevisions(materialRevisions);
}
private void addFailedTests() {
if (failedTestSuites == null || failedTestSuites.size() == 0) {
return;
}
sectionSeperator();
emailBody.append(FAILED_TEST_SECTION);
sectionSeperator();
emailBody.append(String.format("The following tests failed in pipeline '%s' (instance '%s'):", stageIdentifier.getPipelineName(), stageIdentifier.getPipelineLabel()));
for (TestSuite failedTestSuite : failedTestSuites) {
sectionSeperator();
emailBody.append(SUITE_NAME_PREFIX + failedTestSuite.fullName() + "\n");
for (TestInformation testInformation : failedTestSuite.tests()) {
emailBody.append(" " + testInformation.getName() + "\n");
for (String jobName : testInformation.getJobNames()) {
emailBody.append(" " + testStatusString(testInformation) + " on '" + jobName + "' (" + jobDetailLink(jobName) + ")\n");
}
}
}
}
private String testStatusString(TestInformation testInformation) {
return testInformation.getStatus().equals(TestStatus.Error) ? "Errored" : "Failed";
}
private void addMaterialRevisions(MaterialRevisions materialRevisions) {
sectionSeperator();
emailBody.append(MATERIAL_SECTION_HEADER);
materialRevisions.accept(this);
}
private void addStageLink() {
emailBody.append(String.format("See details: %s", stageDetailLink()));
}
private String stageDetailLink() {
String ipAddress = SystemUtil.getFirstLocalNonLoopbackIpAddress();
int port = systemEnvironment.getServerPort();
String urlString = String.format("http://%s:%s/go/pipelines/%s", ipAddress, port, stageIdentifier.stageLocator());
return useConfiguredSiteUrl(urlString);
}
private String useConfiguredSiteUrl(String urlString) {
try {
return StageNotificationService.this.serverConfigService.siteUrlFor(urlString, false);
} catch (URISyntaxException e) {
throw bomb("Could not construct URL.", e);
}
}
private String jobDetailLink(String jobName) {
String ipAddress = SystemUtil.getFirstLocalNonLoopbackIpAddress();
int port = systemEnvironment.getServerPort();
String urlString = String.format("http://%s:%s/go/tab/build/detail/%s/%s", ipAddress, port, stageIdentifier.stageLocator(), jobName);
return useConfiguredSiteUrl(urlString);
}
public void visit(MaterialRevision materialRevision) {
}
public void visit(Material material, Revision revision) {
this.material = material;
}
public void visit(Modification modification) {
sectionSeperator();
material.emailContent(emailBody, modification);
}
private void sectionSeperator() {
emailBody.append(SECTION_SEPERATOR);
}
public void visit(ModifiedFile file) {
emailBody.append('\n').append(file.getAction()).append(' ').append(file.getFileName());
}
public String getContent() {
return emailBody.toString();
}
}
}