return session.getMapper(ResourceMapper.class).selectResource(projectId);
}
+ @CheckForNull
public SnapshotDto getLastSnapshot(String resourceKey, SqlSession session) {
return session.getMapper(ResourceMapper.class).selectLastSnapshotByResourceKey(resourceKey);
}
+ @CheckForNull
+ public SnapshotDto getLastSnapshotByResourceId(long resourceId) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getLastSnapshotByResourceId(resourceId, session);
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+ }
+
+ @CheckForNull
public SnapshotDto getLastSnapshotByResourceId(long resourceId, SqlSession session) {
return session.getMapper(ResourceMapper.class).selectLastSnapshotByResourceId(resourceId);
}
*/
package org.sonar.core.resource;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
import java.util.Date;
public final class SnapshotDto {
private Long id;
private Long parentId;
private Long rootId;
-
+
private Date date;
private Date buildDate;
private Long resourceId;
private Integer depth;
private Long rootProjectId;
+ private String period1Mode;
+ private String period2Mode;
+ private String period3Mode;
+ private String period4Mode;
+ private String period5Mode;
+
+ private String period1Param;
+ private String period2Param;
+ private String period3Param;
+ private String period4Param;
+ private String period5Param;
+
+ private Date period1Date;
+ private Date period2Date;
+ private Date period3Date;
+ private Date period4Date;
+ private Date period5Date;
+
public Long getId() {
return id;
}
}
public Date getDate() {
- return date;//NOSONAR May expose internal representation by returning reference to mutable object
+ return date;
}
public SnapshotDto setDate(Date date) {
- this.date = date;// NOSONAR May expose internal representation by incorporating reference to mutable object
+ this.date = date;
return this;
}
public Date getBuildDate() {
- return buildDate;//NOSONAR May expose internal representation by returning reference to mutable object
+ return buildDate;
}
public SnapshotDto setBuildDate(Date buildDate) {
- this.buildDate = buildDate;// NOSONAR May expose internal representation by incorporating reference to mutable object
+ this.buildDate = buildDate;
return this;
}
this.rootProjectId = rootProjectId;
return this;
}
+
+ @CheckForNull
+ public String getPeriod1Mode() {
+ return period1Mode;
+ }
+
+ public SnapshotDto setPeriod1Mode(@Nullable String period1Mode) {
+ this.period1Mode = period1Mode;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriod2Mode() {
+ return period2Mode;
+ }
+
+ public SnapshotDto setPeriod2Mode(@Nullable String period2Mode) {
+ this.period2Mode = period2Mode;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriod3Mode() {
+ return period3Mode;
+ }
+
+ public SnapshotDto setPeriod3Mode(@Nullable String period3Mode) {
+ this.period3Mode = period3Mode;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriod4Mode() {
+ return period4Mode;
+ }
+
+ public SnapshotDto setPeriod4Mode(@Nullable String period4Mode) {
+ this.period4Mode = period4Mode;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriod5Mode() {
+ return period5Mode;
+ }
+
+ public SnapshotDto setPeriod5Mode(@Nullable String period5Mode) {
+ this.period5Mode = period5Mode;
+ return this;
+ }
+
+ public String getPeriodMode(int index) {
+ switch (index) {
+ case 1:
+ return period1Mode;
+ case 2:
+ return period2Mode;
+ case 3:
+ return period3Mode;
+ case 4:
+ return period4Mode;
+ case 5:
+ return period5Mode;
+ default:
+ throw new IndexOutOfBoundsException("Index of periodMode is between 1 and 5");
+ }
+ }
+
+ @CheckForNull
+ public String getPeriod1Param() {
+ return period1Param;
+ }
+
+ public SnapshotDto setPeriod1Param(@Nullable String period1Param) {
+ this.period1Param = period1Param;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriod2Param() {
+ return period2Param;
+ }
+
+ public SnapshotDto setPeriod2Param(@Nullable String period2Param) {
+ this.period2Param = period2Param;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriod3Param() {
+ return period3Param;
+ }
+
+ public SnapshotDto setPeriod3Param(@Nullable String period3Param) {
+ this.period3Param = period3Param;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriod4Param() {
+ return period4Param;
+ }
+
+ public SnapshotDto setPeriod4Param(@Nullable String period4Param) {
+ this.period4Param = period4Param;
+ return this;
+ }
+
+ @CheckForNull
+ public String getPeriod5Param() {
+ return period5Param;
+ }
+
+ public SnapshotDto setPeriod5Param(@Nullable String period5Param) {
+ this.period5Param = period5Param;
+ return this;
+ }
+
+ public String getPeriodModeParameter(int periodIndex) {
+ switch (periodIndex) {
+ case 1:
+ return period1Param;
+ case 2:
+ return period2Param;
+ case 3:
+ return period3Param;
+ case 4:
+ return period4Param;
+ case 5:
+ return period5Param;
+ default:
+ throw new IndexOutOfBoundsException("Index of periodModeParameter is between 1 and 5");
+ }
+ }
+
+ @CheckForNull
+ public Date getPeriod1Date() {
+ return period1Date;
+ }
+
+ public SnapshotDto setPeriod1Date(@Nullable Date period1Date) {
+ this.period1Date = period1Date;
+ return this;
+ }
+
+ @CheckForNull
+ public Date getPeriod2Date() {
+ return period2Date;
+ }
+
+ public SnapshotDto setPeriod2Date(@Nullable Date period2Date) {
+ this.period2Date = period2Date;
+ return this;
+ }
+
+ @CheckForNull
+ public Date getPeriod3Date() {
+ return period3Date;
+ }
+
+ public SnapshotDto setPeriod3Date(@Nullable Date period3Date) {
+ this.period3Date = period3Date;
+ return this;
+ }
+
+ @CheckForNull
+ public Date getPeriod4Date() {
+ return period4Date;
+ }
+
+ public SnapshotDto setPeriod4Date(@Nullable Date period4Date) {
+ this.period4Date = period4Date;
+ return this;
+ }
+
+ @CheckForNull
+ public Date getPeriod5Date() {
+ return period5Date;
+ }
+
+ public SnapshotDto setPeriod5Date(@Nullable Date period5Date) {
+ this.period5Date = period5Date;
+ return this;
+ }
+
+ public Date getPeriodDate(int periodIndex) {
+ switch (periodIndex) {
+ case 1:
+ return period1Date;
+ case 2:
+ return period2Date;
+ case 3:
+ return period3Date;
+ case 4:
+ return period4Date;
+ case 5:
+ return period5Date;
+ default:
+ throw new IndexOutOfBoundsException("Index of periodDate is between 1 and 5");
+ }
+ }
}
import org.sonar.api.database.model.Snapshot;
import org.sonar.api.i18n.I18n;
+import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import java.text.ParseException;
this.i18n = i18n;
}
+ @CheckForNull
public String label(Snapshot snapshot, int periodIndex) {
return label(snapshot.getPeriodMode(periodIndex), snapshot.getPeriodModeParameter(periodIndex), snapshot.getPeriodDate(periodIndex));
}
+ @CheckForNull
public String abbreviation(Snapshot snapshot, int periodIndex) {
return abbreviation(snapshot.getPeriodMode(periodIndex), snapshot.getPeriodModeParameter(periodIndex), snapshot.getPeriodDate(periodIndex));
}
+ @CheckForNull
public String label(int periodIndex) {
String periodProperty = settings.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex);
PeriodParameters periodParameters = new PeriodParameters(periodProperty);
return label(periodParameters.getMode(), periodParameters.getParam(), periodParameters.getDate());
}
+ @CheckForNull
public String abbreviation(int periodIndex) {
String periodProperty = settings.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + periodIndex);
PeriodParameters periodParameters = new PeriodParameters(periodProperty);
return abbreviation(periodParameters.getMode(), periodParameters.getParam(), periodParameters.getDate());
}
+ @CheckForNull
public String label(String mode, String param, Date date) {
return label(mode, param, convertDate(date), false);
}
+ @CheckForNull
public String label(String mode, String param, String date) {
return label(mode, param, date, false);
}
+ @CheckForNull
public String abbreviation(String mode, String param, Date date) {
return label(mode, param, convertDate(date), true);
}
+ @CheckForNull
private String label(String mode, @Nullable String param, @Nullable String date, boolean shortLabel) {
String label;
if (CoreProperties.TIMEMACHINE_MODE_DAYS.equals(mode)) {
return i18n.message(getLocale(), msgKey, null, parameters);
}
+ @CheckForNull
private String convertDate(Date date) {
if (date != null) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy MMM dd");
<result property="path" column="path"/>
<result property="depth" column="depth"/>
<result property="rootProjectId" column="root_project_id"/>
+ <result property="period1Mode" column="period1_mode"/>
+ <result property="period2Mode" column="period2_mode"/>
+ <result property="period3Mode" column="period3_mode"/>
+ <result property="period4Mode" column="period4_mode"/>
+ <result property="period5Mode" column="period5_mode"/>
+ <result property="period1Param" column="period1_param"/>
+ <result property="period2Param" column="period2_param"/>
+ <result property="period3Param" column="period3_param"/>
+ <result property="period4Param" column="period4_param"/>
+ <result property="period5Param" column="period5_param"/>
+ <result property="period1Date" column="period1_date"/>
+ <result property="period2Date" column="period2_date"/>
+ <result property="period3Date" column="period3_date"/>
+ <result property="period4Date" column="period4_date"/>
+ <result property="period5Date" column="period5_date"/>
</resultMap>
<resultMap id="resourceResultMap" type="Resource">
</select>
<select id="selectLastSnapshotByResourceKey" parameterType="string" resultMap="snapshotResultMap">
- select s.* from snapshots s, projects p where p.kee=#{id} and p.enabled=${_true} and p.copy_resource_id is null and s.islast=${_true} and p.id=s.project_id
+ SELECT s.* FROM snapshots s
+ INNER JOIN projects p on p.id=s.project_id AND p.enabled=${_true} AND p.copy_resource_id IS NULL
+ <where>
+ AND p.kee=#{id}
+ AND s.islast=${_true}
+ </where>
</select>
<select id="selectLastSnapshotByResourceId" parameterType="string" resultMap="snapshotResultMap">
- select s.* from snapshots s where s.project_id=#{id} and s.islast=${_true}
- </select>
+ SELECT s.* from snapshots s
+ <where>
+ AND s.project_id=#{id}
+ AND s.islast=${_true}
+ </where>
+ </select>
<select id="selectDescendantProjects" parameterType="long" resultMap="resourceResultMap">
select * from projects where scope='PRJ' and root_id=#{id}
assertThat(dao.selectProvisionedProject("unknown")).isNull();
}
+ @Test
+ public void get_last_snapshot_by_resource_id() {
+ setupData("fixture");
+
+ SnapshotDto snapshotDto = dao.getLastSnapshotByResourceId(1L);
+ assertThat(snapshotDto.getId()).isEqualTo(1);
+
+ snapshotDto = dao.getLastSnapshotByResourceId(2L);
+ assertThat(snapshotDto.getId()).isEqualTo(2L);
+
+ snapshotDto = dao.getLastSnapshotByResourceId(3L);
+ assertThat(snapshotDto.getId()).isEqualTo(3L);
+
+ assertThat(dao.getLastSnapshotByResourceId(42L)).isNull();
+ }
+
private List<String> getKeys(final List<Component> components) {
return newArrayList(Iterables.transform(components, new Function<Component, String>() {
@Override
import org.sonar.core.properties.PropertyDto;
import org.sonar.core.properties.PropertyQuery;
import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.SnapshotDto;
+import org.sonar.core.timemachine.Periods;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;
private final ResourceDao resourceDao;
private final MeasureDao measureDao;
private final PropertiesDao propertiesDao;
+ private final Periods periods;
private final Durations durations;
private final I18n i18n;
- public ComponentAppAction(ResourceDao resourceDao, MeasureDao measureDao, PropertiesDao propertiesDao, Durations durations, I18n i18n) {
+ public ComponentAppAction(ResourceDao resourceDao, MeasureDao measureDao, PropertiesDao propertiesDao, Periods periods, Durations durations, I18n i18n) {
this.resourceDao = resourceDao;
this.measureDao = measureDao;
this.propertiesDao = propertiesDao;
+ this.periods = periods;
this.durations = durations;
this.i18n = i18n;
}
if (component == null) {
throw new NotFoundException(String.format("Component '%s' does not exists.", fileKey));
}
+ Long projectId = component.projectId();
+ Long subProjectId = component.subProjectId();
+ // projectId and subProjectId can't be null here
+ if (projectId != null && subProjectId != null) {
+ List<PropertyDto> propertyDtos = propertiesDao.selectByQuery(PropertyQuery.builder()
+ .setKey("favourite")
+ .setComponentId(component.getId())
+ .setUserId(userSession.userId())
+ .build());
+ boolean isFavourite = propertyDtos.size() == 1;
+
+ json.prop("key", component.key());
+ json.prop("path", component.path());
+ json.prop("name", component.name());
+ json.prop("q", component.qualifier());
+
+ Component subProject = componentById(subProjectId);
+ json.prop("subProjectName", subProject != null ? subProject.longName() : null);
+
+ Component project = componentById(projectId);
+ json.prop("projectName", project != null ? project.longName() : null);
+
+ json.prop("fav", isFavourite);
+ appendPeriods(json, projectId);
+ appendMeasures(json, fileKey);
+ }
+ json.endObject();
+ json.close();
+ }
- List<PropertyDto> propertyDtos = propertiesDao.selectByQuery(PropertyQuery.builder()
- .setKey("favourite")
- .setComponentId(component.getId())
- .setUserId(userSession.userId())
- .build());
- boolean isFavourite = propertyDtos.size() == 1;
-
- json.prop("key", component.key());
- json.prop("path", component.path());
- json.prop("name", component.name());
- json.prop("q", component.qualifier());
-
- Component subProject = componentById(component.subProjectId());
- json.prop("subProjectName", subProject != null ? subProject.longName() : null);
-
- Component project = componentById(component.projectId());
- json.prop("projectName", project != null ? project.longName() : null);
-
- json.prop("fav", isFavourite);
-
+ private void appendMeasures(JsonWriter json, String fileKey) {
json.name("measures").beginObject();
json.prop("fNcloc", formattedMeasure(fileKey, CoreMetrics.NCLOC));
json.prop("fCoverage", formattedMeasure(fileKey, CoreMetrics.COVERAGE));
json.prop("fMinorIssues", formattedMeasure(fileKey, CoreMetrics.MINOR_VIOLATIONS));
json.prop("fInfoIssues", formattedMeasure(fileKey, CoreMetrics.INFO_VIOLATIONS));
json.endObject();
+ }
- json.endObject();
- json.close();
+ private void appendPeriods(JsonWriter json, Long projectId) {
+ json.name("periods").beginArray();
+ SnapshotDto snapshotDto = resourceDao.getLastSnapshotByResourceId(projectId);
+ if (snapshotDto != null) {
+ for (int i = 1; i <= 5; i++) {
+ String mode = snapshotDto.getPeriodMode(i);
+ if (mode != null) {
+ String label = periods.label(mode, snapshotDto.getPeriodModeParameter(i), snapshotDto.getPeriodDate(i));
+ if (label != null) {
+ json.beginObject().prop(Integer.toString(i), label).endObject();
+ }
+ }
+ }
+ }
+ json.endArray();
}
@CheckForNull
"subProjectName": "SonarQube :: Plugin API",
"projectName": "SonarQube",
"fav": true,
- "periods": [],
+ "periods": [
+ {"1" : "since previous analysis (May 08 2014)"},
+ {"2" : "over 365 days (May 17 2013)"},
+ {"3" : "since previous version (4.3 - Apr 17 2014)"}
+ ],
"measures": {
"fNcloc": "200",
"fCoverage": "95.4%",
import org.sonar.core.properties.PropertyDto;
import org.sonar.core.properties.PropertyQuery;
import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.SnapshotDto;
+import org.sonar.core.timemachine.Periods;
import org.sonar.server.user.MockUserSession;
import org.sonar.server.ws.WsTester;
+import java.util.Date;
import java.util.Locale;
import static com.google.common.collect.Lists.newArrayList;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.*;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@Mock
PropertiesDao propertiesDao;
+ @Mock
+ Periods periods;
+
@Mock
Durations durations;
@Before
public void setUp() throws Exception {
- tester = new WsTester(new ComponentsWs(new ComponentAppAction(resourceDao, measureDao, propertiesDao, durations, i18n)));
+ tester = new WsTester(new ComponentsWs(new ComponentAppAction(resourceDao, measureDao, propertiesDao, periods, durations, i18n)));
}
@Test
when(measureDao.findByComponentKeyAndMetricKey(COMPONENT_KEY, CoreMetrics.TECHNICAL_DEBT_KEY)).thenReturn(new MeasureDto().setValue(182.0));
when(durations.format(any(Locale.class), any(Duration.class), eq(Durations.DurationFormat.SHORT))).thenReturn("3h 2min");
+ when(resourceDao.getLastSnapshotByResourceId(eq(1L))).thenReturn(new SnapshotDto().setPeriod1Mode("previous_analysis"));
+ when(periods.label(anyString(), anyString(), any(Date.class))).thenReturn("since previous analysis (May 08 2014)");
+
WsTester.TestRequest request = tester.newGetRequest("api/components", "app").setParam("key", COMPONENT_KEY);
request.execute().assertJson(getClass(), "app.json");
}
import org.sonar.core.measure.db.MeasureDao;
import org.sonar.core.properties.PropertiesDao;
import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.timemachine.Periods;
import org.sonar.server.ws.WsTester;
import static org.fest.assertions.Assertions.assertThat;
@Before
public void setUp() throws Exception {
WsTester tester = new WsTester(new ComponentsWs(new ComponentAppAction(mock(ResourceDao.class), mock(MeasureDao.class), mock(PropertiesDao.class),
- mock(Durations.class), mock(I18n.class))));
+ mock(Periods.class), mock(Durations.class), mock(I18n.class))));
controller = tester.controller("api/components");
}
import static org.fest.assertions.Assertions.assertThat;
public class MeasureFilterExecutorTest {
+
private static final long JAVA_PROJECT_ID = 1L;
private static final long JAVA_FILE_BIG_ID = 3L;
private static final long JAVA_FILE_TINY_ID = 4L;
"subProjectName": "SonarQube :: Plugin API",
"projectName": "SonarQube",
"fav": true,
+ "periods": [
+ {"1" : "since previous analysis (May 08 2014)"}
+ ],
"measures": {
"fNcloc": "200",
"fCoverage": "95.4%",