]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7851 WS api/qualityprofiles/changelog stops using ES
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Sun, 11 Sep 2016 15:54:26 +0000 (17:54 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Mon, 12 Sep 2016 12:11:59 +0000 (14:11 +0200)
Changes are loaded from database but not from Elasticsearch anymore

17 files changed:
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ActiveRuleChange.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileActivity.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileActivityQuery.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivator.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ChangelogAction.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ChangelogLoader.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileFinder.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ChangelogActionTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ChangelogLoaderTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfilesWsTest.java
server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_full.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_no_login.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_no_param.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_nominal.json
server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_page1.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_page2.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_page3.json [deleted file]

index 9621ce0fc268516480ff0a5525a4ab77d8400e14..3c1bc44396443e75571340423db878b0c4050431 100644 (file)
@@ -21,12 +21,13 @@ package org.sonar.server.qualityprofile;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.Maps;
+import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.server.activity.Activity;
+import org.sonar.db.qualityprofile.QProfileChangeDto;
 
 public class ActiveRuleChange {
 
@@ -88,25 +89,26 @@ public class ActiveRuleChange {
     return this;
   }
 
-  public Activity toActivity() {
-    Activity activity = new Activity();
-    activity.setType(Activity.Type.QPROFILE);
-    activity.setAction(type.name());
-    activity.setProfileKey(getKey().qProfile());
-    activity.setData("key", getKey().toString());
-    activity.setData("ruleKey", getKey().ruleKey().toString());
+  public QProfileChangeDto toDto() {
+    QProfileChangeDto dto = new QProfileChangeDto();
+    dto.setChangeType(type.name());
+    dto.setProfileKey(getKey().qProfile());
+    Map<String, String> data = new HashMap<>();
+    data.put("key", getKey().toString());
+    data.put("ruleKey", getKey().ruleKey().toString());
 
     parameters.entrySet().stream()
       .filter(param -> !param.getKey().isEmpty())
-      .forEach(param -> activity.setData("param_" + param.getKey(), param.getValue()));
+      .forEach(param -> data.put("param_" + param.getKey(), param.getValue()));
 
     if (StringUtils.isNotEmpty(severity)) {
-      activity.setData("severity", severity);
+      data.put("severity", severity);
     }
     if (inheritance != null) {
-      activity.setData("inheritance", inheritance.name());
+      data.put("inheritance", inheritance.name());
     }
-    return activity;
+    dto.setData(data);
+    return dto;
   }
 
   public static ActiveRuleChange createFor(Type type, ActiveRuleKey key) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileActivity.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileActivity.java
deleted file mode 100644 (file)
index f8753b6..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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;
-
-import com.google.common.collect.Maps;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.server.activity.index.ActivityDoc;
-import org.sonar.server.activity.index.ActivityIndexDefinition;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.util.Map;
-
-/**
- * @since 4.4
- */
-public class QProfileActivity extends ActivityDoc {
-
-  private static final String FIELD_SEVERITY = "severity";
-
-  private String ruleName = null;
-  private String authorName = null;
-
-  public QProfileActivity(Map<String, Object> fields) {
-    super(fields);
-    Map<String, String> details = getField("details");
-    for (Map.Entry detail : details.entrySet()) {
-      fields.put((String) detail.getKey(), detail.getValue());
-    }
-    if (!fields.containsKey(FIELD_SEVERITY)) {
-      fields.put(FIELD_SEVERITY, null);
-    }
-  }
-
-  @CheckForNull
-  public String ruleName() {
-    return ruleName;
-  }
-
-  public void ruleName(@Nullable String ruleName) {
-    this.ruleName = ruleName;
-  }
-
-  @CheckForNull
-  public String authorName() {
-    return authorName;
-  }
-
-  public void authorName(@Nullable String authorName) {
-    this.authorName = authorName;
-  }
-
-  public String profileKey(){
-    return getField("profileKey");
-  }
-
-  public RuleKey ruleKey(){
-    return RuleKey.parse(getField("ruleKey"));
-  }
-
-  @Override
-  @CheckForNull
-  public String getLogin() {
-    return getNullableField(ActivityIndexDefinition.FIELD_LOGIN);
-  }
-
-  @CheckForNull
-  public String severity(){
-    return (String) getNullableField(FIELD_SEVERITY);
-  }
-
-  public Map<String, String> parameters() {
-    Map<String, String> params = Maps.newHashMap();
-    for (Map.Entry<String, Object> param : fields.entrySet()) {
-      if (param.getKey().startsWith("param_")) {
-        params.put(param.getKey().replace("param_", ""), (String) param.getValue());
-      }
-    }
-    return params;
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileActivityQuery.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileActivityQuery.java
deleted file mode 100644 (file)
index 06f242b..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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;
-
-import java.util.Arrays;
-import javax.annotation.Nullable;
-import org.sonar.server.activity.Activity;
-import org.sonar.server.activity.index.ActivityQuery;
-
-/**
- * @since 4.4
- */
-public class QProfileActivityQuery extends ActivityQuery {
-
-  public QProfileActivityQuery() {
-    super();
-    setTypes(Arrays.asList(Activity.Type.QPROFILE.name()));
-  }
-
-  public QProfileActivityQuery setQprofileKey(@Nullable String qprofileKey) {
-    if (qprofileKey != null) {
-      addDataOrFilter("profileKey", qprofileKey);
-    }
-    return this;
-  }
-}
index 96a923b6970c371fd52737ade543c505ffd7765e..36af7117c29ac3eaee1c210d6e3e04a000a52de4 100644 (file)
@@ -41,7 +41,6 @@ import org.sonar.db.qualityprofile.ActiveRuleParamDto;
 import org.sonar.db.qualityprofile.QualityProfileDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleParamDto;
-import org.sonar.server.activity.ActivityService;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
 import org.sonar.server.rule.index.RuleIndex;
@@ -63,19 +62,17 @@ public class RuleActivator {
   private final RuleActivatorContextFactory contextFactory;
   private final RuleIndex ruleIndex;
   private final ActiveRuleIndexer activeRuleIndexer;
-  private final ActivityService activityService;
   private final UserSession userSession;
 
   public RuleActivator(System2 system2, DbClient db, RuleIndex ruleIndex,
     RuleActivatorContextFactory contextFactory, TypeValidations typeValidations,
-    ActiveRuleIndexer activeRuleIndexer, ActivityService activityService, UserSession userSession) {
+    ActiveRuleIndexer activeRuleIndexer, UserSession userSession) {
     this.system2 = system2;
     this.db = db;
     this.ruleIndex = ruleIndex;
     this.contextFactory = contextFactory;
     this.typeValidations = typeValidations;
     this.activeRuleIndexer = activeRuleIndexer;
-    this.activityService = activityService;
     this.userSession = userSession;
   }
 
@@ -248,7 +245,7 @@ public class RuleActivator {
       activeRule = doUpdate(change, context, dbSession);
     }
 
-    activityService.save(change.toActivity());
+    db.qProfileChangeDao().insert(dbSession, change.toDto());
     return activeRule;
   }
 
index 14e4704afc8af1ec3efc077e95b515b42b2d8101..968259448d928b4d65802d2a291b686516c675d4 100644 (file)
  */
 package org.sonar.server.qualityprofile.ws;
 
-import com.google.common.base.Optional;
 import java.util.Date;
-import java.util.Map.Entry;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.search.SearchHit;
-import org.sonar.api.resources.Languages;
+import java.util.Map;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 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.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.activity.index.ActivityIndex;
-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.search.Result;
-
-import static org.sonar.api.utils.Paging.forPageIndex;
+import org.sonar.db.qualityprofile.QProfileChangeQuery;
+import org.sonar.db.qualityprofile.QualityProfileDto;
+
 import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
 
 public class ChangelogAction implements QProfileWsAction {
@@ -53,118 +38,82 @@ public class ChangelogAction implements QProfileWsAction {
   private static final String PARAM_SINCE = "since";
   private static final String PARAM_TO = "to";
 
-  private DbClient dbClient;
-  private ActivityIndex activityIndex;
-  private QProfileFactory profileFactory;
-  private Languages languages;
+  private final ChangelogLoader changelogLoader;
+  private final QProfileFinder profileFinder;
 
-  public ChangelogAction(DbClient dbClient, ActivityIndex activityIndex, QProfileFactory profileFactory, Languages languages) {
-    this.dbClient = dbClient;
-    this.activityIndex = activityIndex;
-    this.profileFactory = profileFactory;
-    this.languages = languages;
+  public ChangelogAction(ChangelogLoader changelogLoader, QProfileFinder profileFinder) {
+    this.changelogLoader = changelogLoader;
+    this.profileFinder = profileFinder;
   }
 
   @Override
   public void define(NewController context) {
-    NewAction changelog = context.createAction("changelog")
+    NewAction wsAction = 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);
+    profileFinder.defineProfileParams(wsAction);
 
-    changelog.addPagingParams(50, MAX_LIMIT);
+    wsAction.addPagingParams(50, MAX_LIMIT);
 
-    changelog.createParam(PARAM_SINCE)
+    wsAction.createParam(PARAM_SINCE)
       .setDescription("Start date for the changelog.")
       .setExampleValue("2011-04-25T01:15:42+0100");
 
-    changelog.createParam(PARAM_TO)
+    wsAction.createParam(PARAM_TO)
       .setDescription("End date for the changelog.")
       .setExampleValue("2013-07-25T07:35:42+0200");
   }
 
   @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().selectByKey(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.paramAsDateTime(PARAM_SINCE);
-      if (since != null) {
-        query.setSince(since);
-      }
-      Date to = request.paramAsDateTime(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 = searchActivities(query, options);
-      writeResponse(response.newJsonWriter(), result, forPageIndex(page).withPageSize(options.getLimit()).andTotal((int) result.getTotal()));
-    } finally {
-      session.close();
-    }
-  }
+    QualityProfileDto profile = profileFinder.find(request);
 
-  private Result<QProfileActivity> searchActivities(QProfileActivityQuery query, SearchOptions options) {
-    DbSession session = dbClient.openSession(false);
-    try {
-      SearchResponse response = activityIndex.doSearch(query, options);
-      Result<QProfileActivity> result = new Result<>(response);
-      for (SearchHit hit : response.getHits().getHits()) {
-        QProfileActivity profileActivity = new QProfileActivity(hit.getSource());
-        Optional<RuleDto> ruleDto = dbClient.ruleDao().selectByKey(session, profileActivity.ruleKey());
-        profileActivity.ruleName(ruleDto.isPresent() ? ruleDto.get().getName() : null);
-
-        String login = profileActivity.getLogin();
-        if (login != null) {
-          UserDto user = dbClient.userDao().selectActiveUserByLogin(session, login);
-          profileActivity.authorName(user != null ? user.getName() : null);
-        }
-        result.getHits().add(profileActivity);
-      }
-      return result;
-    } finally {
-      session.close();
+    QProfileChangeQuery query = new QProfileChangeQuery(profile.getKey());
+    Date since = request.paramAsDateTime(PARAM_SINCE);
+    if (since != null) {
+      query.setFromIncluded(since.getTime());
     }
+    Date to = request.paramAsDateTime(PARAM_TO);
+    if (to != null) {
+      query.setToExcluded(to.getTime());
+    }
+    int page = request.mandatoryParamAsInt(Param.PAGE);
+    int pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
+    query.setPage(page, pageSize);
+
+    ChangelogLoader.Changelog changelog = changelogLoader.load(query);
+    writeResponse(response.newJsonWriter(), page, pageSize, changelog);
   }
 
-  private void writeResponse(JsonWriter json, Result<QProfileActivity> result, Paging paging) {
+  private void writeResponse(JsonWriter json, int page, int pageSize, ChangelogLoader.Changelog changelog) {
     json.beginObject();
-    json.prop("total", result.getTotal());
-    json.prop(Param.PAGE, paging.pageIndex());
-    json.prop(Param.PAGE_SIZE, paging.pageSize());
+    json.prop("total", changelog.getTotal());
+    json.prop(Param.PAGE, page);
+    json.prop(Param.PAGE_SIZE, pageSize);
     json.name("events").beginArray();
-    for (QProfileActivity event : result.getHits()) {
+    for (ChangelogLoader.Change change : changelog.getChanges()) {
       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);
+        .prop("date", DateUtils.formatDateTime(change.getCreatedAt()))
+        .prop("authorLogin", change.getUserLogin())
+        .prop("authorName", change.getUserName())
+        .prop("action", change.getType())
+        .prop("ruleKey", change.getRuleKey() == null ? null : change.getRuleKey().toString())
+        .prop("ruleName", change.getRuleName());
+      writeParameters(json, change);
       json.endObject();
     }
     json.endArray();
     json.endObject().close();
   }
 
-  private void writeParameters(JsonWriter json, QProfileActivity event) {
+  private void writeParameters(JsonWriter json, ChangelogLoader.Change change) {
     json.name("params").beginObject()
-      .prop("severity", event.severity());
-    for (Entry<String, String> param : event.parameters().entrySet()) {
+      .prop("severity", change.getSeverity());
+    for (Map.Entry<String, String> param : change.getParams().entrySet()) {
       json.prop(param.getKey(), param.getValue());
     }
     json.endObject();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ChangelogLoader.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ChangelogLoader.java
new file mode 100644 (file)
index 0000000..cf123a8
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.util.stream.Collectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.QProfileChangeDto;
+import org.sonar.db.qualityprofile.QProfileChangeQuery;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.user.UserDto;
+
+import static java.util.Objects.requireNonNull;
+
+@ServerSide
+public class ChangelogLoader {
+
+  private final DbClient dbClient;
+
+  public ChangelogLoader(DbClient dbClient) {
+    this.dbClient = dbClient;
+  }
+
+  /**
+   * @return non-null list of changes, by descending order of date
+   */
+  public Changelog load(QProfileChangeQuery query) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      List<QProfileChangeDto> dtos = dbClient.qProfileChangeDao().selectByQuery(dbSession, query);
+      List<Change> changes = dtos.stream()
+        .map(Change::from)
+        .collect(Collectors.toList(dtos.size()));
+      completeUserAndRuleNames(dbSession, changes);
+
+      int total = dbClient.qProfileChangeDao().countForProfileKey(dbSession, query.getProfileKey());
+      return new Changelog(total, changes);
+    }
+  }
+
+  private void completeUserAndRuleNames(DbSession dbSession, List<Change> changes) {
+    Set<String> logins = changes.stream().filter(c -> c.userLogin != null).map(c -> c.userLogin).collect(Collectors.toSet());
+    Map<String, String> userNamesByLogins = dbClient.userDao()
+      .selectByLogins(dbSession, logins)
+      .stream()
+      .collect(java.util.stream.Collectors.toMap(UserDto::getLogin, UserDto::getName));
+
+    Set<RuleKey> ruleKeys = changes.stream().filter(c -> c.ruleKey != null).map(c -> c.ruleKey).collect(Collectors.toSet());
+    Map<RuleKey, String> ruleNamesByKeys = dbClient.ruleDao()
+      .selectByKeys(dbSession, Lists.newArrayList(ruleKeys))
+      .stream()
+      .collect(java.util.stream.Collectors.toMap(RuleDto::getKey, RuleDto::getName));
+
+    changes.forEach(c -> {
+      c.userName = userNamesByLogins.get(c.userLogin);
+      c.ruleName = ruleNamesByKeys.get(c.ruleKey);
+    });
+  }
+
+  static class Change {
+    private String key;
+    private String type;
+    private long at;
+    private String severity;
+    private String userLogin;
+    private String userName;
+    private String inheritance;
+    private RuleKey ruleKey;
+    private String ruleName;
+    private final Map<String, String> params = new HashMap<>();
+
+    private Change() {
+    }
+
+    @VisibleForTesting
+    Change(String key, String type, long at, @Nullable String severity, @Nullable String userLogin, @Nullable String userName, @Nullable String inheritance, @Nullable RuleKey ruleKey, @Nullable String ruleName) {
+      this.key = key;
+      this.type = type;
+      this.at = at;
+      this.severity = severity;
+      this.userLogin = userLogin;
+      this.userName = userName;
+      this.inheritance = inheritance;
+      this.ruleKey = ruleKey;
+      this.ruleName = ruleName;
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    @CheckForNull
+    public String getSeverity() {
+      return severity;
+    }
+
+    @CheckForNull
+    public String getUserLogin() {
+      return userLogin;
+    }
+
+    @CheckForNull
+    public String getUserName() {
+      return userName;
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    @CheckForNull
+    public String getInheritance() {
+      return inheritance;
+    }
+
+    public RuleKey getRuleKey() {
+      return ruleKey;
+    }
+
+    @CheckForNull
+    public String getRuleName() {
+      return ruleName;
+    }
+
+    public long getCreatedAt() {
+      return at;
+    }
+
+    public Map<String, String> getParams() {
+      return params;
+    }
+
+    private static Change from(QProfileChangeDto dto) {
+      Map<String, String> data = dto.getDataAsMap();
+      Change change = new Change();
+      change.key = dto.getKey();
+      change.userLogin = dto.getLogin();
+      change.type = dto.getChangeType();
+      change.at = dto.getCreatedAt();
+      // see content of data in class org.sonar.server.qualityprofile.ActiveRuleChange
+      change.severity = data.get("severity");
+      String ruleKey = data.get("ruleKey");
+      if (ruleKey != null) {
+        change.ruleKey = RuleKey.parse(ruleKey);
+      }
+      change.inheritance = data.get("inheritance");
+      data.entrySet().stream()
+        .filter(entry -> entry.getKey().startsWith("param_"))
+        .forEach(entry -> change.params.put(entry.getKey().replace("param_", ""), entry.getValue()));
+      return change;
+    }
+  }
+
+  static class Changelog {
+    private final int total;
+    private final List<Change> changes;
+
+    Changelog(int total, List<Change> changes) {
+      this.total = total;
+      this.changes = requireNonNull(changes);
+    }
+
+    public int getTotal() {
+      return total;
+    }
+
+    public List<Change> getChanges() {
+      return changes;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileFinder.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileFinder.java
new file mode 100644 (file)
index 0000000..b1c2509
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.Request;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.QualityProfileDto;
+import org.sonar.server.component.ws.LanguageParamUtils;
+import org.sonar.server.exceptions.NotFoundException;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static org.apache.commons.lang.StringUtils.isEmpty;
+
+public class QProfileFinder {
+
+  private final DbClient dbClient;
+  private final Languages languages;
+
+  public QProfileFinder(DbClient dbClient, Languages languages) {
+    this.dbClient = dbClient;
+    this.languages = languages;
+  }
+
+  public void defineProfileParams(WebService.NewAction action) {
+    action.createParam(QProfileIdentificationParamUtils.PARAM_PROFILE_KEY)
+      .setDescription("A quality profile key. Either this parameter, or a combination of profileName + language must be set.")
+      .setExampleValue("sonar-way-java-12345");
+    action.createParam(QProfileIdentificationParamUtils.PARAM_PROFILE_NAME)
+      .setDescription("A quality profile name. If this parameter is set, profileKey must not be set and language must be set to disambiguate.")
+      .setExampleValue("Sonar way");
+    action.createParam(QProfileIdentificationParamUtils.PARAM_LANGUAGE)
+      .setDescription("A quality profile language. If this parameter is set, profileKey must not be set and profileName must be set to disambiguate.")
+      .setPossibleValues(LanguageParamUtils.getLanguageKeys(languages))
+      .setExampleValue("js");
+  }
+
+  public QualityProfileDto find(Request request) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      return find(request, dbSession);
+    }
+  }
+
+  public QualityProfileDto find(Request request, DbSession dbSession) {
+    String language = request.param(QProfileIdentificationParamUtils.PARAM_LANGUAGE);
+    String profileName = request.param(QProfileIdentificationParamUtils.PARAM_PROFILE_NAME);
+    String profileKey = request.param(QProfileIdentificationParamUtils.PARAM_PROFILE_KEY);
+
+    checkArgument(
+      (!isEmpty(language) && !isEmpty(profileName)) ^ !isEmpty(profileKey), "Either profileKey or profileName + language must be set");
+
+    if (profileKey != null) {
+      return findByKey(dbSession, profileKey);
+    }
+    return findByName(dbSession, language, profileName);
+  }
+
+  private QualityProfileDto findByKey(DbSession dbSession, String profileKey) {
+    QualityProfileDto profile;
+    profile = dbClient.qualityProfileDao().selectByKey(dbSession, profileKey);
+    if (profile == null) {
+      throw new NotFoundException(format("Unable to find a profile for with key '%s'", profileKey));
+    }
+    return profile;
+  }
+
+  private QualityProfileDto findByName(DbSession dbSession, String language, String profileName) {
+    QualityProfileDto profile;
+    profile = dbClient.qualityProfileDao().selectByNameAndLanguage(profileName, language, dbSession);
+    if (profile == null) {
+      throw new NotFoundException(format("Unable to find a profile for language '%s' with name '%s'", language, profileName));
+    }
+    return profile;
+  }
+}
index f1eb53c0f0138e272dc662c631bc01471b817905..c67888895c19c1e027687f6d1a7afd7329d4ca34 100644 (file)
  */
 package org.sonar.server.qualityprofile.ws;
 
-import com.google.common.collect.Maps;
-import java.util.Date;
-import java.util.Map;
-import org.joda.time.DateTime;
+import java.util.Collections;
+import java.util.List;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.config.MapSettings;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.server.ws.WebService.Param;
-import org.sonar.api.utils.DateUtils;
+import org.sonar.api.server.ws.Request;
 import org.sonar.api.utils.System2;
-import org.sonar.core.util.Uuids;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
-import org.sonar.db.qualityprofile.ActiveRuleKey;
-import org.sonar.db.rule.RuleDto;
+import org.sonar.db.qualityprofile.QProfileChangeQuery;
 import org.sonar.db.rule.RuleTesting;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.activity.Activity;
-import org.sonar.server.activity.index.ActivityDoc;
-import org.sonar.server.activity.index.ActivityIndex;
-import org.sonar.server.activity.index.ActivityIndexDefinition;
-import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.language.LanguageTesting;
-import org.sonar.server.qualityprofile.ActiveRuleChange;
-import org.sonar.server.qualityprofile.ActiveRuleChange.Type;
-import org.sonar.server.qualityprofile.QProfileFactory;
 import org.sonar.server.qualityprofile.QProfileTesting;
 import org.sonar.server.ws.WsTester;
 
+import static java.util.Arrays.asList;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.sonar.server.qualityprofile.QProfileTesting.XOO_P1_KEY;
 
 public class ChangelogActionTest {
 
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  private static final long A_DATE = 1_500_000_000_000L;
 
   @Rule
-  public EsTester esTester = new EsTester(new ActivityIndexDefinition(new MapSettings()));
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  private DbClient db = dbTester.getDbClient();
-  private DbSession dbSession = dbTester.getSession();
   private WsTester wsTester;
-  private String login;
+  private ChangelogLoader changelogLoader = mock(ChangelogLoader.class);
+  private QProfileFinder profileFinder = mock(QProfileFinder.class);
 
   @Before
   public void before() {
-    // 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());
-
-    login = "david";
-    UserDto user = new UserDto().setLogin(login).setName("David").setEmail("dav@id.com").setCreatedAt(System.currentTimeMillis()).setUpdatedAt(System.currentTimeMillis());
-    db.userDao().insert(dbSession, user);
-
-    dbSession.commit();
-    dbSession.clearCache();
-
     wsTester = new WsTester(new QProfilesWs(mock(RuleActivationActions.class), mock(BulkRuleActivationActions.class), mock(ProjectAssociationActions.class),
-      new ChangelogAction(db, new ActivityIndex(esTester.client()), new QProfileFactory(db), LanguageTesting.newLanguages("xoo"))));
+      new ChangelogAction(changelogLoader, profileFinder)));
   }
 
   @Test
   public void changelog_empty() throws Exception {
+    when(profileFinder.find(any(Request.class))).thenReturn(QProfileTesting.newXooP1());
+    when(changelogLoader.load(any(QProfileChangeQuery.class))).thenReturn(new ChangelogLoader.Changelog(0, Collections.emptyList()));
+
     wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY)
       .execute().assertJson(getClass(), "changelog_empty.json");
   }
 
   @Test
   public void changelog_nominal() throws Exception {
-    createActivity(login, ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1), Severity.MAJOR, new Date(), "max", "10");
+    when(profileFinder.find(any(Request.class))).thenReturn(QProfileTesting.newXooP1());
+    ChangelogLoader.Change change1 = new ChangelogLoader.Change("C1", "ACTIVATED", A_DATE, null, null, null, null, null, null);
+    ChangelogLoader.Change change2 = new ChangelogLoader.Change("C2", "ACTIVATED", A_DATE + 10, null, null, null, null, null, null);
+    List<ChangelogLoader.Change> changes = asList(change1, change2);
+    when(changelogLoader.load(any(QProfileChangeQuery.class))).thenReturn(new ChangelogLoader.Changelog(10, changes));
 
     wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY)
       .execute().assertJson(getClass(), "changelog_nominal.json");
   }
 
   @Test
-  public void changelog_no_param() throws Exception {
-    createActivity(login, ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1), Severity.MAJOR, new Date());
-
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY)
-      .execute().assertJson(getClass(), "changelog_no_param.json");
-  }
-
-  @Test
-  public void changelog_system_user() throws Exception {
-    createActivity(null, ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1), Severity.MAJOR, new Date());
-
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY)
-      .execute().assertJson(getClass(), "changelog_no_login.json");
-  }
-
-  @Test
-  public void changelog_with_dates() throws Exception {
-    Date yesterday = DateTime.now().minusDays(1).toDate();
-    Date tomorrow = DateTime.now().plusDays(1).toDate();
-
-    createActivity(login, ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1), Severity.MAJOR, new Date(), "max", "10");
-
-    // Tests with "since"
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("since",
-      DateUtils.formatDateTime(yesterday))
-      .execute().assertJson(getClass(), "changelog_nominal.json");
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("since",
-      DateUtils.formatDateTime(tomorrow))
-      .execute().assertJson(getClass(), "changelog_empty.json");
+  public void changelog_with_all_fields() throws Exception {
+    when(profileFinder.find(any(Request.class))).thenReturn(QProfileTesting.newXooP1());
+    ChangelogLoader.Change change1 = new ChangelogLoader.Change("C1", "ACTIVATED", A_DATE, "MAJOR", "marcel", "Marcel", "INHERITED", RuleTesting.XOO_X1, "X One");
+    change1.getParams().put("foo", "foo_value");
+    change1.getParams().put("bar", "bar_value");
+    List<ChangelogLoader.Change> changes = asList(change1);
+    when(changelogLoader.load(any(QProfileChangeQuery.class))).thenReturn(new ChangelogLoader.Changelog(10, changes));
 
-    // Tests with "to"
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("to",
-      DateUtils.formatDateTime(yesterday))
-      .execute().assertJson(getClass(), "changelog_empty.json");
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam("to",
-      DateUtils.formatDateTime(tomorrow))
-      .execute().assertJson(getClass(), "changelog_nominal.json");
-
-    // Test with both bounds set
     wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY)
-      .setParam("since", DateUtils.formatDateTime(yesterday))
-      .setParam("to", DateUtils.formatDateTime(tomorrow))
-      .execute().assertJson(getClass(), "changelog_nominal.json");
-  }
-
-  @Test
-  public void changelog_with_pagination() throws Exception {
-    Date farthest = new Date(1_500_000_000_000L);
-    createActivity(login, ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1), Severity.MAJOR, farthest, "max", "10");
-    Date nearest = new Date(1_500_000_100_000L);
-    createActivity(login, ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1), Severity.CRITICAL, nearest, "max", "20");
-
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam(Param.PAGE_SIZE, "1")
-      .execute().assertJson(getClass(), "changelog_page1.json");
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam(Param.PAGE_SIZE, "1").setParam(Param.PAGE, "2")
-      .execute().assertJson(getClass(), "changelog_page2.json");
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", XOO_P1_KEY).setParam(Param.PAGE_SIZE, "1").setParam(Param.PAGE, "3")
-      .execute().assertJson(getClass(), "changelog_page3.json");
+      .execute().assertJson(getClass(), "changelog_full.json");
   }
 
   @Test(expected = NotFoundException.class)
   public void fail_on_unknown_profile() throws Exception {
-    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", "unknown-profile").execute();
-  }
+    when(profileFinder.find(any(Request.class))).thenThrow(new NotFoundException("Profile not found"));
 
-  private void createActivity(String login, Type type, ActiveRuleKey activeRuleKey, String severity, Date createdAt, String... params) throws Exception {
-    Map<String, String> details = Maps.newHashMap();
-    details.put("key", activeRuleKey.toString());
-    details.put("ruleKey", activeRuleKey.ruleKey().toString());
-    details.put("profileKey", activeRuleKey.qProfile());
-    details.put("severity", severity);
-    for (int i = 0; i < params.length; i += 2) {
-      details.put("param_" + params[i], params[i + 1]);
-    }
-    ActivityDoc doc = new ActivityDoc(Maps.<String, Object>newHashMap());
-    doc.setAction(type.toString());
-    doc.setCreatedAt(createdAt);
-    doc.setDetails(details);
-    doc.setKey(Uuids.create());
-    doc.setLogin(login);
-    doc.setType(Activity.Type.QPROFILE.toString());
-
-    esTester.putDocuments(ActivityIndexDefinition.INDEX, ActivityIndexDefinition.TYPE, doc);
+    wsTester.newGetRequest(QProfilesWs.API_ENDPOINT, "changelog").setParam("profileKey", "unknown-profile").execute();
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ChangelogLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ChangelogLoaderTest.java
new file mode 100644 (file)
index 0000000..540cc86
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.qualityprofile.QProfileChangeDto;
+import org.sonar.db.qualityprofile.QProfileChangeQuery;
+import org.sonar.db.qualityprofile.QualityProfileTesting;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleTesting;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserTesting;
+import org.sonar.server.qualityprofile.ActiveRule;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+
+public class ChangelogLoaderTest {
+
+  private static final String A_PROFILE_KEY = "P1";
+  private static final long A_DATE = 1_500_000_000L;
+  private static final RuleKey A_RULE_KEY = RuleKey.of("java", "S001");
+  private static final String A_USER_LOGIN = "marcel";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private ChangelogLoader underTest = new ChangelogLoader(dbTester.getDbClient());
+
+  @Test
+  public void return_changes_in_reverse_chronological_order() {
+    insertChange("C1", ActiveRuleChange.Type.ACTIVATED, A_DATE, A_USER_LOGIN, null);
+    insertChange("C2", ActiveRuleChange.Type.DEACTIVATED, A_DATE + 10, "mazout", null);
+
+    QProfileChangeQuery query = new QProfileChangeQuery(A_PROFILE_KEY);
+    ChangelogLoader.Changelog changes = underTest.load(query);
+
+    assertThat(changes.getTotal()).isEqualTo(2);
+    assertThat(changes.getChanges()).extracting(ChangelogLoader.Change::getKey).containsExactly("C2", "C1");
+  }
+
+  @Test
+  public void return_change_with_only_required_fields() {
+    insertChange("C1", ActiveRuleChange.Type.ACTIVATED, A_DATE, null, null);
+
+    QProfileChangeQuery query = new QProfileChangeQuery(A_PROFILE_KEY);
+    ChangelogLoader.Change change = underTest.load(query).getChanges().get(0);
+
+    assertThat(change.getKey()).isEqualTo("C1");
+    assertThat(change.getCreatedAt()).isEqualTo(A_DATE);
+    assertThat(change.getType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED.name());
+    // optional fields are null or empty
+    assertThat(change.getInheritance()).isNull();
+    assertThat(change.getRuleKey()).isNull();
+    assertThat(change.getRuleName()).isNull();
+    assertThat(change.getSeverity()).isNull();
+    assertThat(change.getUserLogin()).isNull();
+    assertThat(change.getUserName()).isNull();
+    assertThat(change.getParams()).isEmpty();
+  }
+
+  @Test
+  public void return_change_with_all_fields() {
+    Map<String, String> data = ImmutableMap.of(
+      "ruleKey", A_RULE_KEY.toString(),
+      "severity", "MINOR",
+      "inheritance", ActiveRule.Inheritance.INHERITED.name(),
+      "param_foo", "foo_value",
+      "param_bar", "bar_value");
+    insertChange("C1", ActiveRuleChange.Type.ACTIVATED, A_DATE, A_USER_LOGIN, data);
+
+    QProfileChangeQuery query = new QProfileChangeQuery(A_PROFILE_KEY);
+    ChangelogLoader.Change change = underTest.load(query).getChanges().get(0);
+
+    assertThat(change.getKey()).isEqualTo("C1");
+    assertThat(change.getCreatedAt()).isEqualTo(A_DATE);
+    assertThat(change.getType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED.name());
+    assertThat(change.getInheritance()).isEqualTo(ActiveRule.Inheritance.INHERITED.name());
+    assertThat(change.getRuleKey()).isEqualTo(A_RULE_KEY);
+    assertThat(change.getRuleName()).isNull();
+    assertThat(change.getSeverity()).isEqualTo("MINOR");
+    assertThat(change.getUserLogin()).isEqualTo(A_USER_LOGIN);
+    assertThat(change.getUserName()).isNull();
+    assertThat(change.getParams()).containsOnly(entry("foo", "foo_value"), entry("bar", "bar_value"));
+  }
+
+  @Test
+  public void return_name_of_rule() {
+    Map<String, String> data = ImmutableMap.of("ruleKey", "java:S001");
+    insertChange("C1", ActiveRuleChange.Type.ACTIVATED, A_DATE, A_USER_LOGIN, data);
+    insertRule(A_RULE_KEY, "Potential NPE");
+
+    QProfileChangeQuery query = new QProfileChangeQuery(A_PROFILE_KEY);
+    ChangelogLoader.Change change = underTest.load(query).getChanges().get(0);
+
+    assertThat(change.getRuleKey()).isEqualTo(A_RULE_KEY);
+    assertThat(change.getRuleName()).isEqualTo("Potential NPE");
+  }
+
+  @Test
+  public void return_name_of_user() {
+    insertChange("C1", ActiveRuleChange.Type.ACTIVATED, A_DATE, A_USER_LOGIN, null);
+    insertUser(A_USER_LOGIN, "Marcel");
+
+    QProfileChangeQuery query = new QProfileChangeQuery(A_PROFILE_KEY);
+    ChangelogLoader.Change change = underTest.load(query).getChanges().get(0);
+
+    assertThat(change.getUserLogin()).isEqualTo(A_USER_LOGIN);
+    assertThat(change.getUserName()).isEqualTo("Marcel");
+  }
+
+  @Test
+  public void return_empty_changelog() {
+    QProfileChangeQuery query = new QProfileChangeQuery("P1");
+
+    ChangelogLoader.Changelog changelog = underTest.load(query);
+
+    assertThat(changelog.getTotal()).isEqualTo(0);
+    assertThat(changelog.getChanges()).isEmpty();
+  }
+
+  private void insertChange(String key, ActiveRuleChange.Type type, long date,
+    @Nullable String login, @Nullable Map<String, String> data) {
+    QProfileChangeDto dto = QualityProfileTesting.newQProfileChangeDto()
+      .setProfileKey(A_PROFILE_KEY)
+      .setKey(key)
+      .setCreatedAt(date)
+      .setLogin(login)
+      .setChangeType(type.name())
+      .setData(data);
+    QualityProfileTesting.insert(dbTester, dto);
+  }
+
+  private void insertRule(RuleKey key, String name) {
+    RuleDto dto = RuleTesting.newDto(key).setName(name);
+    dbTester.getDbClient().ruleDao().insert(dbTester.getSession(), dto);
+    dbTester.getSession().commit();
+  }
+
+  private void insertUser(String login, String name) {
+    UserDto dto = UserTesting.newUserDto()
+      .setLogin(login)
+      .setName(name);
+    dbTester.getDbClient().userDao().insert(dbTester.getSession(), dto);
+    dbTester.getSession().commit();
+  }
+}
index 3ae6c3107312cabd66334ed483c9e13bcbe5750a..09db9cd1c4dc57ffcefbb6c281f892f3fa945e16 100644 (file)
@@ -69,7 +69,7 @@ public class QProfilesWsTest {
       new ProjectsAction(null, userSessionRule),
       new BackupAction(null, null, null, languages),
       new RestoreAction(null, languages, userSessionRule),
-      new ChangelogAction(null, null, null, languages),
+      new ChangelogAction(null, mock(QProfileFinder.class)),
       new ChangeParentAction(null, null, null, languages, userSessionRule),
       new CompareAction(null, null, null, languages),
       new CopyAction(null, languages, userSessionRule),
@@ -232,8 +232,7 @@ public class QProfilesWsTest {
     WebService.Action changelog = controller.action("changelog");
     assertThat(changelog).isNotNull();
     assertThat(changelog.isPost()).isFalse();
-    assertThat(changelog.params()).hasSize(7).extracting("key").containsOnly(
-      "profileKey", "profileName", "language", "since", "to", "p", "ps");
+    assertThat(changelog.params().size()).isPositive();
     assertThat(changelog.responseExampleAsString()).isNotEmpty();
   }
 
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_full.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_full.json
new file mode 100644 (file)
index 0000000..1fa4b2a
--- /dev/null
@@ -0,0 +1,19 @@
+{
+  "total": 10,
+  "p": 1,
+  "ps": 50,
+  "events": [
+    {
+      "authorLogin": "marcel",
+      "authorName": "Marcel",
+      "action": "ACTIVATED",
+      "ruleKey": "xoo:x1",
+      "ruleName": "X One",
+      "params": {
+        "severity": "MAJOR",
+        "foo": "foo_value",
+        "bar": "bar_value"
+      }
+    }
+  ]
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_no_login.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_no_login.json
deleted file mode 100644 (file)
index eb0fe99..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "total": 1,
-  "p": 1,
-  "ps": 50,
-  "events": [
-    {
-      "action" : "ACTIVATED",
-      "ruleKey" : "xoo:x1",
-      "ruleName" : "Rule x1",
-      "params" : {
-        "severity" : "MAJOR"
-      }
-    }
-  ]
-}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_no_param.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_no_param.json
deleted file mode 100644 (file)
index 9ad8c7c..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "total": 1,
-  "p": 1,
-  "ps": 50,
-  "events": [
-    {
-      "action" : "ACTIVATED",
-      "authorLogin" : "david",
-      "authorName" : "David",
-      "ruleKey" : "xoo:x1",
-      "ruleName" : "Rule x1",
-      "params" : {
-        "severity" : "MAJOR"
-      }
-    }
-  ]
-}
\ No newline at end of file
index b0262c23911e61b8c844378d55f4417edff487a7..0ad79039d5e480d99f2f997532f71e9bddb19328 100644 (file)
@@ -1,18 +1,15 @@
 {
-  "total": 1,
+  "total": 10,
   "p": 1,
   "ps": 50,
   "events": [
     {
-      "action" : "ACTIVATED",
-      "authorLogin" : "david",
-      "authorName" : "David",
-      "ruleKey" : "xoo:x1",
-      "ruleName" : "Rule x1",
-      "params" : {
-        "severity" : "MAJOR",
-        "max" : "10"
-      }
+      "action": "ACTIVATED",
+      "params": {}
+    },
+    {
+      "action": "ACTIVATED",
+      "params": {}
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_page1.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_page1.json
deleted file mode 100644 (file)
index adc4088..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "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/ChangelogActionTest/changelog_page2.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_page2.json
deleted file mode 100644 (file)
index 728705a..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "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/ChangelogActionTest/changelog_page3.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/ChangelogActionTest/changelog_page3.json
deleted file mode 100644 (file)
index 06536dd..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "total": 2,
-  "p": 3,
-  "ps": 1,
-  "events": []
-}
\ No newline at end of file