aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>2015-04-08 15:16:27 +0200
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>2015-04-14 10:05:02 +0200
commita744a740f2a6a5646159391fb1994c3c7b098509 (patch)
tree37974e64503878d411c34fc1e7f0312be985f6c0 /server
parenta74d2a51765e1270d790a1ad14384d58aa2530c4 (diff)
downloadsonarqube-a744a740f2a6a5646159391fb1994c3c7b098509.tar.gz
sonarqube-a744a740f2a6a5646159391fb1994c3c7b098509.zip
SONAR-6308 WS to show a profile's changelog
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileChangelogAction.java142
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-changelog.json38
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest.java141
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_empty.json6
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_nominal.json18
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page1.json18
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page2.json18
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page3.json6
9 files changed, 388 insertions, 0 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
index 43f1691fb9f..f3b490d007b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
@@ -391,6 +391,7 @@ class ServerComponents {
pico.addSingleton(QProfileImportersAction.class);
pico.addSingleton(QProfileInheritanceAction.class);
pico.addSingleton(QProfileChangeParentAction.class);
+ pico.addSingleton(QProfileChangelogAction.class);
pico.addSingleton(QProfilesWs.class);
pico.addSingleton(ProfilesWs.class);
pico.addSingleton(RuleActivationActions.class);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileChangelogAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileChangelogAction.java
new file mode 100644
index 00000000000..6869e8273a6
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileChangelogAction.java
@@ -0,0 +1,142 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualityprofile.ws;
+
+import org.sonar.api.resources.Languages;
+import org.sonar.api.server.ws.*;
+import org.sonar.api.server.ws.WebService.NewAction;
+import org.sonar.api.server.ws.WebService.NewController;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.Paging;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.qualityprofile.QProfileActivity;
+import org.sonar.server.qualityprofile.QProfileActivityQuery;
+import org.sonar.server.qualityprofile.QProfileFactory;
+import org.sonar.server.qualityprofile.QProfileService;
+import org.sonar.server.search.Result;
+
+import java.util.Date;
+import java.util.Map.Entry;
+
+public class QProfileChangelogAction implements BaseQProfileWsAction {
+
+ private static final String PARAM_SINCE = "since";
+ private static final String PARAM_TO = "to";
+
+ private QProfileService service;
+ private DbClient dbClient;
+ private QProfileFactory profileFactory;
+ private Languages languages;
+
+ public QProfileChangelogAction(QProfileService service, DbClient dbClient, QProfileFactory profileFactory, Languages languages) {
+ this.service = service;
+ this.dbClient = dbClient;
+ this.profileFactory = profileFactory;
+ this.languages = languages;
+ }
+
+ @Override
+ public void define(NewController context) {
+ NewAction changelog = context.createAction("changelog")
+ .setSince("5.2")
+ .setDescription("Get the history of changes on a quality profile: rule activation/deactivation, change in parameters/severity. " +
+ "Events are ordered by date in descending order (most recent first).")
+ .setHandler(this)
+ .setResponseExample(getClass().getResource("example-changelog.json"));
+
+ QProfileIdentificationParamUtils.defineProfileParams(changelog, languages);
+
+ changelog.addPagingParams(50);
+
+ changelog.createParam(PARAM_SINCE)
+ .setDescription("Start date for the changelog.")
+ .setExampleValue("2011-04-25");
+
+ changelog.createParam(PARAM_TO)
+ .setDescription("End date for the changelog.")
+ .setExampleValue("2013-07-25");
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ DbSession session = dbClient.openSession(false);
+ try {
+ String profileKey = QProfileIdentificationParamUtils.getProfileKeyFromParameters(request, profileFactory, session);
+ if (dbClient.qualityProfileDao().getByKey(session, profileKey) == null) {
+ throw new NotFoundException(String.format("Could not find a profile with key '%s'", profileKey));
+ }
+
+ QProfileActivityQuery query = new QProfileActivityQuery().setQprofileKey(profileKey);
+ Date since = request.paramAsDate(PARAM_SINCE);
+ if (since != null) {
+ query.setSince(since);
+ }
+ Date to = request.paramAsDate(PARAM_TO);
+ if (to != null) {
+ query.setTo(to);
+ }
+ SearchOptions options = new SearchOptions();
+
+ int page = request.mandatoryParamAsInt(Param.PAGE);
+ options.setPage(page, request.mandatoryParamAsInt(Param.PAGE_SIZE));
+
+ Result<QProfileActivity> result = service.searchActivities(query, options);
+ writeResponse(response.newJsonWriter(), result, Paging.create(options.getLimit(), page, (int) result.getTotal()));
+ } finally {
+ session.close();
+ }
+ }
+
+ private void writeResponse(JsonWriter json, Result<QProfileActivity> result, Paging paging) {
+ json.beginObject();
+ json.prop("total", result.getTotal());
+ json.prop(Param.PAGE, paging.pageIndex());
+ json.prop(Param.PAGE_SIZE, paging.pageSize());
+ json.name("events").beginArray();
+ for (QProfileActivity event : result.getHits()) {
+ json.beginObject()
+ .prop("date", DateUtils.formatDateTime(event.getCreatedAt()))
+ .prop("authorLogin", event.getLogin())
+ .prop("authorName", event.authorName())
+ .prop("action", event.getAction())
+ .prop("ruleKey", event.ruleKey().toString())
+ .prop("ruleName", event.ruleName());
+ writeParameters(json, event);
+ json.endObject();
+ }
+ json.endArray();
+ json.endObject().close();
+ }
+
+ private void writeParameters(JsonWriter json, QProfileActivity event) {
+ json.name("params").beginObject()
+ .prop("severity", event.severity());
+ for (Entry<String, String> param : event.parameters().entrySet()) {
+ json.prop(param.getKey(), param.getValue());
+ }
+ json.endObject();
+ }
+
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-changelog.json b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-changelog.json
new file mode 100644
index 00000000000..50ba1e571d9
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-changelog.json
@@ -0,0 +1,38 @@
+{
+ "total": 3,
+ "ps": 10,
+ "p": 1,
+ "events": [
+ {
+ "date" : "2015-02-23T17:58:39+0100",
+ "action" : "ACTIVATED",
+ "authorLogin" : "anakin.skywalker",
+ "authorName" : "Anakin Skywalker",
+ "ruleKey" : "squid:S2438",
+ "ruleName" : "\"Threads\" should not be used where \"Runnables\" are expected",
+ "params" : {
+ "severity" : "CRITICAL"
+ }
+ },
+ {
+ "date" : "2015-02-23T17:58:18+0100",
+ "action" : "DEACTIVATED",
+ "authorLogin" : "padme.amidala",
+ "authorName" : "Padme Amidala",
+ "ruleKey" : "squid:S2162",
+ "ruleName" : "\"equals\" methods should be symmetric and work for subclasses"
+ },
+ {
+ "action" : "ACTIVATED",
+ "authorLogin" : "obiwan.kenobi",
+ "authorName" : "Obiwan Kenobi",
+ "ruleKey" : "squid:S00101",
+ "ruleName" : "Class names should comply with a naming convention",
+ "date" : "2014-09-12T15:20:46+0200",
+ "params" : {
+ "severity" : "MAJOR",
+ "format" : "^[A-Z][a-zA-Z0-9]*$"
+ }
+ }
+ ]
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest.java
new file mode 100644
index 00000000000..d7d7b43898a
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest.java
@@ -0,0 +1,141 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualityprofile.ws;
+
+import org.joda.time.DateTime;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.qualityprofile.db.ActiveRuleKey;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.user.UserDto;
+import org.sonar.server.activity.ActivityService;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+import org.sonar.server.qualityprofile.QProfileTesting;
+import org.sonar.server.rule.RuleTesting;
+import org.sonar.server.tester.ServerTester;
+import org.sonar.server.user.MockUserSession;
+import org.sonar.server.ws.WsTester;
+
+import java.util.Date;
+
+import static org.sonar.server.qualityprofile.QProfileTesting.XOO_P1_KEY;
+
+public class QProfileChangelogActionMediumTest {
+
+ @ClassRule
+ public static ServerTester tester = new ServerTester();
+
+ private DbClient db;
+ private DbSession dbSession;
+ private WsTester wsTester;
+
+ @Before
+ public void before() {
+ tester.clearDbAndIndexes();
+ db = tester.get(DbClient.class);
+ dbSession = db.openSession(false);
+
+ // create pre-defined rules
+ RuleDto xooRule1 = RuleTesting.newXooX1().setSeverity("MINOR");
+ db.ruleDao().insert(dbSession, xooRule1);
+
+ // create pre-defined profiles P1 and P2
+ db.qualityProfileDao().insert(dbSession, QProfileTesting.newXooP1(), QProfileTesting.newXooP2());
+
+ // create a user for activity author
+ UserDto user = new UserDto().setLogin("david").setName("David").setEmail("dav@id.com").setCreatedAt(System.currentTimeMillis()).setUpdatedAt(System.currentTimeMillis());
+ db.userDao().insert(dbSession, user);
+
+ dbSession.commit();
+ dbSession.clearCache();
+
+ wsTester = new WsTester(tester.get(QProfilesWs.class));
+ }
+
+ @After
+ public void after() throws Exception {
+ dbSession.close();
+ }
+
+ @Test
+ public void changelog_nominal() throws Exception {
+ MockUserSession.set().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN).setLogin("david");
+ tester.get(ActivityService.class).save(ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1))
+ .setSeverity(Severity.MAJOR)
+ .setParameter("max", "10").toActivity());
+
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY)
+ .execute().assertJson(getClass(), "changelog_nominal.json");
+ }
+
+ @Test
+ public void changelog_with_dates() throws Exception {
+ Date yesterday = DateTime.now().minusDays(1).toDate();
+ Date tomorrow = DateTime.now().plusDays(1).toDate();
+
+ MockUserSession.set().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN).setLogin("david");
+ tester.get(ActivityService.class).save(ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1))
+ .setSeverity(Severity.MAJOR)
+ .setParameter("max", "10").toActivity());
+
+ // Tests with "since"
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("since", DateUtils.formatDate(yesterday))
+ .execute().assertJson(getClass(), "changelog_nominal.json");
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("since", DateUtils.formatDate(tomorrow))
+ .execute().assertJson(getClass(), "changelog_empty.json");
+
+ // Tests with "to"
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("to", DateUtils.formatDate(yesterday))
+ .execute().assertJson(getClass(), "changelog_empty.json");
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("to", DateUtils.formatDate(tomorrow))
+ .execute().assertJson(getClass(), "changelog_nominal.json");
+ }
+
+ @Test
+ public void changelog_with_pagination() throws Exception {
+ MockUserSession.set().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN).setLogin("david");
+ tester.get(ActivityService.class).save(ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1))
+ .setSeverity(Severity.MAJOR)
+ .setParameter("max", "10").toActivity());
+ tester.get(ActivityService.class).save(ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1))
+ .setSeverity(Severity.CRITICAL)
+ .setParameter("max", "20").toActivity());
+
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("ps", "1")
+ .execute().assertJson(getClass(), "changelog_page1.json");
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("ps", "1").setParam("p", "2")
+ .execute().assertJson(getClass(), "changelog_page2.json");
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("ps", "1").setParam("p", "3")
+ .execute().assertJson(getClass(), "changelog_page3.json");
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void fail_on_unknown_profile() throws Exception {
+ wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", "unknown-profile").execute();
+ }
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_empty.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_empty.json
new file mode 100644
index 00000000000..22d12b01f52
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_empty.json
@@ -0,0 +1,6 @@
+{
+ "total": 0,
+ "p": 1,
+ "ps": 50,
+ "events": []
+} \ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_nominal.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_nominal.json
new file mode 100644
index 00000000000..b0262c23911
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_nominal.json
@@ -0,0 +1,18 @@
+{
+ "total": 1,
+ "p": 1,
+ "ps": 50,
+ "events": [
+ {
+ "action" : "ACTIVATED",
+ "authorLogin" : "david",
+ "authorName" : "David",
+ "ruleKey" : "xoo:x1",
+ "ruleName" : "Rule x1",
+ "params" : {
+ "severity" : "MAJOR",
+ "max" : "10"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page1.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page1.json
new file mode 100644
index 00000000000..adc40882c1b
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page1.json
@@ -0,0 +1,18 @@
+{
+ "total": 2,
+ "p": 1,
+ "ps": 1,
+ "events": [
+ {
+ "action" : "ACTIVATED",
+ "authorLogin" : "david",
+ "authorName" : "David",
+ "ruleKey" : "xoo:x1",
+ "ruleName" : "Rule x1",
+ "params" : {
+ "severity" : "CRITICAL",
+ "max" : "20"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page2.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page2.json
new file mode 100644
index 00000000000..728705acc14
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page2.json
@@ -0,0 +1,18 @@
+{
+ "total": 2,
+ "p": 2,
+ "ps": 1,
+ "events": [
+ {
+ "action" : "ACTIVATED",
+ "authorLogin" : "david",
+ "authorName" : "David",
+ "ruleKey" : "xoo:x1",
+ "ruleName" : "Rule x1",
+ "params" : {
+ "severity" : "MAJOR",
+ "max" : "10"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page3.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page3.json
new file mode 100644
index 00000000000..06536dd3116
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page3.json
@@ -0,0 +1,6 @@
+{
+ "total": 2,
+ "p": 3,
+ "ps": 1,
+ "events": []
+} \ No newline at end of file