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 {
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) {
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-}
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;
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;
}
activeRule = doUpdate(change, context, dbSession);
}
- activityService.save(change.toActivity());
+ db.qProfileChangeDao().insert(dbSession, change.toDto());
return activeRule;
}
*/
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 {
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();
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
*/
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();
}
}
--- /dev/null
+/*
+ * 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();
+ }
+}
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),
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();
}
--- /dev/null
+{
+ "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"
+ }
+ }
+ ]
+}
+++ /dev/null
-{
- "total": 1,
- "p": 1,
- "ps": 50,
- "events": [
- {
- "action" : "ACTIVATED",
- "ruleKey" : "xoo:x1",
- "ruleName" : "Rule x1",
- "params" : {
- "severity" : "MAJOR"
- }
- }
- ]
-}
\ No newline at end of file
+++ /dev/null
-{
- "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
{
- "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
+}
+++ /dev/null
-{
- "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
+++ /dev/null
-{
- "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
+++ /dev/null
-{
- "total": 2,
- "p": 3,
- "ps": 1,
- "events": []
-}
\ No newline at end of file