]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5305 Return periods in /api/components/app
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 19 May 2014 08:46:23 +0000 (10:46 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 19 May 2014 08:46:23 +0000 (10:46 +0200)
sonar-core/src/main/java/org/sonar/core/resource/ResourceDao.java
sonar-core/src/main/java/org/sonar/core/resource/SnapshotDto.java
sonar-core/src/main/java/org/sonar/core/timemachine/Periods.java
sonar-core/src/main/resources/org/sonar/core/resource/ResourceMapper.xml
sonar-core/src/test/java/org/sonar/core/resource/ResourceDaoTest.java
sonar-server/src/main/java/org/sonar/server/component/ws/ComponentAppAction.java
sonar-server/src/main/resources/org/sonar/server/component/ws/components-app-example-show.json
sonar-server/src/test/java/org/sonar/server/component/ws/ComponentAppActionTest.java
sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java
sonar-server/src/test/java/org/sonar/server/measure/MeasureFilterExecutorTest.java
sonar-server/src/test/resources/org/sonar/server/component/ws/ComponentAppActionTest/app.json

index 7637e4c70e10345c35407f7f6d4ddf3017fc13a6..3d6ac2c7debe9613a8ff23f09bd02acb08d3026e 100644 (file)
@@ -102,10 +102,22 @@ public class ResourceDao {
     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);
   }
index a2f17e5a4fe74a692c2c8cc6b885df582080bb19..aa5207fb1908a92eb61a9bd5a0c418b183b1dab9 100644 (file)
  */
 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;
@@ -39,6 +42,24 @@ public final class SnapshotDto {
   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;
   }
@@ -67,20 +88,20 @@ public final class SnapshotDto {
   }
 
   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;
   }
 
@@ -173,4 +194,205 @@ public final class SnapshotDto {
     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");
+    }
+  }
 }
index e0edb8cab56f05d42ab164dfbcd516f8ab7f9111..d5932a7aba399c3d27db3a5e60e4f43ffa83daff 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.api.config.Settings;
 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;
@@ -44,38 +45,46 @@ public class Periods implements BatchExtension, ServerComponent {
     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)) {
@@ -117,6 +126,7 @@ public class Periods implements BatchExtension, ServerComponent {
     return i18n.message(getLocale(), msgKey, null, parameters);
   }
 
+  @CheckForNull
   private String convertDate(Date date) {
     if (date != null) {
       SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy MMM dd");
index d7fc5a305b67a3268bb607967bd5909bbb747815..df5db9f95bc58b1ae2ea3a003566314da3e44fa4 100644 (file)
     <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}
index 2ab9a786848c677e46f1c019ee050bd61e5e313b..c40a43ea280db50da48cbc3d141b497930f2e15f 100644 (file)
@@ -439,6 +439,22 @@ public class ResourceDaoTest extends AbstractDaoTestCase {
     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
index 8fc426d1daf942015c986648f91656d28e0f412a..c21ff1afdcea00d657b5835cf1cd356175dd76d5 100644 (file)
@@ -39,6 +39,8 @@ import org.sonar.core.properties.PropertiesDao;
 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;
 
@@ -54,13 +56,15 @@ public class ComponentAppAction implements RequestHandler {
   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;
   }
@@ -93,27 +97,37 @@ public class ComponentAppAction implements RequestHandler {
     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));
@@ -126,9 +140,23 @@ public class ComponentAppAction implements RequestHandler {
     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
index edb029da2920c26a6dd7174ebe941d77fcfac9d4..83bf5229658d86f5d44a88ae8ed4d54c6982a72f 100644 (file)
@@ -6,7 +6,11 @@
   "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%",
index 4dbbcb53cafd17fd0f3968e88c0bb5a2487bb726..4b1720ec673d42bdd356d8d7dfac0bc26970ddad 100644 (file)
@@ -37,14 +37,16 @@ import org.sonar.core.properties.PropertiesDao;
 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)
@@ -62,6 +64,9 @@ public class ComponentAppActionTest {
   @Mock
   PropertiesDao propertiesDao;
 
+  @Mock
+  Periods periods;
+
   @Mock
   Durations durations;
 
@@ -72,7 +77,7 @@ public class ComponentAppActionTest {
 
   @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
@@ -99,6 +104,9 @@ public class ComponentAppActionTest {
     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");
   }
index 14a86e4e6d44224185e82faf2182466b0404fe16..6bd1d3e10e72a10bea0968945403b8b8a2d5a964 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.api.utils.Durations;
 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;
@@ -41,7 +42,7 @@ public class ComponentsWsTest {
   @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");
   }
 
index babee4705db17c4c5016d62f61165f846c9edaea..8edc2aa6b5ae926583f217889bdc29ed1a8060e1 100644 (file)
@@ -37,6 +37,7 @@ import static com.google.common.collect.Lists.newArrayList;
 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;
index dd9f33365b023001982cba8fe3fea8d8192b059f..e50f1e591bd2b190b5c1467ab2a4ed1a8928ee4a 100644 (file)
@@ -6,6 +6,9 @@
   "subProjectName": "SonarQube :: Plugin API",
   "projectName": "SonarQube",
   "fav": true,
+  "periods": [
+    {"1" : "since previous analysis (May 08 2014)"}
+  ],
   "measures": {
     "fNcloc": "200",
     "fCoverage": "95.4%",