import javax.annotation.Nullable;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
import static com.google.common.collect.Maps.newLinkedHashMap;
public Collection<String> listTags(@Nullable String query, int pageSize) {
return indexClient.get(IssueIndex.class).listTagsMatching(query, pageSize);
}
+
+ public Collection<String> setTags(String issueKey, Collection<String> tags) {
+ verifyLoggedIn();
+
+ DbSession session = dbClient.openSession(false);
+ try {
+ DefaultIssue issue = getByKeyForUpdate(session, issueKey).toDefaultIssue();
+ IssueChangeContext context = IssueChangeContext.createUser(new Date(), UserSession.get().login());
+ if (issueUpdater.setTags(issue, tags, context)) {
+ saveIssue(session, issue, context, null);
+ }
+ return issue.tags();
+
+ } finally {
+ session.close();
+ }
+ }
}
private final IssueShowAction showAction;
private final SearchAction esSearchAction;
private final TagsAction tagsAction;
+ private final SetTagsAction setTagsAction;
- public IssuesWs(IssueShowAction showAction, SearchAction searchAction, TagsAction tagsAction) {
+ public IssuesWs(IssueShowAction showAction, SearchAction searchAction, TagsAction tagsAction, SetTagsAction setTagsAction) {
this.showAction = showAction;
this.esSearchAction = searchAction;
this.tagsAction = tagsAction;
+ this.setTagsAction = setTagsAction;
}
@Override
showAction.define(controller);
esSearchAction.define(controller);
tagsAction.define(controller);
+ setTagsAction.define(controller);
defineChangelogAction(controller);
defineAssignAction(controller);
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.issue.ws;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.WebService.NewAction;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.issue.IssueService;
+
+import java.util.Collection;
+
+/**
+ * Set tags on an issue.
+ * @since 5.1
+ */
+public class SetTagsAction implements RequestHandler {
+
+ private static final Splitter WS_TAGS_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
+
+ private final IssueService service;
+
+ public SetTagsAction(IssueService service) {
+ this.service = service;
+ }
+
+ void define(WebService.NewController controller) {
+ NewAction action = controller.createAction("set_tags")
+ .setHandler(this)
+ .setPost(true)
+ .setSince("5.1")
+ .setDescription("Set tags on an issue. Requires authentication and Browse permission on project");
+ action.createParam("key")
+ .setDescription("Issue key")
+ .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef")
+ .setRequired(true);
+ action.createParam("tags")
+ .setDescription("A comma separated list of tags")
+ .setExampleValue("security,cwe,misra-c")
+ .setRequired(true)
+ .setDefaultValue("");
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ String key = request.mandatoryParam("key");
+ String tags = request.mandatoryParam("tags");
+ Collection<String> resultTags = service.setTags(key, ImmutableSet.copyOf(WS_TAGS_SPLITTER.split(tags)));
+ JsonWriter json = response.newJsonWriter().beginObject().name("tags").beginArray();
+ for (String tag : resultTags) {
+ json.value(tag);
+ }
+ json.endArray().endObject().close();
+ }
+
+}
import org.sonar.core.metric.DefaultMetricFinder;
import org.sonar.core.notification.DefaultNotificationManager;
import org.sonar.core.permission.PermissionFacade;
-import org.sonar.core.persistence.*;
+import org.sonar.core.persistence.DaoUtils;
+import org.sonar.core.persistence.DatabaseVersion;
+import org.sonar.core.persistence.DefaultDatabase;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.persistence.PreviewDatabaseFactory;
+import org.sonar.core.persistence.SemaphoreUpdater;
+import org.sonar.core.persistence.SemaphoresImpl;
import org.sonar.core.preview.PreviewCache;
import org.sonar.core.profiling.Profiling;
import org.sonar.core.purge.PurgeProfiler;
import org.sonar.server.activity.ws.ActivitiesWebService;
import org.sonar.server.activity.ws.ActivityMapping;
import org.sonar.server.authentication.ws.AuthenticationWs;
-import org.sonar.server.batch.*;
+import org.sonar.server.batch.BatchIndex;
+import org.sonar.server.batch.BatchWs;
+import org.sonar.server.batch.GlobalReferentialsAction;
+import org.sonar.server.batch.ProjectReferentialsAction;
+import org.sonar.server.batch.UploadReportAction;
import org.sonar.server.charts.ChartFactory;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.component.ComponentService;
import org.sonar.server.component.DefaultRubyComponentService;
import org.sonar.server.component.db.ComponentDao;
import org.sonar.server.component.db.SnapshotDao;
-import org.sonar.server.component.ws.*;
-import org.sonar.server.computation.*;
+import org.sonar.server.component.ws.ComponentAppAction;
+import org.sonar.server.component.ws.ComponentsWs;
+import org.sonar.server.component.ws.EventsWs;
+import org.sonar.server.component.ws.ProjectsWs;
+import org.sonar.server.component.ws.ResourcesWs;
+import org.sonar.server.computation.AnalysisReportQueue;
+import org.sonar.server.computation.AnalysisReportTaskCleaner;
+import org.sonar.server.computation.AnalysisReportTaskLauncher;
+import org.sonar.server.computation.ComponentIndexationInDatabaseStep;
+import org.sonar.server.computation.ComputationService;
+import org.sonar.server.computation.ComputationStepRegistry;
+import org.sonar.server.computation.DataCleanerStep;
+import org.sonar.server.computation.DigestAnalysisReportStep;
+import org.sonar.server.computation.IndexProjectIssuesStep;
+import org.sonar.server.computation.InvalidatePreviewCacheStep;
+import org.sonar.server.computation.SwitchSnapshotStep;
+import org.sonar.server.computation.SynchronizeProjectPermissionsStep;
import org.sonar.server.computation.db.AnalysisReportDao;
import org.sonar.server.computation.dbcleaner.DefaultPurgeTask;
import org.sonar.server.computation.dbcleaner.IndexPurgeListener;
import org.sonar.server.computation.dbcleaner.ProjectCleaner;
import org.sonar.server.computation.dbcleaner.period.DefaultPeriodCleaner;
-import org.sonar.server.computation.ws.*;
+import org.sonar.server.computation.ws.ActiveAnalysisReportsAction;
+import org.sonar.server.computation.ws.AnalysisReportHistorySearchAction;
+import org.sonar.server.computation.ws.AnalysisReportWebService;
+import org.sonar.server.computation.ws.ExperimentalAnalysisReportAction;
+import org.sonar.server.computation.ws.ExperimentalAnalysisReportWebService;
+import org.sonar.server.computation.ws.IsAnalysisReportQueueEmptyAction;
import org.sonar.server.config.ws.PropertiesWs;
import org.sonar.server.dashboard.db.DashboardDao;
import org.sonar.server.dashboard.db.WidgetDao;
import org.sonar.server.db.EmbeddedDatabaseFactory;
import org.sonar.server.db.migrations.DatabaseMigrations;
import org.sonar.server.db.migrations.DatabaseMigrator;
-import org.sonar.server.debt.*;
+import org.sonar.server.debt.DebtCharacteristicsXMLImporter;
+import org.sonar.server.debt.DebtModelBackup;
+import org.sonar.server.debt.DebtModelLookup;
+import org.sonar.server.debt.DebtModelOperations;
+import org.sonar.server.debt.DebtModelPluginRepository;
+import org.sonar.server.debt.DebtModelService;
+import org.sonar.server.debt.DebtModelXMLExporter;
+import org.sonar.server.debt.DebtRulesXMLImporter;
import org.sonar.server.design.FileDesignWidget;
import org.sonar.server.duplication.ws.DuplicationsJsonWriter;
import org.sonar.server.duplication.ws.DuplicationsParser;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.IndexCreator;
import org.sonar.server.es.IndexRegistry;
-import org.sonar.server.issue.*;
+import org.sonar.server.issue.ActionService;
+import org.sonar.server.issue.AssignAction;
+import org.sonar.server.issue.CommentAction;
+import org.sonar.server.issue.InternalRubyIssueService;
+import org.sonar.server.issue.IssueBulkChangeService;
+import org.sonar.server.issue.IssueChangelogFormatter;
+import org.sonar.server.issue.IssueChangelogService;
+import org.sonar.server.issue.IssueCommentService;
+import org.sonar.server.issue.IssueQueryService;
+import org.sonar.server.issue.IssueService;
+import org.sonar.server.issue.PlanAction;
+import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.SetSeverityAction;
+import org.sonar.server.issue.TransitionAction;
import org.sonar.server.issue.actionplan.ActionPlanService;
import org.sonar.server.issue.actionplan.ActionPlanWs;
import org.sonar.server.issue.db.IssueDao;
import org.sonar.server.issue.filter.IssueFilterService;
import org.sonar.server.issue.filter.IssueFilterWriter;
import org.sonar.server.issue.filter.IssueFilterWs;
-import org.sonar.server.issue.index.*;
+import org.sonar.server.issue.index.IssueAuthorizationIndexer;
+import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.issue.index.IssueNormalizer;
import org.sonar.server.issue.ws.IssueActionsWriter;
import org.sonar.server.issue.ws.IssueShowAction;
import org.sonar.server.issue.ws.IssuesWs;
+import org.sonar.server.issue.ws.SetTagsAction;
import org.sonar.server.measure.MeasureFilterEngine;
import org.sonar.server.measure.MeasureFilterExecutor;
import org.sonar.server.measure.MeasureFilterFactory;
import org.sonar.server.platform.ws.RestartHandler;
import org.sonar.server.platform.ws.ServerWs;
import org.sonar.server.platform.ws.SystemWs;
-import org.sonar.server.plugins.*;
+import org.sonar.server.plugins.InstalledPluginReferentialFactory;
+import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.ServerExtensionInstaller;
+import org.sonar.server.plugins.ServerPluginJarInstaller;
+import org.sonar.server.plugins.ServerPluginJarsInstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.UpdateCenterClient;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.properties.ProjectSettingsFactory;
import org.sonar.server.qualitygate.QgateProjectFinder;
import org.sonar.server.qualitygate.QualityGates;
import org.sonar.server.qualitygate.RegisterQualityGates;
-import org.sonar.server.qualitygate.ws.*;
-import org.sonar.server.qualityprofile.*;
+import org.sonar.server.qualitygate.ws.QGatesAppAction;
+import org.sonar.server.qualitygate.ws.QGatesCopyAction;
+import org.sonar.server.qualitygate.ws.QGatesCreateAction;
+import org.sonar.server.qualitygate.ws.QGatesCreateConditionAction;
+import org.sonar.server.qualitygate.ws.QGatesDeleteConditionAction;
+import org.sonar.server.qualitygate.ws.QGatesDeselectAction;
+import org.sonar.server.qualitygate.ws.QGatesDestroyAction;
+import org.sonar.server.qualitygate.ws.QGatesListAction;
+import org.sonar.server.qualitygate.ws.QGatesRenameAction;
+import org.sonar.server.qualitygate.ws.QGatesSearchAction;
+import org.sonar.server.qualitygate.ws.QGatesSelectAction;
+import org.sonar.server.qualitygate.ws.QGatesSetAsDefaultAction;
+import org.sonar.server.qualitygate.ws.QGatesShowAction;
+import org.sonar.server.qualitygate.ws.QGatesUnsetDefaultAction;
+import org.sonar.server.qualitygate.ws.QGatesUpdateConditionAction;
+import org.sonar.server.qualitygate.ws.QGatesWs;
+import org.sonar.server.qualityprofile.BuiltInProfiles;
+import org.sonar.server.qualityprofile.QProfileBackuper;
+import org.sonar.server.qualityprofile.QProfileCopier;
+import org.sonar.server.qualityprofile.QProfileExporters;
+import org.sonar.server.qualityprofile.QProfileFactory;
+import org.sonar.server.qualityprofile.QProfileLoader;
+import org.sonar.server.qualityprofile.QProfileLookup;
+import org.sonar.server.qualityprofile.QProfileProjectLookup;
+import org.sonar.server.qualityprofile.QProfileProjectOperations;
+import org.sonar.server.qualityprofile.QProfileReset;
+import org.sonar.server.qualityprofile.QProfileService;
+import org.sonar.server.qualityprofile.QProfiles;
+import org.sonar.server.qualityprofile.RegisterQualityProfiles;
+import org.sonar.server.qualityprofile.RuleActivator;
+import org.sonar.server.qualityprofile.RuleActivatorContextFactory;
import org.sonar.server.qualityprofile.db.ActiveRuleDao;
import org.sonar.server.qualityprofile.index.ActiveRuleIndex;
import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer;
-import org.sonar.server.qualityprofile.ws.*;
-import org.sonar.server.rule.*;
+import org.sonar.server.qualityprofile.ws.BulkRuleActivationActions;
+import org.sonar.server.qualityprofile.ws.ProfilesWs;
+import org.sonar.server.qualityprofile.ws.QProfileRestoreBuiltInAction;
+import org.sonar.server.qualityprofile.ws.QProfilesWs;
+import org.sonar.server.qualityprofile.ws.RuleActivationActions;
+import org.sonar.server.rule.DefaultRuleFinder;
+import org.sonar.server.rule.DeprecatedRulesDefinitionLoader;
+import org.sonar.server.rule.RegisterRules;
+import org.sonar.server.rule.RubyRuleService;
+import org.sonar.server.rule.RuleCreator;
+import org.sonar.server.rule.RuleDefinitionsLoader;
+import org.sonar.server.rule.RuleDeleter;
+import org.sonar.server.rule.RuleOperations;
+import org.sonar.server.rule.RuleRepositories;
+import org.sonar.server.rule.RuleService;
+import org.sonar.server.rule.RuleUpdater;
import org.sonar.server.rule.db.RuleDao;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleNormalizer;
-import org.sonar.server.rule.ws.*;
-import org.sonar.server.search.*;
+import org.sonar.server.rule.ws.ActiveRuleCompleter;
+import org.sonar.server.rule.ws.AppAction;
+import org.sonar.server.rule.ws.DeleteAction;
+import org.sonar.server.rule.ws.RuleMapping;
+import org.sonar.server.rule.ws.RulesWebService;
+import org.sonar.server.rule.ws.SearchAction;
+import org.sonar.server.rule.ws.TagsAction;
+import org.sonar.server.rule.ws.UpdateAction;
+import org.sonar.server.search.IndexClient;
+import org.sonar.server.search.IndexQueue;
+import org.sonar.server.search.IndexSynchronizer;
+import org.sonar.server.search.SearchClient;
+import org.sonar.server.search.SearchHealth;
import org.sonar.server.source.HtmlSourceDecorator;
import org.sonar.server.source.IndexSourceLinesStep;
import org.sonar.server.source.SourceService;
import org.sonar.server.source.index.SourceLineIndex;
import org.sonar.server.source.index.SourceLineIndexDefinition;
import org.sonar.server.source.index.SourceLineIndexer;
-import org.sonar.server.source.ws.*;
+import org.sonar.server.source.ws.HashAction;
+import org.sonar.server.source.ws.IndexAction;
+import org.sonar.server.source.ws.LinesAction;
+import org.sonar.server.source.ws.RawAction;
+import org.sonar.server.source.ws.ScmAction;
+import org.sonar.server.source.ws.ScmWriter;
import org.sonar.server.source.ws.ShowAction;
-import org.sonar.server.startup.*;
+import org.sonar.server.source.ws.SourcesWs;
+import org.sonar.server.startup.CleanPreviewAnalysisCache;
+import org.sonar.server.startup.CopyRequirementsFromCharacteristicsToRules;
+import org.sonar.server.startup.GeneratePluginIndex;
+import org.sonar.server.startup.JdbcDriverDeployer;
+import org.sonar.server.startup.LogServerId;
+import org.sonar.server.startup.RegisterDashboards;
+import org.sonar.server.startup.RegisterDebtModel;
+import org.sonar.server.startup.RegisterMetrics;
+import org.sonar.server.startup.RegisterNewMeasureFilters;
+import org.sonar.server.startup.RegisterPermissionTemplates;
+import org.sonar.server.startup.RegisterServletFilters;
+import org.sonar.server.startup.RenameDeprecatedPropertyKeys;
+import org.sonar.server.startup.ServerMetadataPersister;
import org.sonar.server.test.CoverageService;
-import org.sonar.server.test.ws.*;
+import org.sonar.server.test.ws.CoverageShowAction;
+import org.sonar.server.test.ws.CoverageWs;
+import org.sonar.server.test.ws.TestsCoveredFilesAction;
+import org.sonar.server.test.ws.TestsShowAction;
+import org.sonar.server.test.ws.TestsTestCasesAction;
+import org.sonar.server.test.ws.TestsWs;
import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.text.RubyTextService;
import org.sonar.server.ui.JRubyI18n;
import org.sonar.server.ui.PageDecorations;
import org.sonar.server.ui.Views;
import org.sonar.server.updatecenter.ws.UpdateCenterWs;
-import org.sonar.server.user.*;
+import org.sonar.server.user.DefaultUserService;
+import org.sonar.server.user.DoPrivileged;
+import org.sonar.server.user.GroupMembershipFinder;
+import org.sonar.server.user.GroupMembershipService;
+import org.sonar.server.user.NewUserNotifier;
+import org.sonar.server.user.SecurityRealmFactory;
import org.sonar.server.user.db.GroupDao;
import org.sonar.server.user.ws.FavoritesWs;
import org.sonar.server.user.ws.UserPropertiesWs;
import org.sonar.server.user.ws.UsersWs;
-import org.sonar.server.util.*;
+import org.sonar.server.util.BooleanTypeValidation;
+import org.sonar.server.util.FloatTypeValidation;
+import org.sonar.server.util.IntegerTypeValidation;
+import org.sonar.server.util.StringListTypeValidation;
+import org.sonar.server.util.StringTypeValidation;
+import org.sonar.server.util.TextTypeValidation;
+import org.sonar.server.util.TypeValidations;
import org.sonar.server.ws.ListingWs;
import org.sonar.server.ws.WebServiceEngine;
pico.addSingleton(IssueShowAction.class);
pico.addSingleton(org.sonar.server.issue.ws.SearchAction.class);
pico.addSingleton(org.sonar.server.issue.ws.TagsAction.class);
+ pico.addSingleton(SetTagsAction.class);
pico.addSingleton(IssueService.class);
pico.addSingleton(IssueActionsWriter.class);
pico.addSingleton(IssueQueryService.class);
*/
package org.sonar.server.issue;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multiset;
+import com.google.common.collect.Sets;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
private IssueDto newIssue() {
return IssueTesting.newDto(rule, file, project);
}
+
+ @Test
+ public void list_tags() {
+ db.issueDao().insert(session,
+ IssueTesting.newDto(rule, file, project).setTags(ImmutableSet.of("convention", "java8", "bug")),
+ IssueTesting.newDto(rule, file, project).setTags(ImmutableSet.of("convention", "bug")),
+ IssueTesting.newDto(rule, file, project),
+ IssueTesting.newDto(rule, file, project).setTags(ImmutableSet.of("convention")));
+ session.commit();
+ index();
+
+ assertThat(service.listTags(null, 5)).containsOnly("convention", "java8", "bug");
+ assertThat(service.listTags(null, 2)).containsOnly("bug", "convention");
+ assertThat(service.listTags("vent", 5)).containsOnly("convention");
+ assertThat(service.listTags(null, 1)).containsOnly("bug");
+ assertThat(service.listTags(null, Integer.MAX_VALUE)).containsOnly("convention", "java8", "bug");
+ }
+
+ @Test
+ public void set_tags() {
+ IssueDto issue = newIssue();
+ tester.get(IssueDao.class).insert(session, issue);
+
+ session.commit();
+ index();
+
+ assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).isEmpty();
+
+ // Tags are lowercased
+ service.setTags(issue.getKey(), ImmutableSet.of("bug", "Convention"));
+ assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).containsOnly("bug", "convention");
+
+ // nulls and empty tags are ignored
+ service.setTags(issue.getKey(), Sets.newHashSet("security", null, "", "convention"));
+ assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).containsOnly("security", "convention");
+
+ // tag validation
+ try {
+ service.setTags(issue.getKey(), ImmutableSet.of("pol op"));
+ } catch (Exception exception) {
+ assertThat(exception).isInstanceOf(IllegalArgumentException.class);
+ }
+ assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).containsOnly("security", "convention");
+
+ // unchanged tags
+ service.setTags(issue.getKey(), ImmutableSet.of("convention", "security"));
+ assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).containsOnly("security", "convention");
+
+ service.setTags(issue.getKey(), ImmutableSet.<String>of());
+ assertThat(indexClient.get(IssueIndex.class).getByKey(issue.getKey()).tags()).isEmpty();
+ }
}
import org.sonar.server.component.db.ComponentDao;
import org.sonar.server.db.DbClient;
import org.sonar.server.debt.DebtModelService;
-import org.sonar.server.issue.*;
+import org.sonar.server.issue.ActionService;
+import org.sonar.server.issue.IssueChangelog;
+import org.sonar.server.issue.IssueChangelogService;
+import org.sonar.server.issue.IssueCommentService;
+import org.sonar.server.issue.IssueQueryService;
+import org.sonar.server.issue.IssueService;
import org.sonar.server.issue.actionplan.ActionPlanService;
import org.sonar.server.rule.Rule;
import org.sonar.server.rule.RuleService;
new SearchAction(mock(DbClient.class), mock(IssueChangeDao.class), mock(IssueService.class), mock(IssueActionsWriter.class), mock(IssueQueryService.class),
mock(RuleService.class),
mock(ActionPlanService.class), mock(UserFinder.class), mock(I18n.class), mock(Durations.class), mock(Languages.class)),
- new TagsAction(null)
+ new TagsAction(null), new SetTagsAction(null)
));
}
package org.sonar.server.issue.ws;
import com.google.common.collect.Lists;
-
import org.junit.Before;
-import org.sonar.server.issue.IssueService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
-import org.junit.runner.RunWith;
-import org.sonar.api.server.ws.WebService.Param;
-import org.junit.Test;
import org.sonar.api.server.ws.WebService.Action;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.server.issue.IssueService;
import org.sonar.server.ws.WsTester;
+
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
tester = new WsTester(
new IssuesWs(new IssueShowAction(null, null, null, null, null, null, null, null, null, null, null),
new SearchAction(null, null, null, null, null, null, null, null, null, null,null),
- tagsAction));
+ tagsAction, new SetTagsAction(null)));
}
@Test
import org.sonar.server.issue.actionplan.ActionPlanService;
import org.sonar.server.rule.RuleService;
import org.sonar.server.ws.WsTester;
+
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
SearchAction searchAction = new SearchAction(mock(DbClient.class), mock(IssueChangeDao.class), mock(IssueService.class), mock(IssueActionsWriter.class),
mock(IssueQueryService.class), mock(RuleService.class),
mock(ActionPlanService.class), mock(UserFinder.class), mock(I18n.class), mock(Durations.class), mock(Languages.class));
- tester = new WsTester(new IssuesWs(showAction, searchAction, new TagsAction(null)));
+ tester = new WsTester(new IssuesWs(showAction, searchAction, new TagsAction(null), new SetTagsAction(null)));
}
@Test
assertThat(controller).isNotNull();
assertThat(controller.description()).isNotEmpty();
assertThat(controller.since()).isEqualTo("3.6");
- assertThat(controller.actions()).hasSize(15);
+ assertThat(controller.actions()).hasSize(16);
}
@Test
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.issue.ws;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.server.ws.WebService.Action;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.server.issue.IssueService;
+import org.sonar.server.ws.WsTester;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SetTagsActionTest {
+
+ @Mock
+ private IssueService service;
+
+ private SetTagsAction setTagsAction;
+
+ private WsTester tester;
+
+ @Before
+ public void setUp() {
+ setTagsAction = new SetTagsAction(service);
+ tester = new WsTester(
+ new IssuesWs(new IssueShowAction(null, null, null, null, null, null, null, null, null, null, null),
+ new SearchAction(null, null, null, null, null, null, null, null, null, null,null),
+ new TagsAction(null), setTagsAction));
+ }
+
+ @Test
+ public void should_define() throws Exception {
+ Action action = tester.controller("api/issues").action("set_tags");
+ assertThat(action.description()).isNotEmpty();
+ assertThat(action.responseExampleAsString()).isNull();
+ assertThat(action.isPost()).isTrue();
+ assertThat(action.isInternal()).isFalse();
+ assertThat(action.handler()).isEqualTo(setTagsAction);
+ assertThat(action.params()).hasSize(2);
+
+ Param query = action.param("key");
+ assertThat(query.isRequired()).isTrue();
+ assertThat(query.description()).isNotEmpty();
+ assertThat(query.exampleValue()).isNotEmpty();
+ Param pageSize = action.param("tags");
+ assertThat(pageSize.isRequired()).isTrue();
+ assertThat(pageSize.defaultValue()).isEqualTo("");
+ assertThat(pageSize.description()).isNotEmpty();
+ assertThat(pageSize.exampleValue()).isNotEmpty();
+ }
+
+ @Test
+ public void should_set_tags() throws Exception {
+ when(service.setTags("polop", ImmutableSet.of("palap"))).thenReturn(ImmutableSet.of("palap"));
+ tester.newPostRequest("api/issues", "set_tags").setParam("key", "polop").setParam("tags", "palap").execute()
+ .assertJson("{\"tags\":[\"palap\"]}");
+ verify(service).setTags("polop", ImmutableSet.of("palap"));
+ }
+}
*/
package org.sonar.core.issue;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.sonar.api.BatchComponent;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.DefaultIssueComment;
import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.server.rule.RuleTagFormat;
import org.sonar.api.user.User;
import org.sonar.api.utils.Duration;
import javax.annotation.Nullable;
import java.util.Calendar;
+import java.util.Collection;
import java.util.Date;
/**
public static final String AUTHOR = "author";
public static final String ACTION_PLAN = "actionPlan";
public static final String TECHNICAL_DEBT = "technicalDebt";
+ public static final String TAGS = "tags";
+
+ private static final Joiner CHANGELOG_TAG_JOINER = Joiner.on(" ").skipNulls();
public boolean setSeverity(DefaultIssue issue, String severity, IssueChangeContext context) {
if (issue.manualSeverity()) {
return setProject(issue, currentProjectKey, context);
}
+ public boolean setTags(DefaultIssue issue, Collection<String> tags, IssueChangeContext context) {
+ Collection<String> newTags = Collections2.transform(
+ Collections2.filter(tags, new Predicate<String>() {
+ @Override
+ public boolean apply(String tag) {
+ return tag != null && !tag.isEmpty();
+ }
+ }), new Function<String, String>() {
+ @Override
+ public String apply(String tag) {
+ String lowerCaseTag = tag.toLowerCase();
+ RuleTagFormat.validate(lowerCaseTag);
+ return lowerCaseTag;
+ }
+ });
+
+ Collection<String> oldTags = issue.tags();
+ if (!CollectionUtils.isEqualCollection(oldTags, newTags)) {
+ issue.setFieldChange(context, TAGS,
+ oldTags.isEmpty() ? null : CHANGELOG_TAG_JOINER.join(oldTags),
+ newTags.isEmpty() ? null : CHANGELOG_TAG_JOINER.join(newTags));
+ issue.setTags(newTags);
+ issue.setUpdateDate(context.date());
+ issue.setChanged(true);
+ issue.setSendNotifications(true);
+ return true;
+ }
+ return false;
+ }
+
}
*/
package org.sonar.core.issue.db;
+import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.issue.internal.DefaultIssue;
import javax.annotation.Nullable;
import java.io.Serializable;
+import java.util.Collection;
import java.util.Date;
/**
*/
public final class IssueDto implements Serializable {
+ private static final char TAGS_SEPARATOR = ',';
+ private static final Joiner TAGS_JOINER = Joiner.on(TAGS_SEPARATOR).skipNulls();
+ private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
+
private Long id;
private String kee;
private Long componentId;
private String projectKey;
private String projectUuid;
private String filePath;
+ private String tags;
public String getKey() {
return getKee();
return this;
}
+ public Collection<String> getTags() {
+ return ImmutableSet.copyOf(TAGS_SPLITTER.split(tags == null ? "" : tags));
+ }
+
+ public IssueDto setTags(Collection<String> tags) {
+ if (tags.isEmpty()) {
+ this.tags = null;
+ } else {
+ this.tags = TAGS_JOINER.join(tags);
+ }
+ return this;
+ }
+
+ public String getTagsString() {
+ return tags;
+ }
+
+ public IssueDto setTagsString(String tags) {
+ this.tags = tags;
+ return this;
+ }
+
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
.setAssignee(issue.assignee())
.setRuleId(ruleId)
.setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
+ .setTags(issue.tags())
.setComponentUuid(issue.componentUuid())
.setComponentId(componentId)
.setComponentKey(issue.componentKey())
.setIssueAttributes(KeyValueFormat.format(issue.attributes()))
.setAuthorLogin(issue.authorLogin())
.setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
+ .setTags(issue.tags())
.setComponentUuid(issue.componentUuid())
.setComponentKey(issue.componentKey())
.setModuleUuid(issue.moduleUuid())
issue.setProjectKey(projectKey);
issue.setManualSeverity(manualSeverity);
issue.setRuleKey(getRuleKey());
+ issue.setTags(getTags());
issue.setLanguage(language);
issue.setActionPlanKey(actionPlanKey);
issue.setAuthorLogin(authorLogin);
i.reporter as reporter,
i.assignee as assignee,
i.author_login as authorLogin,
+ i.tags as tagsString,
i.issue_attributes as issueAttributes,
i.issue_creation_date as issueCreationDate,
i.issue_update_date as issueUpdateDate,
<insert id="insert" parameterType="Issue" useGeneratedKeys="false" keyProperty="id">
INSERT INTO issues (kee, component_id, root_component_id, rule_id, action_plan_key, severity, manual_severity,
- message, line, effort_to_fix, technical_debt, status,
+ message, line, effort_to_fix, technical_debt, status, tags,
resolution, checksum, reporter, assignee, author_login, issue_attributes, issue_creation_date, issue_update_date,
issue_close_date, created_at, updated_at)
VALUES (#{kee}, #{componentId}, #{projectId}, #{ruleId}, #{actionPlanKey}, #{severity}, #{manualSeverity},
- #{message}, #{line}, #{effortToFix}, #{debt}, #{status},
+ #{message}, #{line}, #{effortToFix}, #{debt}, #{status}, #{tagsString},
#{resolution}, #{checksum}, #{reporter}, #{assignee}, #{authorLogin}, #{issueAttributes}, #{issueCreationDate},
#{issueUpdateDate}, #{issueCloseDate}, #{createdAt}, #{updatedAt})
</insert>
reporter=#{reporter},
assignee=#{assignee},
author_login=#{authorLogin},
+ tags=#{tagsString},
root_component_id=#{projectId},
issue_attributes=#{issueAttributes},
issue_creation_date=#{issueCreationDate},
reporter=#{reporter},
assignee=#{assignee},
author_login=#{authorLogin},
+ tags=#{tagsString},
root_component_id=#{projectId},
issue_attributes=#{issueAttributes},
issue_creation_date=#{issueCreationDate},
i.reporter as reporter,
i.assignee as assignee,
i.author_login as authorLogin,
+ i.tags as tagsString,
i.issue_attributes as issueAttributes,
i.issue_creation_date as issueCreationDate,
i.issue_update_date as issueUpdateDate,
issue.changelog.field.resolution=Resolution
issue.changelog.field.technicalDebt=Technical Debt
issue.changelog.field.status=Status
+issue.changelog.field.tags=Tags
#------------------------------------------------------------------------------
import javax.annotation.CheckForNull;
import java.io.Serializable;
+import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
* @since 5.0
*/
String componentUuid();
+
+ /**
+ * @since 5.1
+ */
+ Collection<String> tags();
}
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import javax.annotation.Nullable;
import java.io.Serializable;
-import java.util.*;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static com.google.common.collect.Lists.newArrayList;
private String authorLogin = null;
private String actionPlanKey;
private List<IssueComment> comments = null;
+ private Set<String> tags = null;
// FUNCTIONAL DATES
private Date creationDate;
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
+ @Override
+ public Collection<String> tags() {
+ if (tags == null) {
+ return ImmutableSet.of();
+ } else {
+ return ImmutableSet.copyOf(tags);
+ }
+ }
+
+ public DefaultIssue setTags(Collection<String> tags) {
+ this.tags = ImmutableSet.copyOf(tags);
+ return this;
+ }
}