import org.sonar.api.measures.MeasuresFilters;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationManager;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
public class NewViolationsDecorator implements Decorator {
private TimeMachineConfiguration timeMachineConfiguration;
+ private NotificationManager notificationManager;
- public NewViolationsDecorator(TimeMachineConfiguration timeMachineConfiguration) {
+ public NewViolationsDecorator(TimeMachineConfiguration timeMachineConfiguration, NotificationManager notificationManager) {
this.timeMachineConfiguration = timeMachineConfiguration;
+ this.notificationManager = notificationManager;
}
public boolean shouldExecuteOnProject(Project project) {
CoreMetrics.NEW_INFO_VIOLATIONS);
}
+ @SuppressWarnings("rawtypes")
public void decorate(Resource resource, DecoratorContext context) {
if (shouldDecorateResource(resource, context)) {
computeNewViolations(context);
computeNewViolationsPerSeverity(context);
computeNewViolationsPerRule(context);
}
+ if (ResourceUtils.isRootProject(resource)) {
+ notifyNewViolations((Project) resource, context);
+ }
}
- private boolean shouldDecorateResource(Resource resource, DecoratorContext context) {
+ private boolean shouldDecorateResource(Resource<?> resource, DecoratorContext context) {
return (StringUtils.equals(Scopes.PROJECT, resource.getScope()) || StringUtils.equals(Scopes.DIRECTORY, resource.getScope()) || StringUtils
.equals(Scopes.FILE, resource.getScope()))
&& !ResourceUtils.isUnitTestClass(resource)
return metric;
}
+ protected void notifyNewViolations(Project project, DecoratorContext context) {
+ Integer lastAnalysisPeriodIndex = timeMachineConfiguration.getLastAnalysisPeriodIndex();
+ if (lastAnalysisPeriodIndex != null) {
+ Double newViolationsCount = context.getMeasure(CoreMetrics.NEW_VIOLATIONS).getVariation(lastAnalysisPeriodIndex);
+ if (newViolationsCount != null && newViolationsCount > 0) {
+ // Maybe we should check if this is the first analysis or not?
+ Notification notification = new Notification("new-violations")
+ .setFieldValue("count", String.valueOf(newViolationsCount.intValue()))
+ .setFieldValue("projectName", project.getLongName())
+ .setFieldValue("projectKey", project.getKey())
+ .setFieldValue("projectId", String.valueOf(project.getId()))
+ .setFieldValue("period", lastAnalysisPeriodIndex.toString());
+ notificationManager.scheduleForSending(notification);
+ }
+ }
+ }
+
@Override
public String toString() {
return getClass().getSimpleName();
*/
package org.sonar.plugins.core.timemachine;
-import com.google.common.collect.Lists;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.time.DateUtils;
import org.hamcrest.BaseMatcher;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationManager;
import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Resource;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RulePriority;
import org.sonar.batch.components.PastSnapshot;
import org.sonar.batch.components.TimeMachineConfiguration;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.*;
+import com.google.common.collect.Lists;
public class NewViolationsDecoratorTest {
private Rule rule1;
private NewViolationsDecorator decorator;
private DecoratorContext context;
private Resource resource;
+ private NotificationManager notificationManager;
private Date rightNow;
private Date tenDaysAgo;
private Date fiveDaysAgo;
+ private TimeMachineConfiguration timeMachineConfiguration;
@Before
public void setUp() {
when(pastSnapshot2.getIndex()).thenReturn(2);
when(pastSnapshot2.getTargetDate()).thenReturn(tenDaysAgo);
- TimeMachineConfiguration timeMachineConfiguration = mock(TimeMachineConfiguration.class);
+ timeMachineConfiguration = mock(TimeMachineConfiguration.class);
when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(Arrays.asList(pastSnapshot, pastSnapshot2));
context = mock(DecoratorContext.class);
resource = new File("com/foo/bar");
when(context.getResource()).thenReturn(resource);
- decorator = new NewViolationsDecorator(timeMachineConfiguration);
+ notificationManager = mock(NotificationManager.class);
+ decorator = new NewViolationsDecorator(timeMachineConfiguration, notificationManager);
rule1 = Rule.create().setRepositoryKey("rule1").setKey("rule1").setName("name1");
rule2 = Rule.create().setRepositoryKey("rule2").setKey("rule2").setName("name2");
verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MINOR_VIOLATIONS, rule3, 0.0, 1.0)));
}
+ @Test
+ public void shouldNotNotifyIfNotLastestAnalysis() {
+ Project project = mock(Project.class);
+ when(project.isLatestAnalysis()).thenReturn(false);
+ assertThat(decorator.shouldExecuteOnProject(project), is(false));
+ }
+
+ @Test
+ public void shouldNotNotifyIfNotRootProject() throws Exception {
+ Project project = mock(Project.class);
+ when(project.getQualifier()).thenReturn(Qualifiers.MODULE);
+
+ decorator.decorate(project, context);
+
+ verify(notificationManager, never()).scheduleForSending(any(Notification.class));
+ }
+
+ @Test
+ public void shouldNotNotifyIfNoPeriodForLastAnalysis() throws Exception {
+ Project project = new Project("key");
+ when(timeMachineConfiguration.getLastAnalysisPeriodIndex()).thenReturn(null);
+
+ decorator.notifyNewViolations(project, context);
+
+ verify(notificationManager, never()).scheduleForSending(any(Notification.class));
+ }
+
+ @Test
+ public void shouldNotNotifyIfNoNewViolations() throws Exception {
+ Project project = new Project("key");
+ when(timeMachineConfiguration.getLastAnalysisPeriodIndex()).thenReturn(1);
+ Measure m = new Measure(CoreMetrics.NEW_VIOLATIONS);
+ when(context.getMeasure(CoreMetrics.NEW_VIOLATIONS)).thenReturn(m);
+
+ // NULL is returned here
+ decorator.notifyNewViolations(project, context);
+ verify(notificationManager, never()).scheduleForSending(any(Notification.class));
+
+ // 0 will be returned now
+ m.setVariation1(0.0);
+ decorator.notifyNewViolations(project, context);
+ verify(notificationManager, never()).scheduleForSending(any(Notification.class));
+ }
+
+ @Test
+ public void shouldNotifyUserAboutNewViolations() throws Exception {
+ Project project = new Project("key").setName("LongName");
+ project.setId(45);
+ when(timeMachineConfiguration.getLastAnalysisPeriodIndex()).thenReturn(2);
+ Measure m = new Measure(CoreMetrics.NEW_VIOLATIONS).setVariation2(32.0);
+ when(context.getMeasure(CoreMetrics.NEW_VIOLATIONS)).thenReturn(m);
+
+ decorator.decorate(project, context);
+
+ Notification notification = new Notification("new-violations")
+ .setFieldValue("count", "32")
+ .setFieldValue("projectName", "LongName")
+ .setFieldValue("projectKey", "key")
+ .setFieldValue("projectId", "45")
+ .setFieldValue("period", "2");
+ verify(notificationManager, times(1)).scheduleForSending(eq(notification));
+ }
+
private List<Violation> createViolations() {
List<Violation> violations = Lists.newLinkedList();
violations.add(Violation.create(rule1, resource).setSeverity(RulePriority.CRITICAL).setCreatedAt(rightNow));
import java.util.List;
import org.sonar.api.SonarPlugin;
+import org.sonar.plugins.emailnotifications.newviolations.NewViolationsEmailTemplate;
+import org.sonar.plugins.emailnotifications.newviolations.NewViolationsOnMyFavouriteProject;
import org.sonar.plugins.emailnotifications.reviews.ChangesInReviewAssignedToMeOrCreatedByMe;
import org.sonar.plugins.emailnotifications.reviews.ReviewEmailTemplate;
public class EmailNotificationsPlugin extends SonarPlugin {
+ @SuppressWarnings({"rawtypes", "unchecked"})
public List getExtensions() {
return Arrays.asList(
EmailConfiguration.class,
EmailNotificationChannel.class,
ReviewEmailTemplate.class,
- ChangesInReviewAssignedToMeOrCreatedByMe.class);
+ ChangesInReviewAssignedToMeOrCreatedByMe.class,
+
+ NewViolationsEmailTemplate.class,
+ NewViolationsOnMyFavouriteProject.class);
}
}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.emailnotifications.newviolations;
+
+import org.sonar.api.notifications.Notification;
+import org.sonar.plugins.emailnotifications.EmailConfiguration;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.plugins.emailnotifications.api.EmailTemplate;
+
+/**
+ * Creates email message for notification "new-violations".
+ *
+ * @since 2.10
+ */
+public class NewViolationsEmailTemplate extends EmailTemplate {
+
+ private EmailConfiguration configuration;
+
+ public NewViolationsEmailTemplate(EmailConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+ @Override
+ public EmailMessage format(Notification notification) {
+ if (!"new-violations".equals(notification.getType())) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+
+ String projectName = notification.getFieldValue("projectName");
+ appendLine(sb, "Project", projectName);
+ appendLine(sb, "New violations on last analysis", notification.getFieldValue("count"));
+ appendFooter(sb, notification);
+
+ EmailMessage message = new EmailMessage()
+ .setMessageId("new-violations/" + notification.getFieldValue("projectId"))
+ .setSubject("New violations for project " + projectName)
+ .setMessage(sb.toString());
+
+ return message;
+ }
+
+ private void appendLine(StringBuilder sb, String name, String value) {
+ sb.append(name).append(": ").append(value).append('\n');
+ }
+
+ private void appendFooter(StringBuilder sb, Notification notification) {
+ String projectKey = notification.getFieldValue("projectKey");
+ String period = notification.getFieldValue("period");
+ sb.append("\n--\n")
+ .append("See it in Sonar: ").append(configuration.getServerBaseURL()).append("/drilldown/measures/").append(projectKey)
+ .append("?metric=new_violations&period=").append(period).append('\n');
+ }
+
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.emailnotifications.newviolations;
+
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationDispatcher;
+import org.sonar.core.properties.PropertiesDao;
+
+/**
+ * This dispatcher means: "notify me when new violations are introduced on projects that I flagged as favourite".
+ *
+ * @since 2.14
+ */
+public class NewViolationsOnMyFavouriteProject extends NotificationDispatcher {
+
+ private PropertiesDao propertiesDao;
+
+ public NewViolationsOnMyFavouriteProject(PropertiesDao propertiesDao) {
+ this.propertiesDao = propertiesDao;
+ }
+
+ @Override
+ public void dispatch(Notification notification, Context context) {
+ if (StringUtils.equals(notification.getType(), "new-violations")) {
+ Integer projectId = Integer.parseInt(notification.getFieldValue("projectId"));
+ List<String> userLogins = propertiesDao.findUserIdsForFavouriteResource(projectId);
+ for (String userLogin : userLogins) {
+ context.addUser(userLogin);
+ }
+ }
+ }
+
+}
import org.sonar.api.notifications.NotificationDispatcher;
/**
- * This dispatcher means: "notify me when when someone changes review assigned to me or created by me".
+ * This dispatcher means: "notify me when someone changes review assigned to me or created by me".
*
* @since 2.10
*/
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.emailnotifications.newviolations;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationDispatcher;
+import org.sonar.core.properties.PropertiesDao;
+
+import com.google.common.collect.Lists;
+
+public class NewViolationsOnMyFavouriteProjectTest {
+
+ @Test
+ public void shouldNotDispatchIfNotNewViolationsNotification() throws Exception {
+ NotificationDispatcher.Context context = mock(NotificationDispatcher.Context.class);
+ NewViolationsOnMyFavouriteProject dispatcher = new NewViolationsOnMyFavouriteProject(null);
+ Notification notification = new Notification("other-notif");
+ dispatcher.dispatch(notification, context);
+
+ verify(context, never()).addUser(any(String.class));
+ }
+
+ @Test
+ public void shouldDispatchToUsersWhoHaveFlaggedProjectAsFavourite() {
+ NotificationDispatcher.Context context = mock(NotificationDispatcher.Context.class);
+ PropertiesDao propertiesDao = mock(PropertiesDao.class);
+ when(propertiesDao.findUserIdsForFavouriteResource(34)).thenReturn(Lists.newArrayList("user1", "user2"));
+ NewViolationsOnMyFavouriteProject dispatcher = new NewViolationsOnMyFavouriteProject(propertiesDao);
+
+ Notification notification = new Notification("new-violations").setFieldValue("projectId", "34");
+ dispatcher.dispatch(notification, context);
+
+ verify(context).addUser("user1");
+ verify(context).addUser("user2");
+ verifyNoMoreInteractions(context);
+ }
+
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.emailnotifications.newviolations;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+import org.sonar.plugins.emailnotifications.EmailConfiguration;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+
+public class NewViolationsTemplateTest {
+
+ private NewViolationsEmailTemplate template;
+
+ @Before
+ public void setUp() {
+ EmailConfiguration configuration = mock(EmailConfiguration.class);
+ when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
+ template = new NewViolationsEmailTemplate(configuration);
+ }
+
+ @Test
+ public void shouldNotFormatIfNotCorrectNotification() {
+ Notification notification = new Notification("other-notif");
+ EmailMessage message = template.format(notification);
+ assertThat(message, nullValue());
+ }
+
+ /**
+ * <pre>
+ * Subject: Review #1
+ * From: Freddy Mallet
+ *
+ * Project: Sonar
+ * Resource: org.sonar.server.ui.DefaultPages
+ *
+ * Utility classes should not have a public or default constructor.
+ *
+ * Comment:
+ * This is my first comment
+ *
+ * --
+ * See it in Sonar: http://nemo.sonarsource.org/review/view/1
+ * </pre>
+ */
+ @Test
+ public void shouldFormatCommentAdded() {
+ Notification notification = new Notification("new-violations")
+ .setFieldValue("count", "32")
+ .setFieldValue("projectName", "Foo")
+ .setFieldValue("projectKey", "org.sonar.foo:foo")
+ .setFieldValue("projectId", "45")
+ .setFieldValue("period", "2");
+
+ EmailMessage message = template.format(notification);
+ assertThat(message.getMessageId(), is("new-violations/45"));
+ assertThat(message.getSubject(), is("New violations for project Foo"));
+ assertThat(message.getMessage(), is("" +
+ "Project: Foo\n" +
+ "New violations on last analysis: 32\n" +
+ "\n" +
+ "--\n" +
+ "See it in Sonar: http://nemo.sonarsource.org/drilldown/measures/org.sonar.foo:foo?metric=new_violations&period=2\n"));
+ }
+
+}
#------------------------------------------------------------------------------
notification.channel.EmailNotificationChannel=Email
notification.dispatcher.ChangesInReviewAssignedToMeOrCreatedByMe=Changes in review assigned to me or created by me
+notification.dispatcher.NewViolationsOnMyFavouriteProject=New violations on my favourite projects
#------------------------------------------------------------------------------
*/
package org.sonar.batch.components;
-import com.google.common.collect.Lists;
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.Query;
+
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.utils.Logs;
-import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.PastSnapshotFinder;
-import javax.persistence.Query;
-import java.util.Date;
-import java.util.List;
+import com.google.common.collect.Lists;
public class TimeMachineConfiguration implements BatchExtension {
private List<PastSnapshot> projectPastSnapshots;
private DatabaseSession session;
- public TimeMachineConfiguration(DatabaseSession session, Project project, Configuration configuration, PastSnapshotFinder pastSnapshotFinder) {
+ public TimeMachineConfiguration(DatabaseSession session, Project project, Configuration configuration,
+ PastSnapshotFinder pastSnapshotFinder) {
this.session = session;
this.project = project;
this.configuration = configuration;
}
private Snapshot buildProjectSnapshot() {
- Query query = session.createNativeQuery("select p.id from projects p where p.kee=:resourceKey and p.qualifier<>:lib and p.enabled=:enabled");
+ Query query = session
+ .createNativeQuery("select p.id from projects p where p.kee=:resourceKey and p.qualifier<>:lib and p.enabled=:enabled");
query.setParameter("resourceKey", project.getKey());
query.setParameter("lib", Qualifiers.LIBRARY);
query.setParameter("enabled", Boolean.TRUE);
public boolean isFileVariationEnabled() {
return configuration.getBoolean("sonar.enableFileVariation", Boolean.FALSE);
}
+
+ /**
+ * Returns the index corresponding to the 'previous_analysis' period (which is '1' by default).
+ *
+ * @return the index of 'previous_analysis' period, or NULL is users have modified the periods and haven't set a 'previous_analysis' one.
+ */
+ public Integer getLastAnalysisPeriodIndex() {
+ // period1 is the default for 'previous_analysis'
+ String period1 = configuration.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + "1");
+ if (StringUtils.isBlank(period1) || CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS.equals(period1)) {
+ return 1;
+ }
+ // else search for the other periods
+ for (int index = 2; index < 6; index++) {
+ if (CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS.equals(configuration
+ .getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + index))) {
+ return index;
+ }
+ }
+ // if we're here, this means that we have not found the 'previous_analysis' mode
+ return null;
+ }
}
*/
package org.sonar.batch.components;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
import org.apache.commons.configuration.PropertiesConfiguration;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.sonar.api.CoreProperties;
import org.sonar.api.database.model.Snapshot;
import org.sonar.api.resources.Project;
-import org.sonar.batch.components.PastSnapshotFinder;
-import org.sonar.batch.components.TimeMachineConfiguration;
import org.sonar.jpa.test.AbstractDbUnitTestCase;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
public class TimeMachineConfigurationTest extends AbstractDbUnitTestCase {
@Test
public void shouldSkipTendencies() {
PropertiesConfiguration conf = new PropertiesConfiguration();
conf.setProperty(CoreProperties.SKIP_TENDENCIES_PROPERTY, true);
- assertThat(new TimeMachineConfiguration(getSession(), new Project("my:project"), conf, mock(PastSnapshotFinder.class)).skipTendencies(), is(true));
+ assertThat(
+ new TimeMachineConfiguration(getSession(), new Project("my:project"), conf, mock(PastSnapshotFinder.class)).skipTendencies(),
+ is(true));
}
@Test
public void shouldNotSkipTendenciesByDefault() {
PropertiesConfiguration conf = new PropertiesConfiguration();
- assertThat(new TimeMachineConfiguration(getSession(), new Project("my:project"), conf, mock(PastSnapshotFinder.class)).skipTendencies(), is(false));
+ assertThat(
+ new TimeMachineConfiguration(getSession(), new Project("my:project"), conf, mock(PastSnapshotFinder.class)).skipTendencies(),
+ is(false));
}
@Test
verifyZeroInteractions(pastSnapshotFinder);
}
+
+ @Test
+ public void shouldReturnLastAnalysisIndexIfSet() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ TimeMachineConfiguration timeMachineConfiguration = new TimeMachineConfiguration(getSession(), new Project("my:project"), conf,
+ mock(PastSnapshotFinder.class));
+
+ // Nothing set, so period for 'previous_analysis' is 1 by default
+ assertThat(timeMachineConfiguration.getLastAnalysisPeriodIndex(), is(1));
+
+ // period1 has been replaced and 'previous_analysis' not set elsewhere...
+ conf.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 1, "Version 1");
+ conf.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 2, "Version 2");
+ assertThat(timeMachineConfiguration.getLastAnalysisPeriodIndex(), is(nullValue()));
+
+ // 'previous_analysis' has now been set on period 4
+ conf.setProperty(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 4, CoreProperties.TIMEMACHINE_MODE_PREVIOUS_ANALYSIS);
+ assertThat(timeMachineConfiguration.getLastAnalysisPeriodIndex(), is(4));
+ }
+
}
import org.sonar.core.dashboard.ActiveDashboardDao;
import org.sonar.core.dashboard.DashboardDao;
import org.sonar.core.duplication.DuplicationDao;
+import org.sonar.core.properties.PropertiesDao;
import org.sonar.core.purge.PurgeDao;
import org.sonar.core.resource.ResourceDao;
import org.sonar.core.resource.ResourceIndexerDao;
DashboardDao.class,
DuplicationDao.class,
LoadedTemplateDao.class,
+ PropertiesDao.class,
PurgeDao.class,
ResourceIndexerDao.class,
ResourceDao.class,
*/
package org.sonar.core.persistence;
+import java.io.IOException;
+import java.io.InputStream;
+
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.Environment;
-import org.apache.ibatis.session.*;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchComponent;
import org.sonar.api.ServerComponent;
-import org.sonar.core.dashboard.*;
+import org.sonar.core.dashboard.ActiveDashboardDto;
+import org.sonar.core.dashboard.ActiveDashboardMapper;
+import org.sonar.core.dashboard.DashboardDto;
+import org.sonar.core.dashboard.DashboardMapper;
+import org.sonar.core.dashboard.WidgetDto;
+import org.sonar.core.dashboard.WidgetMapper;
+import org.sonar.core.dashboard.WidgetPropertyDto;
+import org.sonar.core.dashboard.WidgetPropertyMapper;
import org.sonar.core.duplication.DuplicationMapper;
import org.sonar.core.duplication.DuplicationUnitDto;
+import org.sonar.core.properties.PropertiesMapper;
import org.sonar.core.purge.PurgeMapper;
import org.sonar.core.purge.PurgeableSnapshotDto;
-import org.sonar.core.resource.*;
+import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.resource.ResourceIndexDto;
+import org.sonar.core.resource.ResourceIndexerMapper;
+import org.sonar.core.resource.ResourceMapper;
+import org.sonar.core.resource.SnapshotDto;
import org.sonar.core.review.ReviewDto;
import org.sonar.core.review.ReviewMapper;
import org.sonar.core.rule.RuleDto;
import org.sonar.core.template.LoadedTemplateDto;
import org.sonar.core.template.LoadedTemplateMapper;
-import java.io.IOException;
-import java.io.InputStream;
-
public class MyBatis implements BatchComponent, ServerComponent {
private Database database;
loadMapper(conf, DashboardMapper.class);
loadMapper(conf, DuplicationMapper.class);
loadMapper(conf, LoadedTemplateMapper.class);
+ loadMapper(conf, PropertiesMapper.class);
loadMapper(conf, PurgeMapper.class);
loadMapper(conf, ResourceMapper.class);
loadMapper(conf, ReviewMapper.class);
private InputStream getPathToMapper(Class mapperClass) {
InputStream input = getClass().getResourceAsStream(
- "/" + StringUtils.replace(mapperClass.getName(), ".", "/") + "-" + database.getDialect().getId() + ".xml");
+ "/" + StringUtils.replace(mapperClass.getName(), ".", "/") + "-" + database.getDialect().getId() + ".xml");
if (input == null) {
input = getClass().getResourceAsStream("/" + StringUtils.replace(mapperClass.getName(), ".", "/") + ".xml");
}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.core.properties;
+
+import java.util.List;
+
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.MyBatis;
+
+public class PropertiesDao implements BatchComponent, ServerComponent {
+
+ private MyBatis mybatis;
+
+ public PropertiesDao(MyBatis mybatis) {
+ this.mybatis = mybatis;
+ }
+
+ /**
+ * Returns the logins of users who have flagged as favourite the resource identified by the given id.
+ *
+ * @param resourceId the resource id
+ * @return the list of logins (maybe be empty - obviously)
+ */
+ public List<String> findUserIdsForFavouriteResource(Integer resourceId) {
+ SqlSession session = mybatis.openSession();
+ PropertiesMapper mapper = session.getMapper(PropertiesMapper.class);
+ try {
+ return mapper.findUserIdsForFavouriteResource(resourceId);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.core.properties;
+
+import java.util.List;
+
+import org.apache.ibatis.annotations.Param;
+
+public interface PropertiesMapper {
+
+ List<String> findUserIdsForFavouriteResource(@Param("resource_id") Integer resourceId);
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.core.properties.PropertiesMapper">
+
+ <select id="findUserIdsForFavouriteResource" parameterType="map" resultType="String">
+ SELECT U.login
+ FROM properties P, users U
+ WHERE P.prop_key = 'favourite' AND P.resource_id = #{resource_id} AND P.user_id = U.id
+ </select>
+
+</mapper>
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.core.properties;
+
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.persistence.DaoTestCase;
+
+public class PropertiesDaoTest extends DaoTestCase {
+
+ private PropertiesDao dao;
+
+ @Before
+ public void createDao() throws Exception {
+ dao = new PropertiesDao(getMyBatis());
+ }
+
+ @Test
+ public void shouldFindUserIdsForFavouriteResource() throws Exception {
+ setupData("shouldFindUserIdsForFavouriteResource");
+ List<String> userIds = dao.findUserIdsForFavouriteResource(2);
+ assertThat(userIds.size(), is(2));
+ assertThat(userIds, hasItems("user3", "user4"));
+ }
+}
--- /dev/null
+<dataset>
+
+ <properties
+ id="1"
+ prop_key="sonar.core.id"
+ text_value="2.14"
+ resource_id="[null]"
+ user_id="[null]"/>
+
+ <properties
+ id="2"
+ prop_key="favourite"
+ text_value=""
+ resource_id="1"
+ user_id="2"/>
+
+ <properties
+ id="3"
+ prop_key="favourite"
+ text_value=""
+ resource_id="2"
+ user_id="3"/>
+
+ <properties
+ id="4"
+ prop_key="favourite"
+ text_value=""
+ resource_id="2"
+ user_id="4"/>
+
+ <users
+ id="2"
+ login="user2"/>
+
+ <users
+ id="3"
+ login="user3"/>
+
+ <users
+ id="4"
+ login="user4"/>
+
+</dataset>