From a744a740f2a6a5646159391fb1994c3c7b098509 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lievremont Date: Wed, 8 Apr 2015 15:16:27 +0200 Subject: [PATCH] SONAR-6308 WS to show a profile's changelog --- .../server/platform/ServerComponents.java | 1 + .../ws/QProfileChangelogAction.java | 142 ++++++++++++++++++ .../qualityprofile/ws/example-changelog.json | 38 +++++ .../ws/QProfileChangelogActionMediumTest.java | 141 +++++++++++++++++ .../changelog_empty.json | 6 + .../changelog_nominal.json | 18 +++ .../changelog_page1.json | 18 +++ .../changelog_page2.json | 18 +++ .../changelog_page3.json | 6 + 9 files changed, 388 insertions(+) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileChangelogAction.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-changelog.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest.java create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_empty.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_nominal.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page1.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page2.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/QProfileChangelogActionMediumTest/changelog_page3.json 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 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 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 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 -- 2.39.5