Co-authored-by: Lukasz Jarocki <lukasz.jarocki@sonarsource.com> Co-authored-by: Philippe Perrin <philippe.perrin@sonarsource.com> Co-authored-by: MikeBirnstiehl <michael.birnstiehl@sonarsource.com>tags/9.1.0.47736
package org.sonar.db.component; | package org.sonar.db.component; | ||||
import com.google.common.annotations.VisibleForTesting; | import com.google.common.annotations.VisibleForTesting; | ||||
import com.google.common.collect.Lists; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.HashMap; | |||||
import java.util.HashSet; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | |||||
import java.util.Objects; | import java.util.Objects; | ||||
import java.util.Set; | |||||
import java.util.function.BiConsumer; | import java.util.function.BiConsumer; | ||||
import java.util.function.Function; | |||||
import java.util.function.Predicate; | |||||
import java.util.stream.Collectors; | |||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.apache.commons.lang.StringUtils; | import org.apache.commons.lang.StringUtils; | ||||
import org.sonar.api.resources.Qualifiers; | import org.sonar.api.resources.Qualifiers; | ||||
import org.sonar.db.audit.AuditPersister; | import org.sonar.db.audit.AuditPersister; | ||||
import org.sonar.db.audit.model.ComponentKeyNewValue; | import org.sonar.db.audit.model.ComponentKeyNewValue; | ||||
import static org.sonar.core.component.ComponentKeys.checkProjectKey; | |||||
import static org.sonar.db.component.ComponentDto.BRANCH_KEY_SEPARATOR; | import static org.sonar.db.component.ComponentDto.BRANCH_KEY_SEPARATOR; | ||||
import static org.sonar.db.component.ComponentDto.generateBranchKey; | import static org.sonar.db.component.ComponentDto.generateBranchKey; | ||||
} | } | ||||
} | } | ||||
/** | |||||
* | |||||
* @return a map with currentKey/newKey is a bulk update was executed | |||||
*/ | |||||
public Map<String, String> simulateBulkUpdateKey(DbSession dbSession, String projectUuid, String stringToReplace, String replacementString) { | |||||
return collectAllModules(projectUuid, stringToReplace, mapper(dbSession), false) | |||||
.stream() | |||||
.collect(Collectors.toMap( | |||||
ResourceDto::getKey, | |||||
component -> { | |||||
String newKey = computeNewKey(component.getKey(), stringToReplace, replacementString); | |||||
checkProjectKey(newKey); | |||||
return newKey; | |||||
})); | |||||
} | |||||
/** | |||||
* @return a map with the component key as key, and boolean as true if key already exists in db | |||||
*/ | |||||
public Map<String, Boolean> checkComponentKeys(DbSession dbSession, List<String> newComponentKeys) { | |||||
return newComponentKeys.stream().collect(Collectors.toMap(Function.identity(), key -> mapper(dbSession).countResourceByKey(key) > 0)); | |||||
} | |||||
@VisibleForTesting | @VisibleForTesting | ||||
static String computeNewKey(String key, String stringToReplace, String replacementString) { | static String computeNewKey(String key, String stringToReplace, String replacementString) { | ||||
return key.replace(stringToReplace, replacementString); | return key.replace(stringToReplace, replacementString); | ||||
} | } | ||||
public Set<RekeyedResource> bulkUpdateKey(DbSession session, String projectUuid, String stringToReplace, String replacementString, | |||||
Predicate<RekeyedResource> rekeyedResourceFilter) { | |||||
ComponentKeyUpdaterMapper mapper = session.getMapper(ComponentKeyUpdaterMapper.class); | |||||
// must SELECT first everything | |||||
Set<ResourceDto> modules = collectAllModules(projectUuid, stringToReplace, mapper, true); | |||||
checkNewNameOfAllModules(modules, stringToReplace, replacementString, mapper); | |||||
// add branches (no check should be done as branch keys cannot be changed by the user) | |||||
Map<String, String> branchBaseKeys = new HashMap<>(); | |||||
session.getMapper(BranchMapper.class).selectByProjectUuid(projectUuid).stream() | |||||
.filter(branch -> !projectUuid.equals(branch.getUuid())) | |||||
.forEach(branch -> { | |||||
Set<ResourceDto> branchModules = collectAllModules(branch.getUuid(), stringToReplace, mapper, true); | |||||
modules.addAll(branchModules); | |||||
branchModules.forEach(module -> branchBaseKeys.put(module.getKey(), branchBaseKey(module.getKey()))); | |||||
}); | |||||
Map<ResourceDto, List<ResourceDto>> allResourcesByModuleMap = new HashMap<>(); | |||||
for (ResourceDto module : modules) { | |||||
allResourcesByModuleMap.put(module, mapper.selectProjectResources(module.getUuid())); | |||||
} | |||||
Set<RekeyedResource> rekeyedResources = new HashSet<>(); | |||||
// and then proceed with the batch UPDATE at once | |||||
for (ResourceDto module : modules) { | |||||
String oldModuleKey = module.getKey(); | |||||
oldModuleKey = branchBaseKeys.getOrDefault(oldModuleKey, oldModuleKey); | |||||
String newModuleKey = computeNewKey(oldModuleKey, stringToReplace, replacementString); | |||||
Collection<ResourceDto> resources = Lists.newArrayList(module); | |||||
resources.addAll(allResourcesByModuleMap.get(module)); | |||||
runBatchUpdateForAllResources(resources, oldModuleKey, newModuleKey, mapper, | |||||
(resource, oldKey) -> { | |||||
RekeyedResource rekeyedResource = new RekeyedResource(resource, oldKey); | |||||
if (rekeyedResourceFilter.test(rekeyedResource)) { | |||||
rekeyedResources.add(rekeyedResource); | |||||
} | |||||
}, session); | |||||
} | |||||
return rekeyedResources; | |||||
} | |||||
private static String branchBaseKey(String key) { | |||||
int index = key.lastIndexOf(ComponentDto.BRANCH_KEY_SEPARATOR); | |||||
if (index > -1) { | |||||
return key.substring(0, index); | |||||
} | |||||
index = key.lastIndexOf(ComponentDto.PULL_REQUEST_SEPARATOR); | |||||
if (index > -1) { | |||||
return key.substring(0, index); | |||||
} | |||||
return key; | |||||
} | |||||
private void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldKey, String newKey, ComponentKeyUpdaterMapper mapper, | private void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldKey, String newKey, ComponentKeyUpdaterMapper mapper, | ||||
@Nullable BiConsumer<ResourceDto, String> consumer, DbSession dbSession) { | @Nullable BiConsumer<ResourceDto, String> consumer, DbSession dbSession) { | ||||
for (ResourceDto resource : resources) { | for (ResourceDto resource : resources) { | ||||
} | } | ||||
} | } | ||||
private static Set<ResourceDto> collectAllModules(String projectUuid, String stringToReplace, ComponentKeyUpdaterMapper mapper, boolean includeDisabled) { | |||||
ResourceDto project = mapper.selectProjectByUuid(projectUuid); | |||||
Set<ResourceDto> modules = new HashSet<>(); | |||||
if (project.getKey().contains(stringToReplace) && (project.isEnabled() || includeDisabled)) { | |||||
modules.add(project); | |||||
} | |||||
for (ResourceDto submodule : mapper.selectDescendantProjects(projectUuid)) { | |||||
modules.addAll(collectAllModules(submodule.getUuid(), stringToReplace, mapper, includeDisabled)); | |||||
} | |||||
return modules; | |||||
} | |||||
private static void checkNewNameOfAllModules(Set<ResourceDto> modules, String stringToReplace, String replacementString, ComponentKeyUpdaterMapper mapper) { | |||||
for (ResourceDto module : modules) { | |||||
String newKey = computeNewKey(module.getKey(), stringToReplace, replacementString); | |||||
checkProjectKey(newKey); | |||||
checkExistentKey(mapper, newKey); | |||||
} | |||||
} | |||||
private static ComponentKeyUpdaterMapper mapper(DbSession dbSession) { | private static ComponentKeyUpdaterMapper mapper(DbSession dbSession) { | ||||
return dbSession.getMapper(ComponentKeyUpdaterMapper.class); | return dbSession.getMapper(ComponentKeyUpdaterMapper.class); | ||||
} | } |
package org.sonar.db.source; | package org.sonar.db.source; | ||||
import com.google.common.base.Splitter; | import com.google.common.base.Splitter; | ||||
import java.io.Reader; | |||||
import java.sql.Connection; | import java.sql.Connection; | ||||
import java.sql.PreparedStatement; | import java.sql.PreparedStatement; | ||||
import java.sql.ResultSet; | import java.sql.ResultSet; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.function.Consumer; | |||||
import javax.annotation.CheckForNull; | import javax.annotation.CheckForNull; | ||||
import org.apache.commons.io.IOUtils; | |||||
import org.apache.ibatis.session.ResultHandler; | import org.apache.ibatis.session.ResultHandler; | ||||
import org.sonar.db.Dao; | import org.sonar.db.Dao; | ||||
import org.sonar.db.DatabaseUtils; | import org.sonar.db.DatabaseUtils; | ||||
} | } | ||||
} | } | ||||
public void readLineHashesStream(DbSession dbSession, String fileUuid, Consumer<Reader> consumer) { | |||||
Connection connection = dbSession.getConnection(); | |||||
PreparedStatement pstmt = null; | |||||
ResultSet rs = null; | |||||
Reader reader = null; | |||||
try { | |||||
pstmt = connection.prepareStatement("SELECT line_hashes FROM file_sources WHERE file_uuid=?"); | |||||
pstmt.setString(1, fileUuid); | |||||
rs = pstmt.executeQuery(); | |||||
if (rs.next()) { | |||||
reader = rs.getCharacterStream(1); | |||||
if (reader != null) { | |||||
consumer.accept(reader); | |||||
} | |||||
} | |||||
} catch (SQLException e) { | |||||
throw new IllegalStateException("Fail to read FILE_SOURCES.LINE_HASHES of file " + fileUuid, e); | |||||
} finally { | |||||
IOUtils.closeQuietly(reader); | |||||
DatabaseUtils.closeQuietly(rs); | |||||
DatabaseUtils.closeQuietly(pstmt); | |||||
DatabaseUtils.closeQuietly(connection); | |||||
} | |||||
} | |||||
public void insert(DbSession session, FileSourceDto dto) { | public void insert(DbSession session, FileSourceDto dto) { | ||||
mapper(session).insert(dto); | mapper(session).insert(dto); | ||||
} | } |
import com.google.common.base.Strings; | import com.google.common.base.Strings; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | |||||
import java.util.function.Predicate; | import java.util.function.Predicate; | ||||
import org.assertj.core.groups.Tuple; | import org.assertj.core.groups.Tuple; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.sonar.db.audit.model.ComponentKeyNewValue; | import org.sonar.db.audit.model.ComponentKeyNewValue; | ||||
import org.sonar.db.component.ComponentKeyUpdaterDao.RekeyedResource; | import org.sonar.db.component.ComponentKeyUpdaterDao.RekeyedResource; | ||||
import static com.google.common.collect.Lists.newArrayList; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.entry; | |||||
import static org.mockito.ArgumentMatchers.any; | import static org.mockito.ArgumentMatchers.any; | ||||
import static org.mockito.ArgumentMatchers.anyString; | import static org.mockito.ArgumentMatchers.anyString; | ||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
.forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); | .forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); | ||||
} | } | ||||
@Test | |||||
public void bulk_updateKey_updates_branches_too() { | |||||
ComponentDto project = db.components().insertPublicProject(); | |||||
ComponentDto branch = db.components().insertProjectBranch(project); | |||||
ComponentDto module = db.components().insertComponent(prefixDbKeyWithKey(newModuleDto(branch), project.getKey())); | |||||
ComponentDto file1 = db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey())); | |||||
ComponentDto file2 = db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey())); | |||||
int branchComponentCount = 4; | |||||
String oldProjectKey = project.getKey(); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).hasSize(1); | |||||
String oldBranchKey = branch.getDbKey(); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldBranchKey)).hasSize(branchComponentCount); | |||||
String newProjectKey = "newKey"; | |||||
Set<RekeyedResource> rekeyedResources = underTest.bulkUpdateKey(dbSession, project.uuid(), oldProjectKey, newProjectKey, a -> true); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).isEmpty(); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldBranchKey)).isEmpty(); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newProjectKey)).hasSize(1); | |||||
String newBranchKey = ComponentDto.generateBranchKey(newProjectKey, branch.getBranch()); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newBranchKey)).hasSize(branchComponentCount); | |||||
db.select(dbSession, "select kee from components") | |||||
.forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); | |||||
assertThat(rekeyedResources) | |||||
.extracting(t -> t.getResource().getUuid()) | |||||
.containsOnly(project.uuid(), branch.uuid(), module.uuid(), file1.uuid(), file2.uuid()); | |||||
assertThat(rekeyedResources) | |||||
.extracting(t -> t.getResource().getKey()) | |||||
.allMatch(t -> t.startsWith(newProjectKey)); | |||||
assertThat(rekeyedResources) | |||||
.extracting(RekeyedResource::getOldKey) | |||||
.allMatch(t -> t.startsWith(oldProjectKey)); | |||||
} | |||||
@Test | |||||
public void bulk_updateKey_on_branch_containing_slash() { | |||||
ComponentDto project = db.components().insertPublicProject(); | |||||
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("branch/with/slash")); | |||||
String newKey = "newKey"; | |||||
underTest.bulkUpdateKey(dbSession, project.uuid(), project.getKey(), newKey, t -> true); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newKey)).hasSize(1); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, ComponentDto.generateBranchKey(newKey, branch.getBranch()))).hasSize(1); | |||||
} | |||||
@Test | |||||
public void bulk_updateKey_updates_pull_requests_too() { | |||||
ComponentDto project = db.components().insertPublicProject(); | |||||
ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST)); | |||||
ComponentDto module = db.components().insertComponent(prefixDbKeyWithKey(newModuleDto(pullRequest), project.getKey())); | |||||
ComponentDto file1 = db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey())); | |||||
ComponentDto file2 = db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey())); | |||||
int branchComponentCount = 4; | |||||
String oldProjectKey = project.getKey(); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).hasSize(1); | |||||
String oldPullRequestKey = pullRequest.getDbKey(); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldPullRequestKey)).hasSize(branchComponentCount); | |||||
String newProjectKey = "newKey"; | |||||
String newPullRequestKey = ComponentDto.generatePullRequestKey(newProjectKey, pullRequest.getPullRequest()); | |||||
Set<RekeyedResource> rekeyedResources = underTest.bulkUpdateKey(dbSession, project.uuid(), oldProjectKey, newProjectKey, t -> true); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).isEmpty(); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldPullRequestKey)).isEmpty(); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newProjectKey)).hasSize(1); | |||||
assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newPullRequestKey)).hasSize(branchComponentCount); | |||||
db.select(dbSession, "select kee from components") | |||||
.forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey))); | |||||
assertThat(rekeyedResources) | |||||
.extracting(t -> t.getResource().getUuid()) | |||||
.containsOnly(project.uuid(), pullRequest.uuid(), module.uuid(), file1.uuid(), file2.uuid()); | |||||
assertThat(rekeyedResources) | |||||
.extracting(t -> t.getResource().getKey()) | |||||
.allMatch(t -> t.startsWith(newProjectKey)); | |||||
assertThat(rekeyedResources) | |||||
.extracting(RekeyedResource::getOldKey) | |||||
.allMatch(t -> t.startsWith(oldProjectKey)); | |||||
} | |||||
private ComponentDto prefixDbKeyWithKey(ComponentDto componentDto, String key) { | private ComponentDto prefixDbKeyWithKey(ComponentDto componentDto, String key) { | ||||
return componentDto.setDbKey(key + ":" + componentDto.getDbKey()); | return componentDto.setDbKey(key + ":" + componentDto.getDbKey()); | ||||
} | } | ||||
underTest.updateKey(dbSession, "B", "org.struts:struts-ui"); | underTest.updateKey(dbSession, "B", "org.struts:struts-ui"); | ||||
} | } | ||||
@Test | |||||
public void bulk_update_key_updates_disabled_components() { | |||||
ComponentDto project = db.components().insertComponent(newPrivateProjectDto("A").setDbKey("my_project")); | |||||
db.components().insertComponent(newModuleDto(project).setDbKey("my_project:module")); | |||||
db.components().insertComponent(newModuleDto(project).setDbKey("my_project:inactive_module").setEnabled(false)); | |||||
Set<RekeyedResource> rekeyedResources = underTestWithAuditPersister.bulkUpdateKey(dbSession, "A", "my_", "your_", doNotReturnAnyRekeyedResource()); | |||||
List<ComponentDto> result = dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, "your_project"); | |||||
assertThat(result) | |||||
.hasSize(3) | |||||
.extracting(ComponentDto::getDbKey) | |||||
.containsOnlyOnce("your_project", "your_project:module", "your_project:inactive_module"); | |||||
} | |||||
@Test | |||||
public void shouldBulkUpdateKey() { | |||||
populateSomeData(); | |||||
underTest.bulkUpdateKey(dbSession, "A", "org.struts", "org.apache.struts", doNotReturnAnyRekeyedResource()); | |||||
dbSession.commit(); | |||||
assertThat(db.select("select uuid as \"UUID\", kee as \"KEE\" from components")) | |||||
.extracting(t -> t.get("UUID"), t -> t.get("KEE")) | |||||
.containsOnly( | |||||
Tuple.tuple("A", "org.apache.struts:struts"), | |||||
Tuple.tuple("B", "org.apache.struts:struts-core"), | |||||
Tuple.tuple("C", "org.apache.struts:struts-core:/src/org/struts"), | |||||
Tuple.tuple("D", "org.apache.struts:struts-core:/src/org/struts/RequestContext.java"), | |||||
Tuple.tuple("E", "org.apache.struts:struts-ui"), | |||||
Tuple.tuple("F", "org.apache.struts:struts-ui:/src/org/struts"), | |||||
Tuple.tuple("G", "org.apache.struts:struts-ui:/src/org/struts/RequestContext.java"), | |||||
Tuple.tuple("H", "foo:struts-core")); | |||||
} | |||||
@Test | |||||
public void shouldBulkUpdateKeyOnOnlyOneSubmodule() { | |||||
populateSomeData(); | |||||
underTest.bulkUpdateKey(dbSession, "A", "struts-ui", "struts-web", doNotReturnAnyRekeyedResource()); | |||||
dbSession.commit(); | |||||
assertThat(db.select("select uuid as \"UUID\", kee as \"KEE\" from components")) | |||||
.extracting(t -> t.get("UUID"), t -> t.get("KEE")) | |||||
.containsOnly( | |||||
Tuple.tuple("A", "org.struts:struts"), | |||||
Tuple.tuple("B", "org.struts:struts-core"), | |||||
Tuple.tuple("C", "org.struts:struts-core:/src/org/struts"), | |||||
Tuple.tuple("D", "org.struts:struts-core:/src/org/struts/RequestContext.java"), | |||||
Tuple.tuple("E", "org.struts:struts-web"), | |||||
Tuple.tuple("F", "org.struts:struts-web:/src/org/struts"), | |||||
Tuple.tuple("G", "org.struts:struts-web:/src/org/struts/RequestContext.java"), | |||||
Tuple.tuple("H", "foo:struts-core")); | |||||
} | |||||
@Test | |||||
public void shouldFailBulkUpdateKeyIfKeyAlreadyExist() { | |||||
populateSomeData(); | |||||
thrown.expect(IllegalArgumentException.class); | |||||
thrown.expectMessage("Impossible to update key: a component with key \"foo:struts-core\" already exists."); | |||||
underTest.bulkUpdateKey(dbSession, "A", "org.struts", "foo", doNotReturnAnyRekeyedResource()); | |||||
dbSession.commit(); | |||||
} | |||||
@Test | |||||
public void shouldNotUpdateAllSubmodules() { | |||||
ComponentDto project1 = db.components().insertPrivateProject(t1 -> t1.setDbKey("org.struts:struts").setUuid("A")); | |||||
ComponentDto module1 = db.components().insertComponent(newModuleDto(project1).setDbKey("org.struts:struts-core").setUuid("B")); | |||||
ComponentDto directory1 = db.components().insertComponent(newDirectory(module1, "/src/org/struts").setUuid("C")); | |||||
db.components().insertComponent(ComponentTesting.newFileDto(module1, directory1).setDbKey("org.struts:struts-core:/src/org/struts/RequestContext.java").setUuid("D")); | |||||
ComponentDto module2 = db.components().insertComponent(newModuleDto(project1).setDbKey("foo:struts-ui").setUuid("E")); | |||||
ComponentDto directory2 = db.components().insertComponent(newDirectory(module2, "/src/org/struts").setUuid("F")); | |||||
db.components().insertComponent(ComponentTesting.newFileDto(module2, directory2).setDbKey("foo:struts-ui:/src/org/struts/RequestContext.java").setUuid("G")); | |||||
ComponentDto project2 = db.components().insertPublicProject(t1 -> t1.setDbKey("foo:struts-core").setUuid("H")); | |||||
underTest.bulkUpdateKey(dbSession, "A", "org.struts", "org.apache.struts", doNotReturnAnyRekeyedResource()); | |||||
dbSession.commit(); | |||||
assertThat(db.select("select uuid as \"UUID\", kee as \"KEE\" from components")) | |||||
.extracting(t -> t.get("UUID"), t -> t.get("KEE")) | |||||
.containsOnly( | |||||
Tuple.tuple("A", "org.apache.struts:struts"), | |||||
Tuple.tuple("B", "org.apache.struts:struts-core"), | |||||
Tuple.tuple("C", "org.apache.struts:struts-core:/src/org/struts"), | |||||
Tuple.tuple("D", "org.apache.struts:struts-core:/src/org/struts/RequestContext.java"), | |||||
Tuple.tuple("E", "foo:struts-ui"), | |||||
Tuple.tuple("F", "foo:struts-ui:/src/org/struts"), | |||||
Tuple.tuple("G", "foo:struts-ui:/src/org/struts/RequestContext.java"), | |||||
Tuple.tuple("H", "foo:struts-core")); | |||||
} | |||||
@Test | @Test | ||||
public void updateKey_throws_IAE_when_sub_component_key_is_too_long() { | public void updateKey_throws_IAE_when_sub_component_key_is_too_long() { | ||||
ComponentDto project = newPrivateProjectDto("project-uuid").setDbKey("old-project-key"); | ComponentDto project = newPrivateProjectDto("project-uuid").setDbKey("old-project-key"); | ||||
underTest.updateKey(dbSession, project.uuid(), newLongProjectKey); | underTest.updateKey(dbSession, project.uuid(), newLongProjectKey); | ||||
} | } | ||||
@Test | |||||
public void fail_when_new_key_is_invalid() { | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
thrown.expect(IllegalArgumentException.class); | |||||
thrown.expectMessage("Malformed key for 'my?project?key'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit."); | |||||
underTest.bulkUpdateKey(dbSession, project.uuid(), project.getDbKey(), "my?project?key", doNotReturnAnyRekeyedResource()); | |||||
} | |||||
@Test | |||||
public void check_component_keys() { | |||||
populateSomeData(); | |||||
Map<String, Boolean> result = underTest.checkComponentKeys(dbSession, newArrayList("foo:struts", "foo:struts-core", "foo:struts-ui")); | |||||
assertThat(result) | |||||
.hasSize(3) | |||||
.containsOnly(entry("foo:struts", false), entry("foo:struts-core", true), entry("foo:struts-ui", false)); | |||||
} | |||||
@Test | |||||
public void check_component_keys_checks_inactive_components() { | |||||
db.components().insertComponent(ComponentTesting.newPrivateProjectDto().setDbKey("my-project")); | |||||
db.components().insertComponent(ComponentTesting.newPrivateProjectDto().setDbKey("your-project").setEnabled(false)); | |||||
Map<String, Boolean> result = underTest.checkComponentKeys(dbSession, newArrayList("my-project", "your-project", "new-project")); | |||||
assertThat(result) | |||||
.hasSize(3) | |||||
.containsOnly(entry("my-project", true), entry("your-project", true), entry("new-project", false)); | |||||
} | |||||
@Test | |||||
public void simulate_bulk_update_key() { | |||||
populateSomeData(); | |||||
Map<String, String> result = underTest.simulateBulkUpdateKey(dbSession, "A", "org.struts", "foo"); | |||||
assertThat(result) | |||||
.hasSize(3) | |||||
.containsOnly(entry("org.struts:struts", "foo:struts"), entry("org.struts:struts-core", "foo:struts-core"), entry("org.struts:struts-ui", "foo:struts-ui")); | |||||
} | |||||
@Test | |||||
public void simulate_bulk_update_key_does_not_return_disable_components() { | |||||
ComponentDto project = db.components().insertComponent(newPrivateProjectDto("A").setDbKey("project")); | |||||
db.components().insertComponent(newModuleDto(project).setDbKey("project:enabled-module")); | |||||
db.components().insertComponent(newModuleDto(project).setDbKey("project:disabled-module").setEnabled(false)); | |||||
db.components().insertComponent(newPrivateProjectDto("D").setDbKey("other-project")); | |||||
Map<String, String> result = underTest.simulateBulkUpdateKey(dbSession, "A", "project", "new-project"); | |||||
assertThat(result).containsOnly( | |||||
entry("project", "new-project"), | |||||
entry("project:enabled-module", "new-project:enabled-module")); | |||||
} | |||||
@Test | |||||
public void simulate_bulk_update_key_fails_if_invalid_componentKey() { | |||||
ComponentDto project = db.components().insertComponent(newPrivateProjectDto("A").setDbKey("project")); | |||||
db.components().insertComponent(newModuleDto(project).setDbKey("project:enabled-module")); | |||||
db.components().insertComponent(newModuleDto(project).setDbKey("project:disabled-module").setEnabled(false)); | |||||
thrown.expect(IllegalArgumentException.class); | |||||
underTest.simulateBulkUpdateKey(dbSession, "A", "project", "project?"); | |||||
} | |||||
@Test | @Test | ||||
public void compute_new_key() { | public void compute_new_key() { | ||||
assertThat(computeNewKey("my_project", "my_", "your_")).isEqualTo("your_project"); | assertThat(computeNewKey("my_project", "my_", "your_")).isEqualTo("your_project"); | ||||
.componentKeyUpdate(any(DbSession.class), any(ComponentKeyNewValue.class), anyString()); | .componentKeyUpdate(any(DbSession.class), any(ComponentKeyNewValue.class), anyString()); | ||||
} | } | ||||
@Test | |||||
public void bulkUpdate_callsAuditPersister() { | |||||
ComponentDto project = db.components().insertComponent(newPrivateProjectDto("A").setDbKey("project")); | |||||
db.components().insertComponent(newModuleDto(project).setDbKey("project:enabled-module")); | |||||
db.components().insertComponent(newModuleDto(project).setDbKey("project:disabled-module").setEnabled(false)); | |||||
thrown.expect(IllegalArgumentException.class); | |||||
underTest.simulateBulkUpdateKey(dbSession, "A", "project", "project?"); | |||||
verify(auditPersister, times(1)) | |||||
.componentKeyUpdate(any(DbSession.class), any(ComponentKeyNewValue.class), anyString()); | |||||
} | |||||
private Predicate<RekeyedResource> doNotReturnAnyRekeyedResource() { | private Predicate<RekeyedResource> doNotReturnAnyRekeyedResource() { | ||||
return a -> false; | return a -> false; |
import com.google.common.collect.ImmutableList; | import com.google.common.collect.ImmutableList; | ||||
import com.google.common.collect.ImmutableSet; | import com.google.common.collect.ImmutableSet; | ||||
import java.io.IOException; | |||||
import java.io.Reader; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.Random; | import java.util.Random; | ||||
import java.util.function.Consumer; | |||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import java.util.stream.IntStream; | import java.util.stream.IntStream; | ||||
import javax.annotation.Nullable; | |||||
import org.apache.commons.io.IOUtils; | |||||
import org.apache.ibatis.session.ResultContext; | import org.apache.ibatis.session.ResultContext; | ||||
import org.apache.ibatis.session.ResultHandler; | import org.apache.ibatis.session.ResultHandler; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
assertThat(fileSourceDto.getLineHashes()).isEqualTo(expected.getLineHashes()); | assertThat(fileSourceDto.getLineHashes()).isEqualTo(expected.getLineHashes()); | ||||
} | } | ||||
@Test | |||||
public void select_line_hashes() { | |||||
ComponentDto project = dbTester.components().insertPrivateProject(); | |||||
ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); | |||||
FileSourceDto expected = dbTester.fileSources().insertFileSource(file); | |||||
ReaderToStringConsumer fn = new ReaderToStringConsumer(); | |||||
underTest.readLineHashesStream(dbSession, expected.getFileUuid(), fn); | |||||
assertThat(fn.result).isEqualTo(expected.getLineHashes().isEmpty() ? null : String.join("\n", expected.getLineHashes())); | |||||
} | |||||
@Test | |||||
public void no_line_hashes_on_unknown_file() { | |||||
ComponentDto project = dbTester.components().insertPrivateProject(); | |||||
ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); | |||||
dbTester.fileSources().insertFileSource(file); | |||||
ReaderToStringConsumer fn = new ReaderToStringConsumer(); | |||||
underTest.readLineHashesStream(dbSession, "unknown", fn); | |||||
assertThat(fn.result).isNull(); | |||||
} | |||||
@Test | @Test | ||||
public void insert() { | public void insert() { | ||||
FileSourceDto expected = new FileSourceDto() | FileSourceDto expected = new FileSourceDto() | ||||
assertThat(underTest.selectLineHashesVersion(dbSession, "FILE2_UUID")).isEqualTo(LineHashVersion.WITH_SIGNIFICANT_CODE); | assertThat(underTest.selectLineHashesVersion(dbSession, "FILE2_UUID")).isEqualTo(LineHashVersion.WITH_SIGNIFICANT_CODE); | ||||
} | } | ||||
@Test | |||||
public void readLineHashesStream_does_not_fail_when_lineshashes_is_null() { | |||||
underTest.insert(dbSession, new FileSourceDto() | |||||
.setUuid(Uuids.createFast()) | |||||
.setProjectUuid("PRJ_UUID") | |||||
.setFileUuid("FILE2_UUID") | |||||
.setBinaryData("FILE2_BINARY_DATA".getBytes()) | |||||
.setDataHash("FILE2_DATA_HASH") | |||||
.setSrcHash("FILE2_HASH") | |||||
.setCreatedAt(1500000000000L) | |||||
.setUpdatedAt(1500000000001L) | |||||
.setRevision("123456789")); | |||||
dbSession.commit(); | |||||
boolean[] flag = {false}; | |||||
underTest.readLineHashesStream(dbSession, "FILE2_UUID", new Consumer<Reader>() { | |||||
@Override | |||||
public void accept(@Nullable Reader input) { | |||||
fail("function must never been called since there is no data to read"); | |||||
flag[0] = true; | |||||
} | |||||
}); | |||||
assertThat(flag[0]).isFalse(); | |||||
} | |||||
@Test | @Test | ||||
public void scrollLineHashes_has_no_effect_if_no_uuids() { | public void scrollLineHashes_has_no_effect_if_no_uuids() { | ||||
underTest.scrollLineHashes(dbSession, emptySet(), resultContext -> fail("handler should not be called")); | underTest.scrollLineHashes(dbSession, emptySet(), resultContext -> fail("handler should not be called")); | ||||
assertThat(res.getLineHashes()).isEmpty(); | assertThat(res.getLineHashes()).isEmpty(); | ||||
assertThat(res.getLineCount()).isEqualTo(1); | assertThat(res.getLineCount()).isEqualTo(1); | ||||
} | } | ||||
private static class ReaderToStringConsumer implements Consumer<Reader> { | |||||
String result = null; | |||||
@Override | |||||
public void accept(Reader input) { | |||||
try { | |||||
result = IOUtils.toString(input); | |||||
} catch (IOException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
} | |||||
} | } |
| * api/system/status | | * api/system/status | ||||
| * api/system/upgrades | | * api/system/upgrades | ||||
| * api/users/search | | * api/users/search | ||||
| * api/views/run | |||||
| * api/webservices/list | | * api/webservices/list | ||||
| * api/webservices/response_example | | * api/webservices/response_example | ||||
## Release 9.1 Upgrade Notes | ## Release 9.1 Upgrade Notes | ||||
**Custom measures feature has been dropped** | **Custom measures feature has been dropped** | ||||
The custom measures feature, which was previously deprecated, has been removed. ([SONAR-10762)[https://jira.sonarsource.com/browse/SONAR-10762]). | |||||
The custom measures feature, which was previously deprecated, has been removed. ([SONAR-10762](https://jira.sonarsource.com/browse/SONAR-10762)). | |||||
**Deprecated WebAPI endpoints and parameters removal** | |||||
The WebAPI endpoints and parameters deprecated during the 7.X release cycle have been removed. For a complete list of removed endpoints and parameters see [SONAR-15313](https://jira.sonarsource.com/browse/SONAR-15313). | |||||
## Release 9.0 Upgrade Notes | ## Release 9.0 Upgrade Notes | ||||
**Scanners require Java 11** | **Scanners require Java 11** |
private List<String> issues; | private List<String> issues; | ||||
private Set<String> scopes; | private Set<String> scopes; | ||||
private List<String> languages; | private List<String> languages; | ||||
private List<String> moduleUuids; | |||||
private Boolean onComponentOnly; | private Boolean onComponentOnly; | ||||
private String branch; | private String branch; | ||||
private String pullRequest; | private String pullRequest; | ||||
return this; | return this; | ||||
} | } | ||||
@CheckForNull | |||||
public List<String> getModuleUuids() { | |||||
return moduleUuids; | |||||
} | |||||
public SearchRequest setModuleUuids(@Nullable List<String> moduleUuids) { | |||||
this.moduleUuids = moduleUuids; | |||||
return this; | |||||
} | |||||
@CheckForNull | @CheckForNull | ||||
public Boolean getOnComponentOnly() { | public Boolean getOnComponentOnly() { | ||||
return onComponentOnly; | return onComponentOnly; |
.setResolutions(singletonList("FALSE-POSITIVE")) | .setResolutions(singletonList("FALSE-POSITIVE")) | ||||
.setResolved(true) | .setResolved(true) | ||||
.setProjects(singletonList("project-a")) | .setProjects(singletonList("project-a")) | ||||
.setModuleUuids(singletonList("module-a")) | |||||
.setDirectories(singletonList("aDirPath")) | .setDirectories(singletonList("aDirPath")) | ||||
.setFiles(asList("file-a", "file-b")) | .setFiles(asList("file-a", "file-b")) | ||||
.setAssigneesUuid(asList("user-a", "user-b")) | .setAssigneesUuid(asList("user-a", "user-b")) | ||||
assertThat(underTest.getResolutions()).containsExactly("FALSE-POSITIVE"); | assertThat(underTest.getResolutions()).containsExactly("FALSE-POSITIVE"); | ||||
assertThat(underTest.getResolved()).isTrue(); | assertThat(underTest.getResolved()).isTrue(); | ||||
assertThat(underTest.getProjects()).containsExactly("project-a"); | assertThat(underTest.getProjects()).containsExactly("project-a"); | ||||
assertThat(underTest.getModuleUuids()).containsExactly("module-a"); | |||||
assertThat(underTest.getDirectories()).containsExactly("aDirPath"); | assertThat(underTest.getDirectories()).containsExactly("aDirPath"); | ||||
assertThat(underTest.getFiles()).containsExactly("file-a", "file-b"); | assertThat(underTest.getFiles()).containsExactly("file-a", "file-b"); | ||||
assertThat(underTest.getAssigneeUuids()).containsExactly("user-a", "user-b"); | assertThat(underTest.getAssigneeUuids()).containsExactly("user-a", "user-b"); |
type FacetName = | type FacetName = | ||||
| 'assigned_to_me' | | 'assigned_to_me' | ||||
| 'assignees' | | 'assignees' | ||||
| 'authors' | |||||
| 'author' | |||||
| 'createdAt' | | 'createdAt' | ||||
| 'cwe' | | 'cwe' | ||||
| 'directories' | | 'directories' | ||||
| 'files' | | 'files' | ||||
| 'languages' | | 'languages' | ||||
| 'modules' | |||||
| 'owaspTop10' | | 'owaspTop10' | ||||
| 'projects' | | 'projects' | ||||
| 'reporters' | | 'reporters' |
areMyIssuesSelected, | areMyIssuesSelected, | ||||
areQueriesEqual, | areQueriesEqual, | ||||
getOpen, | getOpen, | ||||
mapFacet, | |||||
parseFacets, | parseFacets, | ||||
parseQuery, | parseQuery, | ||||
Query, | Query, | ||||
const facets = requestFacets | const facets = requestFacets | ||||
? Object.keys(openFacets) | ? Object.keys(openFacets) | ||||
.filter(facet => facet !== STANDARDS) | .filter(facet => facet !== STANDARDS) | ||||
.map(mapFacet) | |||||
.join(',') | .join(',') | ||||
: undefined; | : undefined; | ||||
}; | }; | ||||
fetchFacet = (facet: string) => { | fetchFacet = (facet: string) => { | ||||
return this.fetchIssues({ ps: 1, facets: mapFacet(facet) }, false).then( | |||||
return this.fetchIssues({ ps: 1, facets: facet }, false).then( | |||||
({ facets, ...other }) => { | ({ facets, ...other }) => { | ||||
if (this.mounted) { | if (this.mounted) { | ||||
this.setState(state => ({ | this.setState(state => ({ | ||||
const parameters = { | const parameters = { | ||||
...getBranchLikeQuery(this.props.branchLike), | ...getBranchLikeQuery(this.props.branchLike), | ||||
componentKeys: component && component.key, | componentKeys: component && component.key, | ||||
facets: mapFacet(property), | |||||
facets: property, | |||||
s: 'FILE_LINE', | s: 'FILE_LINE', | ||||
...serializeQuery({ ...query, ...changes }), | ...serializeQuery({ ...query, ...changes }), | ||||
ps: 1 | ps: 1 |
case 'project': | case 'project': | ||||
onFilterChange({ ...issuesReset, projects: [issue.projectKey] }); | onFilterChange({ ...issuesReset, projects: [issue.projectKey] }); | ||||
break; | break; | ||||
case 'module': | |||||
if (issue.subProjectUuid) { | |||||
onFilterChange({ ...issuesReset, modules: [issue.subProjectUuid] }); | |||||
} | |||||
break; | |||||
case 'file': | case 'file': | ||||
onFilterChange({ ...issuesReset, files: [issue.componentUuid] }); | onFilterChange({ ...issuesReset, files: [issue.componentUuid] }); | ||||
} | } |
open: boolean; | open: boolean; | ||||
query: Query; | query: Query; | ||||
stats: T.Dict<number> | undefined; | stats: T.Dict<number> | undefined; | ||||
authors: string[]; | |||||
author: string[]; | |||||
} | } | ||||
const SEARCH_SIZE = 100; | const SEARCH_SIZE = 100; | ||||
}).then(authors => ({ maxResults: authors.length === SEARCH_SIZE, results: authors })); | }).then(authors => ({ maxResults: authors.length === SEARCH_SIZE, results: authors })); | ||||
}; | }; | ||||
loadSearchResultCount = (authors: string[]) => { | |||||
return this.props.loadSearchResultCount('authors', { authors }); | |||||
loadSearchResultCount = (author: string[]) => { | |||||
return this.props.loadSearchResultCount('author', { author }); | |||||
}; | }; | ||||
renderSearchResult = (author: string, term: string) => { | renderSearchResult = (author: string, term: string) => { | ||||
onSearch={this.handleSearch} | onSearch={this.handleSearch} | ||||
onToggle={this.props.onToggle} | onToggle={this.props.onToggle} | ||||
open={this.props.open} | open={this.props.open} | ||||
property="authors" | |||||
query={omit(this.props.query, 'authors')} | |||||
property="author" | |||||
query={omit(this.props.query, 'author')} | |||||
renderFacetItem={this.identity} | renderFacetItem={this.identity} | ||||
renderSearchResult={this.renderSearchResult} | renderSearchResult={this.renderSearchResult} | ||||
searchPlaceholder={translate('search.search_for_authors')} | searchPlaceholder={translate('search.search_for_authors')} | ||||
stats={this.props.stats} | stats={this.props.stats} | ||||
values={this.props.authors} | |||||
values={this.props.author} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
)} | )} | ||||
{displayAuthorFacet && !this.props.disableDeveloperAggregatedInfo && ( | {displayAuthorFacet && !this.props.disableDeveloperAggregatedInfo && ( | ||||
<AuthorFacet | <AuthorFacet | ||||
authors={query.authors} | |||||
author={query.author} | |||||
component={component} | component={component} | ||||
fetching={this.props.loadingFacets.authors === true} | |||||
fetching={this.props.loadingFacets.author === true} | |||||
loadSearchResultCount={this.props.loadSearchResultCount} | loadSearchResultCount={this.props.loadSearchResultCount} | ||||
onChange={this.props.onFilterChange} | onChange={this.props.onFilterChange} | ||||
onToggle={this.props.onFacetToggle} | onToggle={this.props.onFacetToggle} | ||||
open={!!openFacets.authors} | |||||
open={!!openFacets.author} | |||||
query={query} | query={query} | ||||
stats={facets.authors} | |||||
stats={facets.author} | |||||
/> | /> | ||||
)} | )} | ||||
</> | </> |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import ListStyleFacet from '../../../../components/facet/ListStyleFacet'; | |||||
import { mockComponent } from '../../../../helpers/testMocks'; | |||||
import { Query } from '../../utils'; | |||||
import AuthorFacet from '../AuthorFacet'; | |||||
it('should render correctly', () => { | |||||
const wrapper = shallowRender(); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
}); | |||||
it('should notify of search result count correctly', () => { | |||||
const loadSearchResultCount = jest.fn(); | |||||
const wrapper = shallowRender({ loadSearchResultCount }); | |||||
wrapper.find(ListStyleFacet).props().loadSearchResultCount!(['1', '2']); | |||||
expect(loadSearchResultCount).toHaveBeenCalled(); | |||||
}); | |||||
function shallowRender(props: Partial<AuthorFacet['props']> = {}) { | |||||
return shallow<AuthorFacet>( | |||||
<AuthorFacet | |||||
component={mockComponent()} | |||||
fetching={false} | |||||
loadSearchResultCount={jest.fn()} | |||||
onChange={jest.fn()} | |||||
onToggle={jest.fn()} | |||||
open={true} | |||||
query={{} as Query} | |||||
stats={{}} | |||||
author={[]} | |||||
{...props} | |||||
/> | |||||
); | |||||
} |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render correctly 1`] = ` | |||||
<ListStyleFacet | |||||
facetHeader="issues.facet.authors" | |||||
fetching={false} | |||||
getFacetItemText={[Function]} | |||||
getSearchResultKey={[Function]} | |||||
getSearchResultText={[Function]} | |||||
loadSearchResultCount={[Function]} | |||||
maxInitialItems={15} | |||||
maxItems={100} | |||||
minSearchLength={2} | |||||
onChange={[MockFunction]} | |||||
onSearch={[Function]} | |||||
onToggle={[MockFunction]} | |||||
open={true} | |||||
property="author" | |||||
query={Object {}} | |||||
renderFacetItem={[Function]} | |||||
renderSearchResult={[Function]} | |||||
searchPlaceholder="search.search_for_authors" | |||||
stats={Object {}} | |||||
values={Array []} | |||||
/> | |||||
`; |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import { isArray } from 'lodash'; | |||||
import { searchUsers } from '../../api/users'; | import { searchUsers } from '../../api/users'; | ||||
import { formatMeasure } from '../../helpers/measures'; | import { formatMeasure } from '../../helpers/measures'; | ||||
import { | import { | ||||
} from '../../helpers/query'; | } from '../../helpers/query'; | ||||
import { scrollToElement } from '../../helpers/scrolling'; | import { scrollToElement } from '../../helpers/scrolling'; | ||||
import { get, save } from '../../helpers/storage'; | import { get, save } from '../../helpers/storage'; | ||||
import { isDefined } from '../../helpers/types'; | |||||
import { Facet, RawFacet } from '../../types/issues'; | import { Facet, RawFacet } from '../../types/issues'; | ||||
import { SecurityStandard, StandardType } from '../../types/security'; | import { SecurityStandard, StandardType } from '../../types/security'; | ||||
export interface Query { | export interface Query { | ||||
assigned: boolean; | assigned: boolean; | ||||
assignees: string[]; | assignees: string[]; | ||||
authors: string[]; | |||||
author: string[]; | |||||
createdAfter: Date | undefined; | createdAfter: Date | undefined; | ||||
createdAt: string; | createdAt: string; | ||||
createdBefore: Date | undefined; | createdBefore: Date | undefined; | ||||
files: string[]; | files: string[]; | ||||
issues: string[]; | issues: string[]; | ||||
languages: string[]; | languages: string[]; | ||||
modules: string[]; | |||||
owaspTop10: string[]; | owaspTop10: string[]; | ||||
projects: string[]; | projects: string[]; | ||||
resolutions: string[]; | resolutions: string[]; | ||||
return { | return { | ||||
assigned: parseAsBoolean(query.assigned), | assigned: parseAsBoolean(query.assigned), | ||||
assignees: parseAsArray(query.assignees, parseAsString), | assignees: parseAsArray(query.assignees, parseAsString), | ||||
authors: parseAsArray(query.authors, parseAsString), | |||||
author: isArray(query.author) ? query.author : [query.author].filter(isDefined), | |||||
createdAfter: parseAsDate(query.createdAfter), | createdAfter: parseAsDate(query.createdAfter), | ||||
createdAt: parseAsString(query.createdAt), | createdAt: parseAsString(query.createdAt), | ||||
createdBefore: parseAsDate(query.createdBefore), | createdBefore: parseAsDate(query.createdBefore), | ||||
files: parseAsArray(query.files, parseAsString), | files: parseAsArray(query.files, parseAsString), | ||||
issues: parseAsArray(query.issues, parseAsString), | issues: parseAsArray(query.issues, parseAsString), | ||||
languages: parseAsArray(query.languages, parseAsString), | languages: parseAsArray(query.languages, parseAsString), | ||||
modules: parseAsArray(query.moduleUuids, parseAsString), | |||||
owaspTop10: parseAsArray(query.owaspTop10, parseAsString), | owaspTop10: parseAsArray(query.owaspTop10, parseAsString), | ||||
projects: parseAsArray(query.projects, parseAsString), | projects: parseAsArray(query.projects, parseAsString), | ||||
resolutions: parseAsArray(query.resolutions, parseAsString), | resolutions: parseAsArray(query.resolutions, parseAsString), | ||||
const filter = { | const filter = { | ||||
assigned: query.assigned ? undefined : 'false', | assigned: query.assigned ? undefined : 'false', | ||||
assignees: serializeStringArray(query.assignees), | assignees: serializeStringArray(query.assignees), | ||||
authors: serializeStringArray(query.authors), | |||||
author: query.author, | |||||
createdAfter: serializeDateShort(query.createdAfter), | createdAfter: serializeDateShort(query.createdAfter), | ||||
createdAt: serializeString(query.createdAt), | createdAt: serializeString(query.createdAt), | ||||
createdBefore: serializeDateShort(query.createdBefore), | createdBefore: serializeDateShort(query.createdBefore), | ||||
files: serializeStringArray(query.files), | files: serializeStringArray(query.files), | ||||
issues: serializeStringArray(query.issues), | issues: serializeStringArray(query.issues), | ||||
languages: serializeStringArray(query.languages), | languages: serializeStringArray(query.languages), | ||||
moduleUuids: serializeStringArray(query.modules), | |||||
owaspTop10: serializeStringArray(query.owaspTop10), | owaspTop10: serializeStringArray(query.owaspTop10), | ||||
projects: serializeStringArray(query.projects), | projects: serializeStringArray(query.projects), | ||||
resolutions: serializeStringArray(query.resolutions), | resolutions: serializeStringArray(query.resolutions), | ||||
tags: serializeStringArray(query.tags), | tags: serializeStringArray(query.tags), | ||||
types: serializeStringArray(query.types) | types: serializeStringArray(query.types) | ||||
}; | }; | ||||
return cleanQuery(filter); | return cleanQuery(filter); | ||||
} | } | ||||
export const areQueriesEqual = (a: T.RawQuery, b: T.RawQuery) => | export const areQueriesEqual = (a: T.RawQuery, b: T.RawQuery) => | ||||
queriesEqual(parseQuery(a), parseQuery(b)); | queriesEqual(parseQuery(a), parseQuery(b)); | ||||
export function mapFacet(facet: string) { | |||||
const propertyMapping: T.Dict<string> = { | |||||
modules: 'moduleUuids' | |||||
}; | |||||
return propertyMapping[facet] || facet; | |||||
} | |||||
export function parseFacets(facets: RawFacet[]): T.Dict<Facet> { | export function parseFacets(facets: RawFacet[]): T.Dict<Facet> { | ||||
if (!facets) { | if (!facets) { | ||||
return {}; | return {}; | ||||
} | } | ||||
// for readability purpose | |||||
const propertyMapping: T.Dict<string> = { | |||||
moduleUuids: 'modules' | |||||
}; | |||||
const result: T.Dict<Facet> = {}; | const result: T.Dict<Facet> = {}; | ||||
facets.forEach(facet => { | facets.forEach(facet => { | ||||
const values: Facet = {}; | const values: Facet = {}; | ||||
facet.values.forEach(value => { | facet.values.forEach(value => { | ||||
values[value.val] = value.count; | values[value.val] = value.count; | ||||
}); | }); | ||||
const finalProperty = propertyMapping[facet.property] || facet.property; | |||||
result[finalProperty] = values; | |||||
result[facet.property] = values; | |||||
}); | }); | ||||
return result; | return result; | ||||
} | } |
status: string; | status: string; | ||||
subProject?: string; | subProject?: string; | ||||
subProjectName?: string; | subProjectName?: string; | ||||
subProjectUuid?: string; | |||||
tags?: string[]; | tags?: string[]; | ||||
textRange?: TextRange; | textRange?: TextRange; | ||||
transitions: string[]; | transitions: string[]; |
import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNED_TO_ME; | import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNED_TO_ME; | ||||
import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNEES; | import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNEES; | ||||
import static org.sonar.server.issue.index.IssueIndex.Facet.AUTHOR; | import static org.sonar.server.issue.index.IssueIndex.Facet.AUTHOR; | ||||
import static org.sonar.server.issue.index.IssueIndex.Facet.AUTHORS; | |||||
import static org.sonar.server.issue.index.IssueIndex.Facet.CREATED_AT; | import static org.sonar.server.issue.index.IssueIndex.Facet.CREATED_AT; | ||||
import static org.sonar.server.issue.index.IssueIndex.Facet.CWE; | import static org.sonar.server.issue.index.IssueIndex.Facet.CWE; | ||||
import static org.sonar.server.issue.index.IssueIndex.Facet.DIRECTORIES; | import static org.sonar.server.issue.index.IssueIndex.Facet.DIRECTORIES; | ||||
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES; | import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES; | ||||
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE; | import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE; | ||||
import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW; | import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_PARAM_AUTHORS; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_MODULE_UUIDS; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES; | ||||
public static final String FACET_PROJECTS = "projects"; | public static final String FACET_PROJECTS = "projects"; | ||||
public static final String FACET_ASSIGNED_TO_ME = "assigned_to_me"; | public static final String FACET_ASSIGNED_TO_ME = "assigned_to_me"; | ||||
public static final String FACET_MODULES = "moduleUuids"; | |||||
private static final int DEFAULT_FACET_SIZE = 15; | private static final int DEFAULT_FACET_SIZE = 15; | ||||
private static final int MAX_FACET_SIZE = 100; | private static final int MAX_FACET_SIZE = 100; | ||||
LANGUAGES(PARAM_LANGUAGES, FIELD_ISSUE_LANGUAGE, STICKY, MAX_FACET_SIZE), | LANGUAGES(PARAM_LANGUAGES, FIELD_ISSUE_LANGUAGE, STICKY, MAX_FACET_SIZE), | ||||
RULES(PARAM_RULES, FIELD_ISSUE_RULE_UUID, STICKY, MAX_FACET_SIZE), | RULES(PARAM_RULES, FIELD_ISSUE_RULE_UUID, STICKY, MAX_FACET_SIZE), | ||||
TAGS(PARAM_TAGS, FIELD_ISSUE_TAGS, STICKY, MAX_FACET_SIZE), | TAGS(PARAM_TAGS, FIELD_ISSUE_TAGS, STICKY, MAX_FACET_SIZE), | ||||
AUTHORS(DEPRECATED_PARAM_AUTHORS, FIELD_ISSUE_AUTHOR_LOGIN, STICKY, MAX_FACET_SIZE), | |||||
AUTHOR(PARAM_AUTHOR, FIELD_ISSUE_AUTHOR_LOGIN, STICKY, MAX_FACET_SIZE), | AUTHOR(PARAM_AUTHOR, FIELD_ISSUE_AUTHOR_LOGIN, STICKY, MAX_FACET_SIZE), | ||||
PROJECT_UUIDS(FACET_PROJECTS, FIELD_ISSUE_PROJECT_UUID, STICKY, MAX_FACET_SIZE), | PROJECT_UUIDS(FACET_PROJECTS, FIELD_ISSUE_PROJECT_UUID, STICKY, MAX_FACET_SIZE), | ||||
MODULE_UUIDS(PARAM_MODULE_UUIDS, FIELD_ISSUE_MODULE_UUID, STICKY, MAX_FACET_SIZE), | |||||
MODULE_UUIDS(FACET_MODULES, FIELD_ISSUE_MODULE_UUID, STICKY, MAX_FACET_SIZE), | |||||
FILES(PARAM_FILES, FIELD_ISSUE_FILE_PATH, STICKY, MAX_FACET_SIZE), | FILES(PARAM_FILES, FIELD_ISSUE_FILE_PATH, STICKY, MAX_FACET_SIZE), | ||||
DIRECTORIES(PARAM_DIRECTORIES, FIELD_ISSUE_DIRECTORY_PATH, STICKY, MAX_FACET_SIZE), | DIRECTORIES(PARAM_DIRECTORIES, FIELD_ISSUE_DIRECTORY_PATH, STICKY, MAX_FACET_SIZE), | ||||
ASSIGNEES(PARAM_ASSIGNEES, FIELD_ISSUE_ASSIGNEE_UUID, STICKY, MAX_FACET_SIZE), | ASSIGNEES(PARAM_ASSIGNEES, FIELD_ISSUE_ASSIGNEE_UUID, STICKY, MAX_FACET_SIZE), | ||||
addFacetIfNeeded(options, aggregationHelper, esRequest, SCOPES, query.scopes().toArray()); | addFacetIfNeeded(options, aggregationHelper, esRequest, SCOPES, query.scopes().toArray()); | ||||
addFacetIfNeeded(options, aggregationHelper, esRequest, LANGUAGES, query.languages().toArray()); | addFacetIfNeeded(options, aggregationHelper, esRequest, LANGUAGES, query.languages().toArray()); | ||||
addFacetIfNeeded(options, aggregationHelper, esRequest, RULES, query.ruleUuids().toArray()); | addFacetIfNeeded(options, aggregationHelper, esRequest, RULES, query.ruleUuids().toArray()); | ||||
addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHORS, query.authors().toArray()); | |||||
addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHOR, query.authors().toArray()); | addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHOR, query.authors().toArray()); | ||||
addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray()); | addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray()); | ||||
addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray()); | addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray()); |
builder.projectUuids(projects.stream().map(IssueQueryFactory::toProjectUuid).collect(toList())); | builder.projectUuids(projects.stream().map(IssueQueryFactory::toProjectUuid).collect(toList())); | ||||
setBranch(builder, projects.get(0), request.getBranch(), request.getPullRequest()); | setBranch(builder, projects.get(0), request.getBranch(), request.getPullRequest()); | ||||
} | } | ||||
builder.moduleUuids(request.getModuleUuids()); | |||||
builder.directories(request.getDirectories()); | builder.directories(request.getDirectories()); | ||||
builder.files(request.getFiles()); | builder.files(request.getFiles()); | ||||
} | } | ||||
@Test | @Test | ||||
public void facets_on_authors() { | |||||
public void facets_on_author() { | |||||
ComponentDto project = ComponentTesting.newPrivateProjectDto(); | ComponentDto project = ComponentTesting.newPrivateProjectDto(); | ||||
ComponentDto file = ComponentTesting.newFileDto(project, null); | ComponentDto file = ComponentTesting.newFileDto(project, null); | ||||
IssueDocTesting.newDoc("I3", file).setAuthorLogin("simon").setEffort(10L), | IssueDocTesting.newDoc("I3", file).setAuthorLogin("simon").setEffort(10L), | ||||
IssueDocTesting.newDoc("I4", file).setAuthorLogin(null).setEffort(10L)); | IssueDocTesting.newDoc("I4", file).setAuthorLogin(null).setEffort(10L)); | ||||
Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(singletonList("authors"))), system2.getDefaultTimeZone().toZoneId()); | |||||
assertThat(facets.getNames()).containsOnly("authors", FACET_MODE_EFFORT); | |||||
assertThat(facets.get("authors")).containsOnly(entry("steph", 10L), entry("simon", 20L)); | |||||
Facets facets = new Facets(underTest.search(newQueryBuilder().build(), new SearchOptions().addFacets(singletonList("author"))), system2.getDefaultTimeZone().toZoneId()); | |||||
assertThat(facets.getNames()).containsOnly("author", FACET_MODE_EFFORT); | |||||
assertThat(facets.get("author")).containsOnly(entry("steph", 10L), entry("simon", 20L)); | |||||
assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 40L)); | assertThat(facets.get(FACET_MODE_EFFORT)).containsOnly(entry("total", 40L)); | ||||
} | } | ||||
assertThatFacetHasOnly(IssueQuery.builder(), "author", entry("steph", 1L), entry("marcel", 2L)); | assertThatFacetHasOnly(IssueQuery.builder(), "author", entry("steph", 1L), entry("marcel", 2L)); | ||||
} | } | ||||
@Test | |||||
public void facets_on_deprecated_authors() { | |||||
ComponentDto project = newPrivateProjectDto(); | |||||
ComponentDto file = newFileDto(project, null); | |||||
indexIssues( | |||||
newDoc("I1", file).setAuthorLogin("steph"), | |||||
newDoc("I2", file).setAuthorLogin("marcel"), | |||||
newDoc("I3", file).setAuthorLogin("marcel"), | |||||
newDoc("I4", file).setAuthorLogin(null)); | |||||
assertThatFacetHasOnly(IssueQuery.builder(), "authors", entry("steph", 1L), entry("marcel", 2L)); | |||||
} | |||||
@Test | @Test | ||||
public void facets_on_authors_return_100_entries_plus_selected_values() { | public void facets_on_authors_return_100_entries_plus_selected_values() { | ||||
ComponentDto project = newPrivateProjectDto(); | ComponentDto project = newPrivateProjectDto(); | ||||
IssueDoc issue2 = newDoc(newFileDto(project, null)).setAuthorLogin("user2"); | IssueDoc issue2 = newDoc(newFileDto(project, null)).setAuthorLogin("user2"); | ||||
indexIssues(issue1, issue2); | indexIssues(issue1, issue2); | ||||
assertThatFacetHasSize(IssueQuery.builder().build(), "authors", 100); | |||||
assertThatFacetHasSize(IssueQuery.builder().authors(asList(issue1.authorLogin(), issue2.authorLogin())).build(), "authors", 102); | |||||
assertThatFacetHasSize(IssueQuery.builder().build(), "author", 100); | |||||
assertThatFacetHasSize(IssueQuery.builder().authors(asList(issue1.authorLogin(), issue2.authorLogin())).build(), "author", 102); | |||||
} | } | ||||
@Test | @Test |
.setResolutions(asList("FALSE-POSITIVE")) | .setResolutions(asList("FALSE-POSITIVE")) | ||||
.setResolved(true) | .setResolved(true) | ||||
.setProjects(asList(project.getDbKey())) | .setProjects(asList(project.getDbKey())) | ||||
.setModuleUuids(asList(module.uuid())) | |||||
.setDirectories(asList("aDirPath")) | .setDirectories(asList("aDirPath")) | ||||
.setFiles(asList(file.uuid())) | .setFiles(asList(file.uuid())) | ||||
.setAssigneesUuid(asList(user.getUuid())) | .setAssigneesUuid(asList(user.getUuid())) | ||||
assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE"); | assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE"); | ||||
assertThat(query.resolved()).isTrue(); | assertThat(query.resolved()).isTrue(); | ||||
assertThat(query.projectUuids()).containsOnly(project.uuid()); | assertThat(query.projectUuids()).containsOnly(project.uuid()); | ||||
assertThat(query.moduleUuids()).containsOnly(module.uuid()); | |||||
assertThat(query.files()).containsOnly(file.uuid()); | assertThat(query.files()).containsOnly(file.uuid()); | ||||
assertThat(query.assignees()).containsOnly(user.getUuid()); | assertThat(query.assignees()).containsOnly(user.getUuid()); | ||||
assertThat(query.scopes()).containsOnly("TEST", "MAIN"); | assertThat(query.scopes()).containsOnly("TEST", "MAIN"); |
import org.sonar.server.issue.index.IssueQuery.PeriodStart; | import org.sonar.server.issue.index.IssueQuery.PeriodStart; | ||||
import static com.google.common.collect.Lists.newArrayList; | import static com.google.common.collect.Lists.newArrayList; | ||||
import static org.apache.commons.lang.math.RandomUtils.nextInt; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.entry; | import static org.assertj.core.api.Assertions.entry; | ||||
*/ | */ | ||||
package org.sonar.server.component; | package org.sonar.server.component; | ||||
import java.util.Set; | |||||
import org.sonar.api.resources.Qualifiers; | |||||
import org.sonar.api.resources.Scopes; | |||||
import org.sonar.api.server.ServerSide; | import org.sonar.api.server.ServerSide; | ||||
import org.sonar.api.web.UserRole; | import org.sonar.api.web.UserRole; | ||||
import org.sonar.core.util.stream.MoreCollectors; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.component.ComponentDto; | |||||
import org.sonar.db.component.ComponentKeyUpdaterDao; | |||||
import org.sonar.db.component.ResourceDto; | |||||
import org.sonar.db.project.ProjectDto; | import org.sonar.db.project.ProjectDto; | ||||
import org.sonar.server.es.ProjectIndexer; | import org.sonar.server.es.ProjectIndexer; | ||||
import org.sonar.server.es.ProjectIndexers; | import org.sonar.server.es.ProjectIndexers; | ||||
import org.sonar.server.project.RekeyedProject; | import org.sonar.server.project.RekeyedProject; | ||||
import org.sonar.server.user.UserSession; | import org.sonar.server.user.UserSession; | ||||
import static java.util.Collections.emptyList; | |||||
import static java.util.Collections.singleton; | import static java.util.Collections.singleton; | ||||
import static java.util.Collections.singletonList; | import static java.util.Collections.singletonList; | ||||
import static org.sonar.core.component.ComponentKeys.checkProjectKey; | import static org.sonar.core.component.ComponentKeys.checkProjectKey; | ||||
projectLifeCycleListeners.onProjectsRekeyed(singleton(new RekeyedProject(newProject, project.getKey()))); | projectLifeCycleListeners.onProjectsRekeyed(singleton(new RekeyedProject(newProject, project.getKey()))); | ||||
} | } | ||||
public void bulkUpdateKey(DbSession dbSession, ProjectDto project, String stringToReplace, String replacementString) { | |||||
Set<ComponentKeyUpdaterDao.RekeyedResource> rekeyedProjects = dbClient.componentKeyUpdaterDao().bulkUpdateKey( | |||||
dbSession, project.getUuid(), stringToReplace, replacementString, | |||||
ComponentService::isMainProject); | |||||
projectIndexers.commitAndIndexProjects(dbSession, singletonList(project), ProjectIndexer.Cause.PROJECT_KEY_UPDATE); | |||||
if (!rekeyedProjects.isEmpty()) { | |||||
projectLifeCycleListeners.onProjectsRekeyed(rekeyedProjects.stream() | |||||
.map(ComponentService::toRekeyedProject) | |||||
.collect(MoreCollectors.toSet(rekeyedProjects.size()))); | |||||
} | |||||
} | |||||
private static boolean isMainProject(ComponentKeyUpdaterDao.RekeyedResource rekeyedResource) { | |||||
ResourceDto resource = rekeyedResource.getResource(); | |||||
String resourceKey = resource.getKey(); | |||||
return Scopes.PROJECT.equals(resource.getScope()) | |||||
&& Qualifiers.PROJECT.equals(resource.getQualifier()) | |||||
&& !resourceKey.contains(ComponentDto.BRANCH_KEY_SEPARATOR) | |||||
&& !resourceKey.contains(ComponentDto.PULL_REQUEST_SEPARATOR); | |||||
} | |||||
private static RekeyedProject toRekeyedProject(ComponentKeyUpdaterDao.RekeyedResource rekeyedResource) { | |||||
ResourceDto resource = rekeyedResource.getResource(); | |||||
Project project = new Project(resource.getUuid(), resource.getKey(), resource.getName(), resource.getDescription(), emptyList()); | |||||
return new RekeyedProject(project, rekeyedResource.getOldKey()); | |||||
} | |||||
} | } |
import static org.sonar.core.util.stream.MoreCollectors.toSet; | import static org.sonar.core.util.stream.MoreCollectors.toSet; | ||||
import static org.sonar.server.es.SearchOptions.MAX_PAGE_SIZE; | import static org.sonar.server.es.SearchOptions.MAX_PAGE_SIZE; | ||||
import static org.sonar.server.issue.index.IssueIndex.FACET_ASSIGNED_TO_ME; | import static org.sonar.server.issue.index.IssueIndex.FACET_ASSIGNED_TO_ME; | ||||
import static org.sonar.server.issue.index.IssueIndex.FACET_MODULES; | |||||
import static org.sonar.server.issue.index.IssueIndex.FACET_PROJECTS; | import static org.sonar.server.issue.index.IssueIndex.FACET_PROJECTS; | ||||
import static org.sonar.server.issue.index.IssueQuery.SORT_BY_ASSIGNEE; | import static org.sonar.server.issue.index.IssueQuery.SORT_BY_ASSIGNEE; | ||||
import static org.sonar.server.issue.index.IssueQueryFactory.ISSUE_STATUSES; | import static org.sonar.server.issue.index.IssueQueryFactory.ISSUE_STATUSES; | ||||
import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; | import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; | ||||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | import static org.sonar.server.ws.WsUtils.writeProtobuf; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SEARCH; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SEARCH; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_PARAM_AUTHORS; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_COUNT; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ADDITIONAL_FIELDS; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ADDITIONAL_FIELDS; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASC; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASC; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNED; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNED; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_MODULE_UUIDS; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ON_COMPONENT_ONLY; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ON_COMPONENT_ONLY; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; | ||||
static final List<String> SUPPORTED_FACETS = List.of( | static final List<String> SUPPORTED_FACETS = List.of( | ||||
FACET_PROJECTS, | FACET_PROJECTS, | ||||
PARAM_MODULE_UUIDS, | |||||
FACET_MODULES, | |||||
PARAM_FILES, | PARAM_FILES, | ||||
FACET_ASSIGNED_TO_ME, | FACET_ASSIGNED_TO_ME, | ||||
PARAM_SEVERITIES, | PARAM_SEVERITIES, | ||||
PARAM_RESOLUTIONS, | PARAM_RESOLUTIONS, | ||||
PARAM_RULES, | PARAM_RULES, | ||||
PARAM_ASSIGNEES, | PARAM_ASSIGNEES, | ||||
DEPRECATED_PARAM_AUTHORS, | |||||
PARAM_AUTHOR, | PARAM_AUTHOR, | ||||
PARAM_DIRECTORIES, | PARAM_DIRECTORIES, | ||||
PARAM_SCOPES, | PARAM_SCOPES, | ||||
); | ); | ||||
private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. "; | private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. "; | ||||
private static final Set<String> FACETS_REQUIRING_PROJECT = newHashSet(PARAM_MODULE_UUIDS, PARAM_FILES, PARAM_DIRECTORIES); | |||||
private static final Set<String> FACETS_REQUIRING_PROJECT = newHashSet(FACET_MODULES, PARAM_FILES, PARAM_DIRECTORIES); | |||||
private final UserSession userSession; | private final UserSession userSession; | ||||
private final IssueIndex issueIndex; | private final IssueIndex issueIndex; | ||||
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.") | + "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.") | ||||
.setSince("3.6") | .setSince("3.6") | ||||
.setChangelog( | .setChangelog( | ||||
new Change("9.1", "Deprecated parameters 'authors', 'facetMode' and 'moduleUuids' were dropped"), | |||||
new Change("8.6", "Parameter 'timeZone' added"), | new Change("8.6", "Parameter 'timeZone' added"), | ||||
new Change("8.5", "Facet 'fileUuids' is dropped in favour of the new facet 'files'" + | new Change("8.5", "Facet 'fileUuids' is dropped in favour of the new facet 'files'" + | ||||
"Note that they are not strictly identical, the latter returns the file paths."), | "Note that they are not strictly identical, the latter returns the file paths."), | ||||
new Change("8.2", "Status 'IN_REVIEW' for Security Hotspots has been deprecated"), | new Change("8.2", "Status 'IN_REVIEW' for Security Hotspots has been deprecated"), | ||||
new Change("7.8", format("added new Security Hotspots statuses : %s, %s and %s", STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_REVIEWED)), | new Change("7.8", format("added new Security Hotspots statuses : %s, %s and %s", STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_REVIEWED)), | ||||
new Change("7.8", "Security hotspots are returned by default"), | new Change("7.8", "Security hotspots are returned by default"), | ||||
new Change("7.7", format("Value '%s' in parameter '%s' is deprecated, please use '%s' instead", DEPRECATED_PARAM_AUTHORS, FACETS, PARAM_AUTHOR)), | |||||
new Change("7.7", format("Value 'authors' in parameter '%s' is deprecated, please use '%s' instead", FACETS, PARAM_AUTHOR)), | |||||
new Change("7.6", format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT_KEYS)), | new Change("7.6", format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT_KEYS)), | ||||
new Change("7.4", "The facet 'projectUuids' is dropped in favour of the new facet 'projects'. " + | new Change("7.4", "The facet 'projectUuids' is dropped in favour of the new facet 'projects'. " + | ||||
"Note that they are not strictly identical, the latter returns the project keys."), | "Note that they are not strictly identical, the latter returns the project keys."), | ||||
new Change("7.4", format("Parameter '%s' does not accept anymore deprecated value 'debt'", FACET_MODE)), | |||||
new Change("7.4", "Parameter 'facetMode' does not accept anymore deprecated value 'debt'"), | |||||
new Change("7.3", "response field 'fromHotspot' added to issues that are security hotspots"), | new Change("7.3", "response field 'fromHotspot' added to issues that are security hotspots"), | ||||
new Change("7.3", "added facets 'sansTop25', 'owaspTop10' and 'cwe'"), | new Change("7.3", "added facets 'sansTop25', 'owaspTop10' and 'cwe'"), | ||||
new Change("7.2", "response field 'externalRuleEngine' added to issues that have been imported from an external rule engine"), | new Change("7.2", "response field 'externalRuleEngine' added to issues that have been imported from an external rule engine"), | ||||
action.createParam(FACETS) | action.createParam(FACETS) | ||||
.setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.") | .setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.") | ||||
.setPossibleValues(SUPPORTED_FACETS); | .setPossibleValues(SUPPORTED_FACETS); | ||||
action.createParam(FACET_MODE) | |||||
.setDefaultValue(FACET_MODE_COUNT) | |||||
.setDeprecatedSince("7.9") | |||||
.setDescription("Choose the returned value for facet items, either count of issues or sum of remediation effort.") | |||||
.setPossibleValues(FACET_MODE_COUNT, FACET_MODE_EFFORT); | |||||
action.addSortParams(IssueQuery.SORTS, null, true); | action.addSortParams(IssueQuery.SORTS, null, true); | ||||
action.createParam(PARAM_ADDITIONAL_FIELDS) | action.createParam(PARAM_ADDITIONAL_FIELDS) | ||||
.setSince("5.2") | .setSince("5.2") | ||||
" with any category") | " with any category") | ||||
.setSince("7.8") | .setSince("7.8") | ||||
.setPossibleValues(Arrays.stream(SQCategory.values()).map(SQCategory::getKey).collect(Collectors.toList())); | .setPossibleValues(Arrays.stream(SQCategory.values()).map(SQCategory::getKey).collect(Collectors.toList())); | ||||
action.createParam(DEPRECATED_PARAM_AUTHORS) | |||||
.setDeprecatedSince("7.7") | |||||
.setDescription("This parameter is deprecated, please use '%s' instead", PARAM_AUTHOR) | |||||
.setExampleValue("torvalds@linux-foundation.org"); | |||||
action.createParam(PARAM_AUTHOR) | action.createParam(PARAM_AUTHOR) | ||||
.setDescription("SCM accounts. To set several values, the parameter must be called once for each value.") | .setDescription("SCM accounts. To set several values, the parameter must be called once for each value.") | ||||
.setExampleValue("author=torvalds@linux-foundation.org&author=linux@fondation.org"); | .setExampleValue("author=torvalds@linux-foundation.org&author=linux@fondation.org"); | ||||
.setInternal(true) | .setInternal(true) | ||||
.setExampleValue(KEY_PROJECT_EXAMPLE_001); | .setExampleValue(KEY_PROJECT_EXAMPLE_001); | ||||
action.createParam(PARAM_MODULE_UUIDS) | |||||
.setDescription("To retrieve issues associated to a specific list of modules (comma-separated list of module IDs). " + | |||||
INTERNAL_PARAMETER_DISCLAIMER) | |||||
.setInternal(true) | |||||
.setDeprecatedSince("7.6") | |||||
.setExampleValue("7d8749e8-3070-4903-9188-bdd82933bb92"); | |||||
action.createParam(PARAM_DIRECTORIES) | action.createParam(PARAM_DIRECTORIES) | ||||
.setDescription("To retrieve issues associated to a specific list of directories (comma-separated list of directory paths). " + | .setDescription("To retrieve issues associated to a specific list of directories (comma-separated list of directory paths). " + | ||||
"This parameter is only meaningful when a module is selected. " + | "This parameter is only meaningful when a module is selected. " + | ||||
addMandatoryValuesToFacet(facets, PARAM_STATUSES, ISSUE_STATUSES); | addMandatoryValuesToFacet(facets, PARAM_STATUSES, ISSUE_STATUSES); | ||||
addMandatoryValuesToFacet(facets, PARAM_RESOLUTIONS, concat(singletonList(""), RESOLUTIONS)); | addMandatoryValuesToFacet(facets, PARAM_RESOLUTIONS, concat(singletonList(""), RESOLUTIONS)); | ||||
addMandatoryValuesToFacet(facets, FACET_PROJECTS, query.projectUuids()); | addMandatoryValuesToFacet(facets, FACET_PROJECTS, query.projectUuids()); | ||||
addMandatoryValuesToFacet(facets, PARAM_MODULE_UUIDS, query.moduleUuids()); | |||||
addMandatoryValuesToFacet(facets, FACET_MODULES, query.moduleUuids()); | |||||
addMandatoryValuesToFacet(facets, PARAM_FILES, query.files()); | addMandatoryValuesToFacet(facets, PARAM_FILES, query.files()); | ||||
List<String> assignees = Lists.newArrayList(""); | List<String> assignees = Lists.newArrayList(""); | ||||
private static void collectFacets(SearchResponseLoader.Collector collector, Facets facets) { | private static void collectFacets(SearchResponseLoader.Collector collector, Facets facets) { | ||||
collector.addProjectUuids(facets.getBucketKeys(FACET_PROJECTS)); | collector.addProjectUuids(facets.getBucketKeys(FACET_PROJECTS)); | ||||
collector.addComponentUuids(facets.getBucketKeys(PARAM_MODULE_UUIDS)); | |||||
collector.addRuleIds(facets.getBucketKeys(PARAM_RULES)); | collector.addRuleIds(facets.getBucketKeys(PARAM_RULES)); | ||||
collector.addUserUuids(facets.getBucketKeys(PARAM_ASSIGNEES)); | collector.addUserUuids(facets.getBucketKeys(PARAM_ASSIGNEES)); | ||||
} | } | ||||
private static void collectRequestParams(SearchResponseLoader.Collector collector, SearchRequest request) { | private static void collectRequestParams(SearchResponseLoader.Collector collector, SearchRequest request) { | ||||
collector.addComponentUuids(request.getModuleUuids()); | |||||
collector.addUserUuids(request.getAssigneeUuids()); | collector.addUserUuids(request.getAssigneeUuids()); | ||||
} | } | ||||
.setAsc(request.mandatoryParamAsBoolean(PARAM_ASC)) | .setAsc(request.mandatoryParamAsBoolean(PARAM_ASC)) | ||||
.setAssigned(request.paramAsBoolean(PARAM_ASSIGNED)) | .setAssigned(request.paramAsBoolean(PARAM_ASSIGNED)) | ||||
.setAssigneesUuid(getLogins(dbSession, request.paramAsStrings(PARAM_ASSIGNEES))) | .setAssigneesUuid(getLogins(dbSession, request.paramAsStrings(PARAM_ASSIGNEES))) | ||||
.setAuthors(request.hasParam(PARAM_AUTHOR) ? request.multiParam(PARAM_AUTHOR) : request.paramAsStrings(DEPRECATED_PARAM_AUTHORS)) | |||||
.setAuthors(request.multiParam(PARAM_AUTHOR)) | |||||
.setComponents(request.paramAsStrings(PARAM_COMPONENT_KEYS)) | .setComponents(request.paramAsStrings(PARAM_COMPONENT_KEYS)) | ||||
.setCreatedAfter(request.param(PARAM_CREATED_AFTER)) | .setCreatedAfter(request.param(PARAM_CREATED_AFTER)) | ||||
.setCreatedAt(request.param(PARAM_CREATED_AT)) | .setCreatedAt(request.param(PARAM_CREATED_AT)) | ||||
.setCreatedBefore(request.param(PARAM_CREATED_BEFORE)) | .setCreatedBefore(request.param(PARAM_CREATED_BEFORE)) | ||||
.setCreatedInLast(request.param(PARAM_CREATED_IN_LAST)) | .setCreatedInLast(request.param(PARAM_CREATED_IN_LAST)) | ||||
.setDirectories(request.paramAsStrings(PARAM_DIRECTORIES)) | .setDirectories(request.paramAsStrings(PARAM_DIRECTORIES)) | ||||
.setFacetMode(request.mandatoryParam(FACET_MODE)) | |||||
.setFacets(request.paramAsStrings(FACETS)) | .setFacets(request.paramAsStrings(FACETS)) | ||||
.setFiles(request.paramAsStrings(PARAM_FILES)) | .setFiles(request.paramAsStrings(PARAM_FILES)) | ||||
.setIssues(request.paramAsStrings(PARAM_ISSUES)) | .setIssues(request.paramAsStrings(PARAM_ISSUES)) | ||||
.setScopes(request.paramAsStrings(PARAM_SCOPES)) | .setScopes(request.paramAsStrings(PARAM_SCOPES)) | ||||
.setLanguages(request.paramAsStrings(PARAM_LANGUAGES)) | .setLanguages(request.paramAsStrings(PARAM_LANGUAGES)) | ||||
.setModuleUuids(request.paramAsStrings(PARAM_MODULE_UUIDS)) | |||||
.setOnComponentOnly(request.paramAsBoolean(PARAM_ON_COMPONENT_ONLY)) | .setOnComponentOnly(request.paramAsBoolean(PARAM_ON_COMPONENT_ONLY)) | ||||
.setBranch(request.param(PARAM_BRANCH)) | .setBranch(request.param(PARAM_BRANCH)) | ||||
.setPullRequest(request.param(PARAM_PULL_REQUEST)) | .setPullRequest(request.param(PARAM_PULL_REQUEST)) |
RemoveUserAction.class, | RemoveUserAction.class, | ||||
UsersAction.class, | UsersAction.class, | ||||
GroupsAction.class, | GroupsAction.class, | ||||
SearchGlobalPermissionsAction.class, | |||||
RemoveUserFromTemplateAction.class, | RemoveUserFromTemplateAction.class, | ||||
AddUserToTemplateAction.class, | AddUserToTemplateAction.class, | ||||
AddGroupToTemplateAction.class, | AddGroupToTemplateAction.class, |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.permission.ws; | |||||
import java.util.Locale; | |||||
import org.sonar.api.server.ws.Request; | |||||
import org.sonar.api.server.ws.Response; | |||||
import org.sonar.api.server.ws.WebService; | |||||
import org.sonar.core.i18n.I18n; | |||||
import org.sonar.db.DbClient; | |||||
import org.sonar.db.DbSession; | |||||
import org.sonar.db.permission.GlobalPermission; | |||||
import org.sonar.db.permission.PermissionQuery; | |||||
import org.sonar.server.permission.PermissionService; | |||||
import org.sonar.server.user.UserSession; | |||||
import org.sonarqube.ws.Permissions.Permission; | |||||
import org.sonarqube.ws.Permissions.WsSearchGlobalPermissionsResponse; | |||||
import static org.sonar.server.permission.PermissionPrivilegeChecker.checkGlobalAdmin; | |||||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||||
import static org.sonarqube.ws.Permissions.Permission.newBuilder; | |||||
public class SearchGlobalPermissionsAction implements PermissionsWsAction { | |||||
public static final String ACTION = "search_global_permissions"; | |||||
private static final String PROPERTY_PREFIX = "global_permissions."; | |||||
private static final String DESCRIPTION_SUFFIX = ".desc"; | |||||
private final DbClient dbClient; | |||||
private final UserSession userSession; | |||||
private final I18n i18n; | |||||
private final PermissionService permissionService; | |||||
public SearchGlobalPermissionsAction(DbClient dbClient, UserSession userSession, I18n i18n, PermissionService permissionService) { | |||||
this.dbClient = dbClient; | |||||
this.userSession = userSession; | |||||
this.i18n = i18n; | |||||
this.permissionService = permissionService; | |||||
} | |||||
@Override | |||||
public void define(WebService.NewController context) { | |||||
context.createAction(ACTION) | |||||
.setDescription("List global permissions. <br />" + | |||||
"Requires the following permission: 'Administer System'") | |||||
.setResponseExample(getClass().getResource("search_global_permissions-example.json")) | |||||
.setSince("5.2") | |||||
.setDeprecatedSince("6.5") | |||||
.setHandler(this); | |||||
} | |||||
@Override | |||||
public void handle(Request wsRequest, Response wsResponse) throws Exception { | |||||
try (DbSession dbSession = dbClient.openSession(false)) { | |||||
checkGlobalAdmin(userSession); | |||||
WsSearchGlobalPermissionsResponse response = buildResponse(dbSession); | |||||
writeProtobuf(response, wsRequest, wsResponse); | |||||
} | |||||
} | |||||
private WsSearchGlobalPermissionsResponse buildResponse(DbSession dbSession) { | |||||
WsSearchGlobalPermissionsResponse.Builder response = WsSearchGlobalPermissionsResponse.newBuilder(); | |||||
Permission.Builder permission = newBuilder(); | |||||
permissionService.getGlobalPermissions().stream() | |||||
.map(GlobalPermission::getKey) | |||||
.forEach(permissionKey -> { | |||||
PermissionQuery query = permissionQuery(permissionKey); | |||||
response.addPermissions( | |||||
permission | |||||
.clear() | |||||
.setKey(permissionKey) | |||||
.setName(i18nName(permissionKey)) | |||||
.setDescription(i18nDescriptionMessage(permissionKey)) | |||||
.setUsersCount(countUsers(dbSession, query)) | |||||
.setGroupsCount(countGroups(dbSession, permissionKey))); | |||||
}); | |||||
return response.build(); | |||||
} | |||||
private String i18nDescriptionMessage(String permissionKey) { | |||||
return i18n.message(Locale.ENGLISH, PROPERTY_PREFIX + permissionKey + DESCRIPTION_SUFFIX, ""); | |||||
} | |||||
private String i18nName(String permissionKey) { | |||||
return i18n.message(Locale.ENGLISH, PROPERTY_PREFIX + permissionKey, permissionKey); | |||||
} | |||||
private int countGroups(DbSession dbSession, String permission) { | |||||
PermissionQuery query = PermissionQuery.builder().setPermission(permission).build(); | |||||
return dbClient.groupPermissionDao().countGroupsByQuery(dbSession, query); | |||||
} | |||||
private int countUsers(DbSession dbSession, PermissionQuery permissionQuery) { | |||||
return dbClient.userPermissionDao().countUsersByQuery(dbSession, permissionQuery); | |||||
} | |||||
private static PermissionQuery permissionQuery(String permissionKey) { | |||||
return PermissionQuery.builder() | |||||
.setPermission(permissionKey) | |||||
.withAtLeastOnePermission() | |||||
.build(); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.permission.ws; | |||||
import com.google.common.collect.FluentIterable; | |||||
import com.google.common.collect.Iterables; | |||||
import com.google.common.collect.Ordering; | |||||
import com.google.common.collect.Table; | |||||
import java.util.List; | |||||
import java.util.Set; | |||||
import org.sonar.api.utils.Paging; | |||||
import org.sonar.db.component.ComponentDto; | |||||
import static com.google.common.base.MoreObjects.firstNonNull; | |||||
import static com.google.common.base.Preconditions.checkState; | |||||
import static com.google.common.collect.ImmutableList.copyOf; | |||||
import static com.google.common.collect.ImmutableTable.copyOf; | |||||
class SearchProjectPermissionsData { | |||||
private final List<ComponentDto> rootComponents; | |||||
private final Paging paging; | |||||
private final Table<String, String, Integer> userCountByProjectUuidAndPermission; | |||||
private final Table<String, String, Integer> groupCountByProjectUuidAndPermission; | |||||
private SearchProjectPermissionsData(Builder builder) { | |||||
this.rootComponents = copyOf(builder.projects); | |||||
this.paging = builder.paging; | |||||
this.userCountByProjectUuidAndPermission = copyOf(builder.userCountByProjectUuidAndPermission); | |||||
this.groupCountByProjectUuidAndPermission = copyOf(builder.groupCountByProjectUuidAndPermission); | |||||
} | |||||
static Builder newBuilder() { | |||||
return new Builder(); | |||||
} | |||||
List<ComponentDto> rootComponents() { | |||||
return rootComponents; | |||||
} | |||||
Paging paging() { | |||||
return paging; | |||||
} | |||||
int userCount(String rootComponentUuid, String permission) { | |||||
return firstNonNull(userCountByProjectUuidAndPermission.get(rootComponentUuid, permission), 0); | |||||
} | |||||
int groupCount(String rootComponentUuid, String permission) { | |||||
return firstNonNull(groupCountByProjectUuidAndPermission.get(rootComponentUuid, permission), 0); | |||||
} | |||||
Set<String> permissions(String rootComponentUuid) { | |||||
return FluentIterable.from( | |||||
Iterables.concat( | |||||
userCountByProjectUuidAndPermission.row(rootComponentUuid).keySet(), | |||||
groupCountByProjectUuidAndPermission.row(rootComponentUuid).keySet())) | |||||
.toSortedSet(Ordering.natural()); | |||||
} | |||||
static class Builder { | |||||
private List<ComponentDto> projects; | |||||
private Paging paging; | |||||
private Table<String, String, Integer> userCountByProjectUuidAndPermission; | |||||
private Table<String, String, Integer> groupCountByProjectUuidAndPermission; | |||||
private Builder() { | |||||
// prevents instantiation outside main class | |||||
} | |||||
SearchProjectPermissionsData build() { | |||||
checkState(projects != null); | |||||
checkState(userCountByProjectUuidAndPermission != null); | |||||
checkState(groupCountByProjectUuidAndPermission != null); | |||||
return new SearchProjectPermissionsData(this); | |||||
} | |||||
Builder rootComponents(List<ComponentDto> projects) { | |||||
this.projects = projects; | |||||
return this; | |||||
} | |||||
Builder paging(Paging paging) { | |||||
this.paging = paging; | |||||
return this; | |||||
} | |||||
Builder userCountByProjectIdAndPermission(Table<String, String, Integer> userCountByProjectIdAndPermission) { | |||||
this.userCountByProjectUuidAndPermission = userCountByProjectIdAndPermission; | |||||
return this; | |||||
} | |||||
Builder groupCountByProjectIdAndPermission(Table<String, String, Integer> groupCountByProjectIdAndPermission) { | |||||
this.groupCountByProjectUuidAndPermission = groupCountByProjectIdAndPermission; | |||||
return this; | |||||
} | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.project.ws; | |||||
import com.google.common.collect.ImmutableList; | |||||
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; | |||||
import org.sonar.api.web.UserRole; | |||||
import org.sonar.db.DbClient; | |||||
import org.sonar.db.DbSession; | |||||
import org.sonar.db.component.ComponentKeyUpdaterDao; | |||||
import org.sonar.db.project.ProjectDto; | |||||
import org.sonar.server.component.ComponentFinder; | |||||
import org.sonar.server.component.ComponentService; | |||||
import org.sonar.server.user.UserSession; | |||||
import org.sonarqube.ws.Projects.BulkUpdateKeyWsResponse; | |||||
import static com.google.common.base.Preconditions.checkArgument; | |||||
import static org.sonar.server.exceptions.BadRequestException.checkRequest; | |||||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_BULK_UPDATE_KEY; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_DRY_RUN; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_FROM; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_TO; | |||||
public class BulkUpdateKeyAction implements ProjectsWsAction { | |||||
private final DbClient dbClient; | |||||
private final ComponentFinder componentFinder; | |||||
private final ComponentKeyUpdaterDao componentKeyUpdater; | |||||
private final ComponentService componentService; | |||||
private final UserSession userSession; | |||||
public BulkUpdateKeyAction(DbClient dbClient, ComponentFinder componentFinder, ComponentService componentService, UserSession userSession) { | |||||
this.dbClient = dbClient; | |||||
this.componentKeyUpdater = dbClient.componentKeyUpdaterDao(); | |||||
this.componentFinder = componentFinder; | |||||
this.componentService = componentService; | |||||
this.userSession = userSession; | |||||
} | |||||
@Override | |||||
public void define(WebService.NewController context) { | |||||
doDefine(context); | |||||
} | |||||
public WebService.NewAction doDefine(WebService.NewController context) { | |||||
WebService.NewAction action = context.createAction(ACTION_BULK_UPDATE_KEY) | |||||
.setDescription("Bulk update a project key and all its sub-components keys. " + | |||||
"The bulk update allows to replace a part of the current key by another string on the current project.<br>" + | |||||
"It's possible to simulate the bulk update by setting the parameter '%s' at true. No key is updated with a dry run.<br>" + | |||||
"Ex: to rename a project with key 'my_project' to 'my_new_project' and all its sub-components keys, call the WS with parameters:" + | |||||
"<ul>" + | |||||
" <li>%s: my_project</li>" + | |||||
" <li>%s: my_</li>" + | |||||
" <li>%s: my_new_</li>" + | |||||
"</ul>" + | |||||
"Requires one of the following permissions: " + | |||||
"<ul>" + | |||||
"<li>'Administer System'</li>" + | |||||
"<li>'Administer' rights on the specified project</li>" + | |||||
"</ul>", | |||||
PARAM_DRY_RUN, | |||||
PARAM_PROJECT, PARAM_FROM, PARAM_TO) | |||||
.setDeprecatedSince("7.6") | |||||
.setSince("6.1") | |||||
.setPost(true) | |||||
.setResponseExample(getClass().getResource("bulk_update_key-example.json")) | |||||
.setHandler(this); | |||||
action.createParam(PARAM_PROJECT) | |||||
.setDescription("Project key") | |||||
.setRequired(true) | |||||
.setExampleValue("my_old_project"); | |||||
action.createParam(PARAM_FROM) | |||||
.setDescription("String to match in components keys") | |||||
.setRequired(true) | |||||
.setExampleValue("_old"); | |||||
action.createParam(PARAM_TO) | |||||
.setDescription("String replacement in components keys") | |||||
.setRequired(true) | |||||
.setExampleValue("_new"); | |||||
action.createParam(PARAM_DRY_RUN) | |||||
.setDescription("Simulate bulk update. No component key is updated.") | |||||
.setBooleanPossibleValues() | |||||
.setDefaultValue(false); | |||||
return action; | |||||
} | |||||
@Override | |||||
public void handle(Request request, Response response) throws Exception { | |||||
writeProtobuf(doHandle(toWsRequest(request)), request, response); | |||||
} | |||||
private BulkUpdateKeyWsResponse doHandle(BulkUpdateKeyRequest request) { | |||||
try (DbSession dbSession = dbClient.openSession(false)) { | |||||
ProjectDto project = componentFinder.getProjectByKey(dbSession, request.getProjectKey()); | |||||
userSession.checkProjectPermission(UserRole.ADMIN, project); | |||||
Map<String, String> newKeysByOldKeys = componentKeyUpdater.simulateBulkUpdateKey(dbSession, project.getUuid(), request.getFrom(), request.getTo()); | |||||
Map<String, Boolean> newKeysWithDuplicateMap = componentKeyUpdater.checkComponentKeys(dbSession, ImmutableList.copyOf(newKeysByOldKeys.values())); | |||||
if (!request.isDryRun()) { | |||||
checkNoDuplicate(newKeysWithDuplicateMap); | |||||
bulkUpdateKey(dbSession, request, project); | |||||
} | |||||
return buildResponse(newKeysByOldKeys, newKeysWithDuplicateMap); | |||||
} | |||||
} | |||||
private static void checkNoDuplicate(Map<String, Boolean> newKeysWithDuplicateMap) { | |||||
newKeysWithDuplicateMap.forEach((key, value) -> checkRequest(!value, "Impossible to update key: a component with key \"%s\" already exists.", key)); | |||||
} | |||||
private void bulkUpdateKey(DbSession dbSession, BulkUpdateKeyRequest request, ProjectDto project) { | |||||
componentService.bulkUpdateKey(dbSession, project, request.getFrom(), request.getTo()); | |||||
} | |||||
private static BulkUpdateKeyWsResponse buildResponse(Map<String, String> newKeysByOldKeys, Map<String, Boolean> newKeysWithDuplicateMap) { | |||||
BulkUpdateKeyWsResponse.Builder response = BulkUpdateKeyWsResponse.newBuilder(); | |||||
newKeysByOldKeys.entrySet().stream() | |||||
// sort by old key | |||||
.sorted(Map.Entry.comparingByKey()) | |||||
.forEach( | |||||
entry -> { | |||||
String newKey = entry.getValue(); | |||||
response.addKeysBuilder() | |||||
.setKey(entry.getKey()) | |||||
.setNewKey(newKey) | |||||
.setDuplicate(newKeysWithDuplicateMap.getOrDefault(newKey, false)); | |||||
}); | |||||
return response.build(); | |||||
} | |||||
private static BulkUpdateKeyRequest toWsRequest(Request request) { | |||||
return BulkUpdateKeyRequest.builder() | |||||
.setProjectKey(request.mandatoryParam(PARAM_PROJECT)) | |||||
.setFrom(request.mandatoryParam(PARAM_FROM)) | |||||
.setTo(request.mandatoryParam(PARAM_TO)) | |||||
.setDryRun(request.mandatoryParamAsBoolean(PARAM_DRY_RUN)) | |||||
.build(); | |||||
} | |||||
private static class BulkUpdateKeyRequest { | |||||
private final String projectKey; | |||||
private final String from; | |||||
private final String to; | |||||
private final boolean dryRun; | |||||
public BulkUpdateKeyRequest(Builder builder) { | |||||
this.projectKey = builder.projectKey; | |||||
this.from = builder.from; | |||||
this.to = builder.to; | |||||
this.dryRun = builder.dryRun; | |||||
} | |||||
public String getProjectKey() { | |||||
return projectKey; | |||||
} | |||||
public String getFrom() { | |||||
return from; | |||||
} | |||||
public String getTo() { | |||||
return to; | |||||
} | |||||
public boolean isDryRun() { | |||||
return dryRun; | |||||
} | |||||
public static Builder builder() { | |||||
return new Builder(); | |||||
} | |||||
} | |||||
public static class Builder { | |||||
private String projectKey; | |||||
private String from; | |||||
private String to; | |||||
private boolean dryRun; | |||||
private Builder() { | |||||
// enforce method constructor | |||||
} | |||||
public Builder setProjectKey(String projectKey) { | |||||
this.projectKey = projectKey; | |||||
return this; | |||||
} | |||||
public Builder setFrom(String from) { | |||||
this.from = from; | |||||
return this; | |||||
} | |||||
public Builder setTo(String to) { | |||||
this.to = to; | |||||
return this; | |||||
} | |||||
public Builder setDryRun(boolean dryRun) { | |||||
this.dryRun = dryRun; | |||||
return this; | |||||
} | |||||
public BulkUpdateKeyRequest build() { | |||||
checkArgument(projectKey != null && !projectKey.isEmpty(), "The key must not be empty"); | |||||
checkArgument(from != null && !from.isEmpty(), "The string to match must not be empty"); | |||||
checkArgument(to != null && !to.isEmpty(), "The string replacement must not be empty"); | |||||
return new BulkUpdateKeyRequest(this); | |||||
} | |||||
} | |||||
} |
BulkDeleteAction.class, | BulkDeleteAction.class, | ||||
DeleteAction.class, | DeleteAction.class, | ||||
UpdateKeyAction.class, | UpdateKeyAction.class, | ||||
BulkUpdateKeyAction.class, | |||||
SearchMyProjectsAction.class, | SearchMyProjectsAction.class, | ||||
SearchAction.class, | SearchAction.class, | ||||
UpdateVisibilityAction.class, | UpdateVisibilityAction.class, |
import java.util.Set; | import java.util.Set; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import java.util.stream.Stream; | import java.util.stream.Stream; | ||||
import org.sonar.api.server.ws.Change; | |||||
import org.sonar.api.server.ws.Request; | |||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import org.sonar.server.exceptions.BadRequestException; | |||||
import org.sonar.server.qualitygate.Condition; | import org.sonar.server.qualitygate.Condition; | ||||
import org.sonar.server.ws.RemovedWebServiceHandler; | |||||
import static org.sonar.server.qualitygate.QualityGateConditionsUpdater.INVALID_METRIC_KEYS; | import static org.sonar.server.qualitygate.QualityGateConditionsUpdater.INVALID_METRIC_KEYS; | ||||
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.CONTROLLER_QUALITY_GATES; | import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.CONTROLLER_QUALITY_GATES; | ||||
action.define(controller); | action.define(controller); | ||||
} | } | ||||
// unset_default is no more authorized | |||||
controller.createAction("unset_default") | |||||
.setDescription("This webservice is no more available : a default quality gate is mandatory.") | |||||
.setSince("4.3") | |||||
.setDeprecatedSince("7.0") | |||||
.setPost(true) | |||||
.setHandler(RemovedWebServiceHandler.INSTANCE) | |||||
.setResponseExample(RemovedWebServiceHandler.INSTANCE.getResponseExample()) | |||||
.setChangelog( | |||||
new Change("7.0", "Unset a quality gate is no more authorized")); | |||||
controller.done(); | controller.done(); | ||||
} | } | ||||
.collect(Collectors.joining()); | .collect(Collectors.joining()); | ||||
} | } | ||||
static Long parseId(Request request, String paramName) { | |||||
try { | |||||
return Long.valueOf(request.mandatoryParam(paramName)); | |||||
} catch (NumberFormatException badFormat) { | |||||
throw BadRequestException.create(paramName + " must be a valid long value"); | |||||
} | |||||
} | |||||
private static Set<String> getPossibleOperators() { | private static Set<String> getPossibleOperators() { | ||||
return Stream.of(Condition.Operator.values()) | return Stream.of(Condition.Operator.values()) | ||||
.map(Condition.Operator::getDbValue) | .map(Condition.Operator::getDbValue) |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.source.ws; | |||||
import com.google.common.io.CharStreams; | |||||
import com.google.common.io.Resources; | |||||
import java.io.IOException; | |||||
import java.io.OutputStreamWriter; | |||||
import java.io.Reader; | |||||
import java.nio.charset.StandardCharsets; | |||||
import java.util.function.Consumer; | |||||
import org.sonar.api.server.ws.Request; | |||||
import org.sonar.api.server.ws.Response; | |||||
import org.sonar.api.server.ws.WebService; | |||||
import org.sonar.api.web.UserRole; | |||||
import org.sonar.db.DbClient; | |||||
import org.sonar.db.DbSession; | |||||
import org.sonar.db.component.ComponentDto; | |||||
import org.sonar.server.component.ComponentFinder; | |||||
import org.sonar.server.user.UserSession; | |||||
import static org.sonar.server.ws.KeyExamples.KEY_FILE_EXAMPLE_001; | |||||
public class HashAction implements SourcesWsAction { | |||||
private final DbClient dbClient; | |||||
private final UserSession userSession; | |||||
private final ComponentFinder componentFinder; | |||||
public HashAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) { | |||||
this.dbClient = dbClient; | |||||
this.userSession = userSession; | |||||
this.componentFinder = componentFinder; | |||||
} | |||||
@Override | |||||
public void define(WebService.NewController controller) { | |||||
WebService.NewAction action = controller.createAction("hash") | |||||
.setDescription("Show line line hashes for a given file. Require See Source Code permission on file's project<br/>") | |||||
.setSince("5.0") | |||||
.setInternal(true) | |||||
.setDeprecatedSince("7.7") | |||||
.setResponseExample(Resources.getResource(getClass(), "example-hash.txt")) | |||||
.setHandler(this); | |||||
action | |||||
.createParam("key") | |||||
.setRequired(true) | |||||
.setDescription("File key") | |||||
.setExampleValue(KEY_FILE_EXAMPLE_001); | |||||
} | |||||
@Override | |||||
public void handle(Request request, Response response) throws Exception { | |||||
try (DbSession session = dbClient.openSession(false)) { | |||||
String componentKey = request.mandatoryParam("key"); | |||||
ComponentDto component = componentFinder.getByKey(session, componentKey); | |||||
userSession.checkComponentPermission(UserRole.USER, component); | |||||
response.stream().setMediaType("text/plain"); | |||||
try (OutputStreamWriter writer = new OutputStreamWriter(response.stream().output(), StandardCharsets.UTF_8)) { | |||||
HashConsumer hashFunction = new HashConsumer(writer, componentKey); | |||||
dbClient.fileSourceDao().readLineHashesStream(session, component.uuid(), hashFunction); | |||||
if (!hashFunction.hasData()) { | |||||
response.noContent(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private static class HashConsumer implements Consumer<Reader> { | |||||
private final OutputStreamWriter writer; | |||||
private final String componentKey; | |||||
private boolean hasData = false; | |||||
public HashConsumer(OutputStreamWriter writer, String componentKey) { | |||||
this.writer = writer; | |||||
this.componentKey = componentKey; | |||||
} | |||||
@Override | |||||
public void accept(Reader input) { | |||||
try { | |||||
hasData = true; | |||||
CharStreams.copy(input, writer); | |||||
} catch (IOException e) { | |||||
throw new IllegalStateException(String.format("Can't read line hashes of file '%s'", componentKey), e); | |||||
} | |||||
} | |||||
public boolean hasData() { | |||||
return hasData; | |||||
} | |||||
} | |||||
} |
ShowAction.class, | ShowAction.class, | ||||
IssueSnippetsAction.class, | IssueSnippetsAction.class, | ||||
LinesAction.class, | LinesAction.class, | ||||
HashAction.class, | |||||
RawAction.class, | RawAction.class, | ||||
IndexAction.class, | IndexAction.class, | ||||
ScmAction.class | ScmAction.class |
{ | |||||
"keys": [ | |||||
{ | |||||
"key": "my_project", | |||||
"newKey": "my_new_project", | |||||
"duplicate": false | |||||
}, | |||||
{ | |||||
"key": "my_project:module_1", | |||||
"newKey": "my_new_project:module_1", | |||||
"duplicate": true | |||||
}, | |||||
{ | |||||
"key": "my_project:module_2", | |||||
"newKey": "my_new_project:module_2", | |||||
"duplicate": false | |||||
} | |||||
] | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.component; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.db.DbClient; | |||||
import org.sonar.db.DbSession; | |||||
import org.sonar.db.DbTester; | |||||
import org.sonar.db.component.ComponentDbTester; | |||||
import org.sonar.db.component.ComponentDto; | |||||
import org.sonar.server.es.TestProjectIndexers; | |||||
import org.sonar.server.project.ProjectLifeCycleListeners; | |||||
import org.sonar.server.tester.UserSessionRule; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||||
import static org.sonar.db.component.ComponentTesting.newModuleDto; | |||||
public class ComponentServiceTest { | |||||
@Rule | |||||
public final UserSessionRule userSession = UserSessionRule.standalone(); | |||||
@Rule | |||||
public final DbTester dbTester = DbTester.create(System2.INSTANCE); | |||||
private final ComponentDbTester componentDb = new ComponentDbTester(dbTester); | |||||
private final DbClient dbClient = dbTester.getDbClient(); | |||||
private final DbSession dbSession = dbTester.getSession(); | |||||
private final TestProjectIndexers projectIndexers = new TestProjectIndexers(); | |||||
private final ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class); | |||||
private final ComponentService underTest = new ComponentService(dbClient, userSession, projectIndexers, projectLifeCycleListeners); | |||||
@Test | |||||
public void bulk_update() { | |||||
ComponentDto project = componentDb.insertPublicProject(c -> c.setDbKey("my_project")); | |||||
ComponentDto module = componentDb.insertComponent(newModuleDto(project).setDbKey("my_project:root:module")); | |||||
ComponentDto inactiveModule = componentDb.insertComponent(newModuleDto(project).setDbKey("my_project:root:inactive_module").setEnabled(false)); | |||||
ComponentDto file = componentDb.insertComponent(newFileDto(module, null).setDbKey("my_project:root:module:src/File.xoo")); | |||||
ComponentDto inactiveFile = componentDb.insertComponent(newFileDto(module, null).setDbKey("my_project:root:module:src/InactiveFile.xoo").setEnabled(false)); | |||||
underTest.bulkUpdateKey(dbSession, componentDb.getProjectDto(project), "my_", "your_"); | |||||
assertComponentKeyUpdated(project.getDbKey(), "your_project"); | |||||
assertComponentKeyUpdated(module.getDbKey(), "your_project:root:module"); | |||||
assertComponentKeyUpdated(file.getDbKey(), "your_project:root:module:src/File.xoo"); | |||||
assertComponentKeyUpdated(inactiveModule.getDbKey(), "your_project:root:inactive_module"); | |||||
assertComponentKeyUpdated(inactiveFile.getDbKey(), "your_project:root:module:src/InactiveFile.xoo"); | |||||
} | |||||
private void assertComponentKeyUpdated(String oldKey, String newKey) { | |||||
assertThat(dbClient.componentDao().selectByKey(dbSession, oldKey)).isEmpty(); | |||||
assertThat(dbClient.componentDao().selectByKey(dbSession, newKey)).isPresent(); | |||||
} | |||||
} |
*/ | */ | ||||
package org.sonar.server.component; | package org.sonar.server.component; | ||||
import com.google.common.collect.ImmutableSet; | |||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.utils.System2; | import org.sonar.api.utils.System2; | ||||
import org.sonar.server.es.ProjectIndexer; | import org.sonar.server.es.ProjectIndexer; | ||||
import org.sonar.server.es.TestProjectIndexers; | import org.sonar.server.es.TestProjectIndexers; | ||||
import org.sonar.server.exceptions.ForbiddenException; | import org.sonar.server.exceptions.ForbiddenException; | ||||
import org.sonar.server.project.Project; | |||||
import org.sonar.server.project.ProjectLifeCycleListeners; | import org.sonar.server.project.ProjectLifeCycleListeners; | ||||
import org.sonar.server.project.RekeyedProject; | |||||
import org.sonar.server.tester.UserSessionRule; | import org.sonar.server.tester.UserSessionRule; | ||||
import static java.util.Collections.emptyList; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
import static org.mockito.Mockito.verify; | |||||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||||
import static org.sonar.db.component.ComponentTesting.newModuleDto; | |||||
public class ComponentServiceUpdateKeyTest { | public class ComponentServiceUpdateKeyTest { | ||||
.hasMessage("Malformed key for 'sample?root'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit."); | .hasMessage("Malformed key for 'sample?root'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit."); | ||||
} | } | ||||
@Test | |||||
public void bulk_update_key() { | |||||
ComponentDto project = componentDb.insertPublicProject(c -> c.setDbKey("my_project")); | |||||
ComponentDto module = componentDb.insertComponent(newModuleDto(project).setDbKey("my_project:root:module")); | |||||
ComponentDto inactiveModule = componentDb.insertComponent(newModuleDto(project).setDbKey("my_project:root:inactive_module").setEnabled(false)); | |||||
ComponentDto file = componentDb.insertComponent(newFileDto(module, null).setDbKey("my_project:root:module:src/File.xoo")); | |||||
ComponentDto inactiveFile = componentDb.insertComponent(newFileDto(module, null).setDbKey("my_project:root:module:src/InactiveFile.xoo").setEnabled(false)); | |||||
underTest.bulkUpdateKey(dbSession, componentDb.getProjectDto(project), "my_", "your_"); | |||||
assertComponentKeyUpdated(project.getDbKey(), "your_project"); | |||||
assertComponentKeyUpdated(module.getDbKey(), "your_project:root:module"); | |||||
assertComponentKeyUpdated(file.getDbKey(), "your_project:root:module:src/File.xoo"); | |||||
assertComponentKeyUpdated(inactiveModule.getDbKey(), "your_project:root:inactive_module"); | |||||
assertComponentKeyUpdated(inactiveFile.getDbKey(), "your_project:root:module:src/InactiveFile.xoo"); | |||||
verify(projectLifeCycleListeners).onProjectsRekeyed(ImmutableSet.of( | |||||
new RekeyedProject(new Project(project.uuid(), "your_project", project.name(), project.uuid(), emptyList()), "my_project"))); | |||||
} | |||||
@Test | |||||
public void bulk_update_key_with_branch_and_pr() { | |||||
ComponentDto project = componentDb.insertPublicProject(c -> c.setDbKey("my_project")); | |||||
ComponentDto branch = componentDb.insertProjectBranch(project); | |||||
ComponentDto module = componentDb.insertComponent(newModuleDto(branch).setDbKey("my_project:root:module")); | |||||
ComponentDto file = componentDb.insertComponent(newFileDto(module, null).setDbKey("my_project:root:module:src/File.xoo")); | |||||
underTest.bulkUpdateKey(dbSession, componentDb.getProjectDto(project), "my_", "your_"); | |||||
assertComponentKeyUpdated(project.getDbKey(), "your_project"); | |||||
assertComponentKeyUpdated(module.getDbKey(), "your_project:root:module"); | |||||
assertComponentKeyUpdated(file.getDbKey(), "your_project:root:module:src/File.xoo"); | |||||
verify(projectLifeCycleListeners).onProjectsRekeyed(ImmutableSet.of( | |||||
new RekeyedProject(new Project(project.uuid(), "your_project", project.name(), project.uuid(), emptyList()), "my_project"))); | |||||
} | |||||
private void assertComponentKeyUpdated(String oldKey, String newKey) { | |||||
assertThat(dbClient.componentDao().selectByKey(dbSession, oldKey)).isEmpty(); | |||||
assertThat(dbClient.componentDao().selectByKey(dbSession, newKey)).isPresent(); | |||||
} | |||||
private ComponentDto insertSampleProject() { | private ComponentDto insertSampleProject() { | ||||
return insertProject("sample:root"); | return insertProject("sample:root"); | ||||
} | } |
import static org.sonar.db.component.ComponentTesting.newFileDto; | import static org.sonar.db.component.ComponentTesting.newFileDto; | ||||
import static org.sonar.db.component.ComponentTesting.newModuleDto; | import static org.sonar.db.component.ComponentTesting.newModuleDto; | ||||
import static org.sonar.db.component.ComponentTesting.newProjectCopy; | import static org.sonar.db.component.ComponentTesting.newProjectCopy; | ||||
import static org.sonar.db.component.ComponentTesting.newSubPortfolio; | |||||
import static org.sonar.db.component.ComponentTesting.newPortfolio; | |||||
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH; | import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_MODULE_UUIDS; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD; | ||||
.setParam(PARAM_COMPONENT_KEYS, module1.getKey()) | .setParam(PARAM_COMPONENT_KEYS, module1.getKey()) | ||||
.executeProtobuf(SearchWsResponse.class).getIssuesList()).extracting(Issue::getKey) | .executeProtobuf(SearchWsResponse.class).getIssuesList()).extracting(Issue::getKey) | ||||
.containsExactlyInAnyOrder(issue1.getKey()); | .containsExactlyInAnyOrder(issue1.getKey()); | ||||
assertThat(ws.newRequest() | |||||
.setParam(PARAM_MODULE_UUIDS, module1.uuid()) | |||||
.executeProtobuf(SearchWsResponse.class).getIssuesList()).extracting(Issue::getKey) | |||||
.containsExactlyInAnyOrder(issue1.getKey()); | |||||
} | } | ||||
@Test | @Test | ||||
.execute() | .execute() | ||||
.assertJson(this.getClass(), "no_issue.json"); | .assertJson(this.getClass(), "no_issue.json"); | ||||
ws.newRequest() | |||||
.setParam(PARAM_MODULE_UUIDS, module1.uuid()) | |||||
.setParam(PARAM_DIRECTORIES, "src/main/java/dir") | |||||
.execute() | |||||
.assertJson(this.getClass(), "search_by_directory_uuid.json"); | |||||
ws.newRequest() | |||||
.setParam(PARAM_MODULE_UUIDS, module2.uuid()) | |||||
.setParam(PARAM_DIRECTORIES, "src/main/java/dir") | |||||
.execute() | |||||
.assertJson(this.getClass(), "no_issue.json"); | |||||
ws.newRequest() | ws.newRequest() | ||||
.setParam(PARAM_DIRECTORIES, "src/main/java/dir") | .setParam(PARAM_DIRECTORIES, "src/main/java/dir") | ||||
.execute() | .execute() |
import static org.sonar.db.component.ComponentTesting.newFileDto; | import static org.sonar.db.component.ComponentTesting.newFileDto; | ||||
import static org.sonar.db.component.ComponentTesting.newModuleDto; | import static org.sonar.db.component.ComponentTesting.newModuleDto; | ||||
import static org.sonar.server.tester.UserSessionRule.standalone; | import static org.sonar.server.tester.UserSessionRule.standalone; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_UUIDS; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES; | ||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_MODULE_UUIDS; | |||||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; | import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECTS; | ||||
public class SearchActionFacetsTest { | public class SearchActionFacetsTest { | ||||
tuple("assignees", of("", 0L, user.getLogin(), 1L))); | tuple("assignees", of("", 0L, user.getLogin(), 1L))); | ||||
} | } | ||||
@Test | |||||
public void display_facets_in_effort_mode() { | |||||
ComponentDto project = db.components().insertPublicProject(); | |||||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||||
RuleDefinitionDto rule = db.rules().insertIssueRule(); | |||||
db.issues().insertIssue(rule, project, file, i -> i | |||||
.setSeverity("MAJOR") | |||||
.setStatus("OPEN") | |||||
.setType(RuleType.CODE_SMELL) | |||||
.setEffort(10L) | |||||
.setAssigneeUuid(null)); | |||||
indexPermissions(); | |||||
indexIssues(); | |||||
SearchWsResponse response = ws.newRequest() | |||||
.setParam(PARAM_COMPONENT_KEYS, project.getKey()) | |||||
.setParam(FACETS, "severities,statuses,resolutions,rules,types,languages,projects,files,assignees") | |||||
.setParam("facetMode", FACET_MODE_EFFORT) | |||||
.executeProtobuf(SearchWsResponse.class); | |||||
Map<String, Number> expectedStatuses = ImmutableMap.<String, Number>builder().put("OPEN", 10L).put("CONFIRMED", 0L) | |||||
.put("REOPENED", 0L).put("RESOLVED", 0L).put("CLOSED", 0L).build(); | |||||
assertThat(response.getFacets().getFacetsList()) | |||||
.extracting(Common.Facet::getProperty, facet -> facet.getValuesList().stream().collect(toMap(FacetValue::getVal, FacetValue::getCount))) | |||||
.containsExactlyInAnyOrder( | |||||
tuple("severities", of("INFO", 0L, "MINOR", 0L, "MAJOR", 10L, "CRITICAL", 0L, "BLOCKER", 0L)), | |||||
tuple("statuses", expectedStatuses), | |||||
tuple("resolutions", of("", 10L, "FALSE-POSITIVE", 0L, "FIXED", 0L, "REMOVED", 0L, "WONTFIX", 0L)), | |||||
tuple("rules", of(rule.getKey().toString(), 10L)), | |||||
tuple("types", of("CODE_SMELL", 10L, "BUG", 0L, "VULNERABILITY", 0L)), | |||||
tuple("languages", of(rule.getLanguage(), 10L)), | |||||
tuple("projects", of(project.getKey(), 10L)), | |||||
tuple("files", of(file.path(), 10L)), | |||||
tuple("assignees", of("", 10L))); | |||||
} | |||||
@Test | @Test | ||||
public void display_projects_facet() { | public void display_projects_facet() { | ||||
ComponentDto project = db.components().insertPublicProject(); | ComponentDto project = db.components().insertPublicProject(); | ||||
.containsExactlyInAnyOrder(tuple("projects", of(project1.getKey(), 1L, project2.getKey(), 1L, project3.getKey(), 1L))); | .containsExactlyInAnyOrder(tuple("projects", of(project1.getKey(), 1L, project2.getKey(), 1L, project3.getKey(), 1L))); | ||||
} | } | ||||
@Test | |||||
public void display_moduleUuids_facet_using_project() { | |||||
ComponentDto project = db.components().insertPublicProject(); | |||||
ComponentDto module = db.components().insertComponent(newModuleDto(project)); | |||||
ComponentDto subModule1 = db.components().insertComponent(newModuleDto(module)); | |||||
ComponentDto subModule2 = db.components().insertComponent(newModuleDto(module)); | |||||
ComponentDto subModule3 = db.components().insertComponent(newModuleDto(module)); | |||||
ComponentDto file1 = db.components().insertComponent(newFileDto(subModule1)); | |||||
ComponentDto file2 = db.components().insertComponent(newFileDto(subModule2)); | |||||
RuleDefinitionDto rule = db.rules().insertIssueRule(); | |||||
db.issues().insertIssue(rule, project, file1); | |||||
db.issues().insertIssue(rule, project, file2); | |||||
indexPermissions(); | |||||
indexIssues(); | |||||
SearchWsResponse response = ws.newRequest() | |||||
.setParam(PARAM_PROJECTS, project.getKey()) | |||||
.setParam(PARAM_COMPONENT_UUIDS, module.uuid()) | |||||
.setParam(PARAM_MODULE_UUIDS, subModule1.uuid()) | |||||
.setParam(WebService.Param.FACETS, "moduleUuids") | |||||
.executeProtobuf(SearchWsResponse.class); | |||||
assertThat(response.getFacets().getFacetsList()) | |||||
.extracting(Common.Facet::getProperty, facet -> facet.getValuesList().stream().collect(toMap(FacetValue::getVal, FacetValue::getCount))) | |||||
.containsExactlyInAnyOrder(tuple("moduleUuids", of(subModule1.uuid(), 1L, subModule2.uuid(), 1L))); | |||||
} | |||||
@Test | |||||
public void fail_to_display_module_facet_when_no_project_is_set() { | |||||
ComponentDto project = db.components().insertPublicProject(); | |||||
ComponentDto module = db.components().insertComponent(newModuleDto(project)); | |||||
ComponentDto file = db.components().insertComponent(newFileDto(module, null)); | |||||
RuleDefinitionDto rule = db.rules().insertIssueRule(); | |||||
db.issues().insertIssue(rule, project, file); | |||||
indexPermissions(); | |||||
indexIssues(); | |||||
expectedException.expect(IllegalArgumentException.class); | |||||
expectedException.expectMessage("Facet(s) 'moduleUuids' require to also filter by project"); | |||||
ws.newRequest() | |||||
.setParam(PARAM_COMPONENT_UUIDS, module.uuid()) | |||||
.setParam(WebService.Param.FACETS, "moduleUuids") | |||||
.execute(); | |||||
} | |||||
@Test | @Test | ||||
public void display_directory_facet_using_project() { | public void display_directory_facet_using_project() { | ||||
ComponentDto project = db.components().insertPublicProject(); | ComponentDto project = db.components().insertPublicProject(); | ||||
SearchWsResponse response = ws.newRequest() | SearchWsResponse response = ws.newRequest() | ||||
.setParam(PARAM_PROJECTS, project1.getKey() + "," + project2.getKey()) | .setParam(PARAM_PROJECTS, project1.getKey() + "," + project2.getKey()) | ||||
.setParam(PARAM_MODULE_UUIDS, module1.uuid() + "," + module2.uuid()) | |||||
.setParam(PARAM_FILES, file1.path() + "," + file2.path()) | .setParam(PARAM_FILES, file1.path() + "," + file2.path()) | ||||
.setParam("rules", rule1.getKey().toString() + "," + rule2.getKey().toString()) | .setParam("rules", rule1.getKey().toString() + "," + rule2.getKey().toString()) | ||||
.setParam("severities", "MAJOR,MINOR") | .setParam("severities", "MAJOR,MINOR") | ||||
tuple("types", of("CODE_SMELL", 1L, "BUG", 0L, "VULNERABILITY", 0L)), | tuple("types", of("CODE_SMELL", 1L, "BUG", 0L, "VULNERABILITY", 0L)), | ||||
tuple("languages", of(rule1.getLanguage(), 1L, rule2.getLanguage(), 0L)), | tuple("languages", of(rule1.getLanguage(), 1L, rule2.getLanguage(), 0L)), | ||||
tuple("projects", of(project1.getKey(), 1L, project2.getKey(), 0L)), | tuple("projects", of(project1.getKey(), 1L, project2.getKey(), 0L)), | ||||
tuple("moduleUuids", of(module1.uuid(), 1L, module2.uuid(), 0L)), | |||||
tuple("moduleUuids", of(module1.uuid(), 1L)), | |||||
tuple("files", of(file1.path(), 1L, file2.path(), 0L)), | tuple("files", of(file1.path(), 1L, file2.path(), 0L)), | ||||
tuple("assignees", of("", 0L, user1.getLogin(), 1L, user2.getLogin(), 0L))); | tuple("assignees", of("", 0L, user1.getLogin(), 1L, user2.getLogin(), 0L))); | ||||
} | } |
.assertJson(this.getClass(), "filter_by_main_scope_2.json"); | .assertJson(this.getClass(), "filter_by_main_scope_2.json"); | ||||
} | } | ||||
@Test | |||||
public void search_by_deprecated_authors_parameter() { | |||||
ComponentDto project = db.components().insertPublicProject(); | |||||
ComponentDto file = db.components().insertComponent(newFileDto(project, null)); | |||||
RuleDefinitionDto rule = db.rules().insertIssueRule(); | |||||
IssueDto issue1 = db.issues().insertIssue(rule, project, file, i -> i.setAuthorLogin("leia")); | |||||
IssueDto issue2 = db.issues().insertIssue(rule, project, file, i -> i.setAuthorLogin("luke")); | |||||
indexPermissions(); | |||||
indexIssues(); | |||||
SearchWsResponse response = ws.newRequest() | |||||
.setParam("authors", "leia") | |||||
.setParam(FACETS, "authors") | |||||
.executeProtobuf(SearchWsResponse.class); | |||||
assertThat(response.getIssuesList()).extracting(Issue::getKey).containsExactlyInAnyOrder(issue1.getKey()); | |||||
Common.Facet facet = response.getFacets().getFacetsList().get(0); | |||||
assertThat(facet.getProperty()).isEqualTo("authors"); | |||||
assertThat(facet.getValuesList()) | |||||
.extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) | |||||
.containsExactlyInAnyOrder( | |||||
tuple("leia", 1L), | |||||
tuple("luke", 1L)); | |||||
// Deprecated parameter 'authors' will be ignored if new parameter 'author' is set | |||||
assertThat(ws.newRequest() | |||||
.setMultiParam("author", singletonList("luke")) | |||||
// This parameter will be ignored | |||||
.setParam("authors", "leia") | |||||
.executeProtobuf(SearchWsResponse.class).getIssuesList()) | |||||
.extracting(Issue::getKey) | |||||
.containsExactlyInAnyOrder(issue2.getKey()); | |||||
} | |||||
@Test | @Test | ||||
public void sort_by_updated_at() { | public void sort_by_updated_at() { | ||||
RuleDto rule = newIssueRule(); | RuleDto rule = newIssueRule(); | ||||
assertThat(def.responseExampleAsString()).isNotEmpty(); | assertThat(def.responseExampleAsString()).isNotEmpty(); | ||||
assertThat(def.params()).extracting("key").containsExactlyInAnyOrder( | assertThat(def.params()).extracting("key").containsExactlyInAnyOrder( | ||||
"additionalFields", "asc", "assigned", "assignees", "authors", "author", "componentKeys", "branch", "pullRequest", "createdAfter", "createdAt", | |||||
"createdBefore", "createdInLast", "directories", "facetMode", "facets", "files", "issues", "scopes", "languages", "moduleUuids", "onComponentOnly", | |||||
"additionalFields", "asc", "assigned", "assignees", "author", "componentKeys", "branch", "pullRequest", "createdAfter", "createdAt", | |||||
"createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly", | |||||
"p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", "statuses", "tags", "types", "owaspTop10", "sansTop25", | "p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "sinceLeakPeriod", "statuses", "tags", "types", "owaspTop10", "sansTop25", | ||||
"cwe", "sonarsourceSecurity", "timeZone"); | "cwe", "sonarsourceSecurity", "timeZone"); | ||||
public void verify_count_of_added_components() { | public void verify_count_of_added_components() { | ||||
ComponentContainer container = new ComponentContainer(); | ComponentContainer container = new ComponentContainer(); | ||||
new PermissionsWsModule().configure(container); | new PermissionsWsModule().configure(container); | ||||
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 27); | |||||
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 26); | |||||
} | } | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.permission.ws; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
import org.sonar.api.resources.Qualifiers; | |||||
import org.sonar.api.resources.ResourceTypes; | |||||
import org.sonar.core.permission.GlobalPermissions; | |||||
import org.sonar.db.component.ResourceTypesRule; | |||||
import org.sonar.db.user.UserDto; | |||||
import org.sonar.server.exceptions.ForbiddenException; | |||||
import org.sonar.server.exceptions.UnauthorizedException; | |||||
import org.sonar.server.l18n.I18nRule; | |||||
import org.sonar.server.permission.PermissionService; | |||||
import org.sonar.server.permission.PermissionServiceImpl; | |||||
import org.sonarqube.ws.Permissions; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; | |||||
import static org.sonar.db.permission.GlobalPermission.SCAN; | |||||
public class SearchGlobalPermissionsActionTest extends BasePermissionWsTest<SearchGlobalPermissionsAction> { | |||||
private I18nRule i18n = new I18nRule(); | |||||
private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); | |||||
private PermissionService permissionService = new PermissionServiceImpl(resourceTypes); | |||||
@Override | |||||
protected SearchGlobalPermissionsAction buildWsAction() { | |||||
return new SearchGlobalPermissionsAction(db.getDbClient(), userSession, i18n, permissionService); | |||||
} | |||||
@Before | |||||
public void setUp() { | |||||
initI18nMessages(); | |||||
} | |||||
@Test | |||||
public void search() { | |||||
loginAsAdmin(); | |||||
UserDto user = db.users().insertUser(); | |||||
db.users().insertPermissionOnUser(user, SCAN); | |||||
Permissions.WsSearchGlobalPermissionsResponse result = newRequest() | |||||
.executeProtobuf(Permissions.WsSearchGlobalPermissionsResponse.class); | |||||
assertThat(result.getPermissionsCount()).isEqualTo(GlobalPermissions.ALL.size()); | |||||
for (Permissions.Permission permission : result.getPermissionsList()) { | |||||
if (permission.getKey().equals(SCAN_EXECUTION)) { | |||||
assertThat(permission.getUsersCount()).isEqualTo(1); | |||||
} else { | |||||
assertThat(permission.getUsersCount()).isZero(); | |||||
} | |||||
} | |||||
} | |||||
@Test | |||||
public void supports_protobuf_response() { | |||||
loginAsAdmin(); | |||||
Permissions.WsSearchGlobalPermissionsResponse result = newRequest() | |||||
.executeProtobuf(Permissions.WsSearchGlobalPermissionsResponse.class); | |||||
assertThat(result).isNotNull(); | |||||
} | |||||
@Test | |||||
public void fail_if_not_admin() { | |||||
userSession.logIn(); | |||||
expectedException.expect(ForbiddenException.class); | |||||
newRequest() | |||||
.execute(); | |||||
} | |||||
@Test | |||||
public void fail_if_not_logged_in() { | |||||
userSession.anonymous(); | |||||
expectedException.expect(UnauthorizedException.class); | |||||
newRequest().execute(); | |||||
} | |||||
private void initI18nMessages() { | |||||
i18n.put("global_permissions.admin", "Administer System"); | |||||
i18n.put("global_permissions.admin.desc", "Ability to perform all administration functions for the instance: " + | |||||
"global configuration and personalization of default dashboards."); | |||||
i18n.put("global_permissions.profileadmin", "Administer Quality Profiles"); | |||||
i18n.put("global_permissions.profileadmin.desc", "Ability to perform any action on the quality profiles."); | |||||
i18n.put("global_permissions.gateadmin", "Administer Quality Gates"); | |||||
i18n.put("global_permissions.gateadmin.desc", "Ability to perform any action on the quality gates."); | |||||
i18n.put("global_permissions.scan", "Execute Analysis"); | |||||
i18n.put("global_permissions.scan.desc", "Ability to execute analyses, and to get all settings required to perform the analysis, " + | |||||
"even the secured ones like the scm account password, the jira account password, and so on."); | |||||
i18n.put("global_permissions.provisioning", "Create Projects"); | |||||
i18n.put("global_permissions.provisioning.desc", "Ability to initialize project structure before first analysis."); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.permission.ws; | |||||
import com.google.common.collect.HashBasedTable; | |||||
import java.util.Collections; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.junit.rules.ExpectedException; | |||||
public class SearchProjectPermissionsDataTest { | |||||
@Rule | |||||
public ExpectedException expectedException = ExpectedException.none(); | |||||
@Test | |||||
public void fail_if_no_projects() { | |||||
expectedException.expect(IllegalStateException.class); | |||||
SearchProjectPermissionsData.newBuilder() | |||||
.groupCountByProjectIdAndPermission(HashBasedTable.create()) | |||||
.userCountByProjectIdAndPermission(HashBasedTable.create()) | |||||
.build(); | |||||
} | |||||
@Test | |||||
public void fail_if_no_group_count() { | |||||
expectedException.expect(IllegalStateException.class); | |||||
SearchProjectPermissionsData.newBuilder() | |||||
.rootComponents(Collections.emptyList()) | |||||
.userCountByProjectIdAndPermission(HashBasedTable.create()) | |||||
.build(); | |||||
} | |||||
@Test | |||||
public void fail_if_no_user_count() { | |||||
expectedException.expect(IllegalStateException.class); | |||||
SearchProjectPermissionsData.newBuilder() | |||||
.rootComponents(Collections.emptyList()) | |||||
.groupCountByProjectIdAndPermission(HashBasedTable.create()) | |||||
.build(); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.project.ws; | |||||
import javax.annotation.Nullable; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.junit.rules.ExpectedException; | |||||
import org.sonar.api.server.ws.WebService; | |||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.api.web.UserRole; | |||||
import org.sonar.db.DbClient; | |||||
import org.sonar.db.DbSession; | |||||
import org.sonar.db.DbTester; | |||||
import org.sonar.db.component.ComponentDbTester; | |||||
import org.sonar.db.component.ComponentDto; | |||||
import org.sonar.server.component.ComponentFinder; | |||||
import org.sonar.server.component.ComponentService; | |||||
import org.sonar.server.component.TestComponentFinder; | |||||
import org.sonar.server.es.EsTester; | |||||
import org.sonar.server.exceptions.BadRequestException; | |||||
import org.sonar.server.exceptions.ForbiddenException; | |||||
import org.sonar.server.exceptions.NotFoundException; | |||||
import org.sonar.server.tester.UserSessionRule; | |||||
import org.sonar.server.ws.TestRequest; | |||||
import org.sonar.server.ws.WsActionTester; | |||||
import org.sonarqube.ws.Projects.BulkUpdateKeyWsResponse; | |||||
import org.sonarqube.ws.Projects.BulkUpdateKeyWsResponse.Key; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.assertj.core.api.Assertions.tuple; | |||||
import static org.mockito.ArgumentMatchers.any; | |||||
import static org.mockito.ArgumentMatchers.eq; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.verify; | |||||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||||
import static org.sonar.db.component.ComponentTesting.newModuleDto; | |||||
import static org.sonar.test.JsonAssert.assertJson; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_DRY_RUN; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_FROM; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT; | |||||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_TO; | |||||
public class BulkUpdateKeyActionTest { | |||||
private static final String MY_PROJECT_KEY = "my_project"; | |||||
private static final String FROM = "my_"; | |||||
private static final String TO = "your_"; | |||||
private System2 system2 = System2.INSTANCE; | |||||
@Rule | |||||
public ExpectedException expectedException = ExpectedException.none(); | |||||
@Rule | |||||
public UserSessionRule userSession = UserSessionRule.standalone().logIn().setRoot(); | |||||
@Rule | |||||
public EsTester es = EsTester.create(); | |||||
@Rule | |||||
public DbTester db = DbTester.create(system2); | |||||
private ComponentDbTester componentDb = new ComponentDbTester(db); | |||||
private DbClient dbClient = db.getDbClient(); | |||||
private ComponentFinder componentFinder = TestComponentFinder.from(db); | |||||
private ComponentService componentService = mock(ComponentService.class); | |||||
private WsActionTester ws = new WsActionTester( | |||||
new BulkUpdateKeyAction(dbClient, componentFinder, componentService, userSession)); | |||||
@Test | |||||
public void json_example() { | |||||
ComponentDto project = componentDb.insertPrivateProject(c -> c.setDbKey("my_project")); | |||||
componentDb.insertComponent(newModuleDto(project).setDbKey("my_project:module_1")); | |||||
ComponentDto anotherProject = componentDb.insertPrivateProject(c -> c.setDbKey("another_project")); | |||||
componentDb.insertComponent(newModuleDto(anotherProject).setDbKey("my_new_project:module_1")); | |||||
ComponentDto module2 = componentDb.insertComponent(newModuleDto(project).setDbKey("my_project:module_2")); | |||||
componentDb.insertComponent(newFileDto(module2, null)); | |||||
String result = ws.newRequest() | |||||
.setParam(PARAM_PROJECT, "my_project") | |||||
.setParam(PARAM_FROM, "my_") | |||||
.setParam(PARAM_TO, "my_new_") | |||||
.setParam(PARAM_DRY_RUN, String.valueOf(true)) | |||||
.execute().getInput(); | |||||
assertJson(result).withStrictArrayOrder().isSimilarTo(getClass().getResource("bulk_update_key-example.json")); | |||||
} | |||||
@Test | |||||
public void dry_run_by_key() { | |||||
insertMyProject(); | |||||
BulkUpdateKeyWsResponse result = callDryRunByKey(MY_PROJECT_KEY, FROM, TO); | |||||
assertThat(result.getKeysCount()).isEqualTo(1); | |||||
assertThat(result.getKeys(0).getNewKey()).isEqualTo("your_project"); | |||||
} | |||||
@Test | |||||
public void bulk_update_project_key() { | |||||
ComponentDto project = insertMyProject(); | |||||
ComponentDto module = componentDb.insertComponent(newModuleDto(project).setDbKey("my_project:root:module")); | |||||
ComponentDto inactiveModule = componentDb.insertComponent(newModuleDto(project).setDbKey("my_project:root:inactive_module").setEnabled(false)); | |||||
ComponentDto file = componentDb.insertComponent(newFileDto(module, null).setDbKey("my_project:root:module:src/File.xoo")); | |||||
ComponentDto inactiveFile = componentDb.insertComponent(newFileDto(module, null).setDbKey("my_project:root:module:src/InactiveFile.xoo").setEnabled(false)); | |||||
BulkUpdateKeyWsResponse result = callByKey(project.getDbKey(), FROM, TO); | |||||
assertThat(result.getKeysCount()).isEqualTo(2); | |||||
assertThat(result.getKeysList()).extracting(Key::getKey, Key::getNewKey, Key::getDuplicate) | |||||
.containsExactly( | |||||
tuple(project.getDbKey(), "your_project", false), | |||||
tuple(module.getDbKey(), "your_project:root:module", false)); | |||||
verify(componentService).bulkUpdateKey(any(DbSession.class), eq(componentDb.getProjectDto(project)), eq(FROM), eq(TO)); | |||||
} | |||||
@Test | |||||
public void bulk_update_provisioned_project_key() { | |||||
String newKey = "provisionedProject2"; | |||||
ComponentDto provisionedProject = componentDb.insertPrivateProject(); | |||||
callByKey(provisionedProject.getDbKey(), provisionedProject.getDbKey(), newKey); | |||||
verify(componentService).bulkUpdateKey(any(DbSession.class), eq(componentDb.getProjectDto(provisionedProject)), eq(provisionedProject.getDbKey()), eq(newKey)); | |||||
} | |||||
@Test | |||||
public void fail_to_bulk_update_key_using_branch_db_key() { | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
ComponentDto branch = db.components().insertProjectBranch(project); | |||||
userSession.addProjectPermission(UserRole.USER, project); | |||||
expectedException.expect(NotFoundException.class); | |||||
expectedException.expectMessage(String.format("Project '%s' not found", branch.getDbKey())); | |||||
callByKey(branch.getDbKey(), FROM, TO); | |||||
} | |||||
@Test | |||||
public void fail_to_bulk_if_a_component_already_exists_with_the_same_key() { | |||||
componentDb.insertPrivateProject(c -> c.setDbKey("my_project")); | |||||
componentDb.insertPrivateProject(c -> c.setDbKey("your_project")); | |||||
expectedException.expect(BadRequestException.class); | |||||
expectedException.expectMessage("Impossible to update key: a component with key \"your_project\" already exists."); | |||||
callByKey("my_project", "my_", "your_"); | |||||
} | |||||
@Test | |||||
public void fail_to_bulk_update_with_invalid_new_key() { | |||||
insertMyProject(); | |||||
expectedException.expect(IllegalArgumentException.class); | |||||
expectedException.expectMessage("Malformed key for 'my?project'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit."); | |||||
callByKey(MY_PROJECT_KEY, FROM, "my?"); | |||||
} | |||||
@Test | |||||
public void fail_to_dry_bulk_update_with_invalid_new_key() { | |||||
insertMyProject(); | |||||
expectedException.expect(IllegalArgumentException.class); | |||||
expectedException.expectMessage("Malformed key for 'my?project'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit."); | |||||
callDryRunByKey(MY_PROJECT_KEY, FROM, "my?"); | |||||
} | |||||
@Test | |||||
public void fail_to_bulk_update_if_not_project_or_module() { | |||||
ComponentDto project = insertMyProject(); | |||||
ComponentDto file = componentDb.insertComponent(newFileDto(project, null)); | |||||
expectedException.expect(NotFoundException.class); | |||||
expectedException.expectMessage(String.format("Project '%s' not found", file.getDbKey())); | |||||
callByKey(file.getDbKey(), FROM, TO); | |||||
} | |||||
@Test | |||||
public void fail_if_from_string_is_not_provided() { | |||||
expectedException.expect(IllegalArgumentException.class); | |||||
ComponentDto project = insertMyProject(); | |||||
callDryRunByKey(project.getDbKey(), null, TO); | |||||
} | |||||
@Test | |||||
public void fail_if_to_string_is_not_provided() { | |||||
expectedException.expect(IllegalArgumentException.class); | |||||
ComponentDto project = insertMyProject(); | |||||
callDryRunByKey(project.getDbKey(), FROM, null); | |||||
} | |||||
@Test | |||||
public void fail_if_key_not_provided() { | |||||
expectedException.expect(IllegalArgumentException.class); | |||||
call(null, FROM, TO, false); | |||||
} | |||||
@Test | |||||
public void fail_if_project_does_not_exist() { | |||||
expectedException.expect(NotFoundException.class); | |||||
callDryRunByKey("UNKNOWN_KEY", FROM, TO); | |||||
} | |||||
@Test | |||||
public void throw_ForbiddenException_if_not_project_administrator() { | |||||
userSession.logIn(); | |||||
ComponentDto project = insertMyProject(); | |||||
expectedException.expect(ForbiddenException.class); | |||||
callDryRunByKey(project.getDbKey(), FROM, TO); | |||||
} | |||||
@Test | |||||
public void fail_when_using_branch_db_key() { | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
userSession.logIn().addProjectPermission(UserRole.USER, project); | |||||
ComponentDto branch = db.components().insertProjectBranch(project); | |||||
expectedException.expect(NotFoundException.class); | |||||
expectedException.expectMessage(String.format("Project '%s' not found", branch.getDbKey())); | |||||
callByKey(branch.getDbKey(), FROM, TO); | |||||
} | |||||
@Test | |||||
public void api_definition() { | |||||
WebService.Action definition = ws.getDef(); | |||||
assertThat(definition.isPost()).isTrue(); | |||||
assertThat(definition.since()).isEqualTo("6.1"); | |||||
assertThat(definition.key()).isEqualTo("bulk_update_key"); | |||||
assertThat(definition.params()) | |||||
.hasSize(4) | |||||
.extracting(WebService.Param::key) | |||||
.containsOnlyOnce("project", "from", "to", "dryRun"); | |||||
} | |||||
private ComponentDto insertMyProject() { | |||||
return componentDb.insertPublicProject(c -> c.setDbKey(MY_PROJECT_KEY)); | |||||
} | |||||
private BulkUpdateKeyWsResponse callDryRunByKey(@Nullable String key, @Nullable String from, @Nullable String to) { | |||||
return call(key, from, to, true); | |||||
} | |||||
private BulkUpdateKeyWsResponse callByKey(@Nullable String key, @Nullable String from, @Nullable String to) { | |||||
return call(key, from, to, false); | |||||
} | |||||
private BulkUpdateKeyWsResponse call(@Nullable String key, @Nullable String from, @Nullable String to, @Nullable Boolean dryRun) { | |||||
TestRequest request = ws.newRequest(); | |||||
if (key != null) { | |||||
request.setParam(PARAM_PROJECT, key); | |||||
} | |||||
if (from != null) { | |||||
request.setParam(PARAM_FROM, from); | |||||
} | |||||
if (to != null) { | |||||
request.setParam(PARAM_TO, to); | |||||
} | |||||
if (dryRun != null) { | |||||
request.setParam(PARAM_DRY_RUN, String.valueOf(dryRun)); | |||||
} | |||||
return request.executeProtobuf(BulkUpdateKeyWsResponse.class); | |||||
} | |||||
} |
public void verify_count_of_added_components_on_SonarQube() { | public void verify_count_of_added_components_on_SonarQube() { | ||||
ComponentContainer container = new ComponentContainer(); | ComponentContainer container = new ComponentContainer(); | ||||
new ProjectsWsModule().configure(container); | new ProjectsWsModule().configure(container); | ||||
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 12); | |||||
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 11); | |||||
} | } | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.source.ws; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.junit.rules.ExpectedException; | |||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.db.DbTester; | |||||
import org.sonar.db.component.ComponentDto; | |||||
import org.sonar.db.source.FileSourceDto; | |||||
import org.sonar.server.component.TestComponentFinder; | |||||
import org.sonar.server.exceptions.ForbiddenException; | |||||
import org.sonar.server.exceptions.NotFoundException; | |||||
import org.sonar.server.tester.UserSessionRule; | |||||
import org.sonar.server.ws.TestRequest; | |||||
import org.sonar.server.ws.WsActionTester; | |||||
import static java.lang.String.format; | |||||
import static java.util.Collections.singletonList; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.sonar.api.web.UserRole.USER; | |||||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||||
public class HashActionTest { | |||||
@Rule | |||||
public ExpectedException expectedException = ExpectedException.none(); | |||||
@Rule | |||||
public DbTester db = DbTester.create(System2.INSTANCE); | |||||
@Rule | |||||
public UserSessionRule userSessionRule = UserSessionRule.standalone(); | |||||
private final WsActionTester tester = new WsActionTester(new HashAction(db.getDbClient(), userSessionRule, TestComponentFinder.from(db))); | |||||
@Test | |||||
public void show_hashes() { | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||||
FileSourceDto fileSource = db.fileSources().insertFileSource(file, f -> f.setLineHashes(singletonList("ABC"))); | |||||
loginAsProjectViewer(project); | |||||
TestRequest request = tester.newRequest().setParam("key", file.getDbKey()); | |||||
assertThat(request.execute().getInput()).isEqualTo("ABC"); | |||||
} | |||||
@Test | |||||
public void hashes_empty_if_no_source() { | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||||
loginAsProjectViewer(project); | |||||
TestRequest request = tester.newRequest().setParam("key", file.getKey()); | |||||
assertThat(request.execute().getStatus()).isEqualTo(204); | |||||
} | |||||
@Test | |||||
public void fail_to_show_hashes_if_file_does_not_exist() { | |||||
expectedException.expect(NotFoundException.class); | |||||
tester.newRequest().setParam("key", "unknown").execute(); | |||||
} | |||||
@Test | |||||
public void fail_when_using_branch_db_key() { | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
ComponentDto branch = db.components().insertProjectBranch(project); | |||||
loginAsProjectViewer(project); | |||||
expectedException.expect(NotFoundException.class); | |||||
expectedException.expectMessage(format("Component key '%s' not found", branch.getDbKey())); | |||||
tester.newRequest().setParam("key", branch.getDbKey()).execute(); | |||||
} | |||||
@Test | |||||
public void fail_on_missing_permission() { | |||||
ComponentDto project = db.components().insertPrivateProject(); | |||||
ComponentDto file = db.components().insertComponent(newFileDto(project)); | |||||
FileSourceDto fileSource = db.fileSources().insertFileSource(file); | |||||
userSessionRule.logIn(db.users().insertUser()); | |||||
expectedException.expect(ForbiddenException.class); | |||||
tester.newRequest().setParam("key", file.getKey()).execute(); | |||||
} | |||||
private void loginAsProjectViewer(ComponentDto project) { | |||||
userSessionRule.logIn(db.users().insertUser()).addProjectPermission(USER, project); | |||||
} | |||||
} |
public void verify_count_of_actions() { | public void verify_count_of_actions() { | ||||
ComponentContainer container = new ComponentContainer(); | ComponentContainer container = new ComponentContainer(); | ||||
underTest.configure(container); | underTest.configure(container); | ||||
assertThat(container.getPicoContainer().getComponentAdapters(WsAction.class)).hasSize(7); | |||||
assertThat(container.getPicoContainer().getComponentAdapters(WsAction.class)).hasSize(6); | |||||
} | } | ||||
} | } |
assertRedirection("/api/projects/update_key", "POST"); | assertRedirection("/api/projects/update_key", "POST"); | ||||
} | } | ||||
@Test | |||||
public void redirect_components_bulk_update_key() { | |||||
when(request.getServletPath()).thenReturn("/api/components/bulk_update_key"); | |||||
when(request.getMethod()).thenReturn("POST"); | |||||
underTest.doFilter(request, response, chain); | |||||
assertRedirection("/api/projects/bulk_update_key", "POST"); | |||||
} | |||||
private void assertRedirection(String path, String method) { | private void assertRedirection(String path, String method) { | ||||
verify(webServiceEngine).execute(servletRequestCaptor.capture(), any(ServletResponse.class)); | verify(webServiceEngine).execute(servletRequestCaptor.capture(), any(ServletResponse.class)); | ||||
assertThat(servletRequestCaptor.getValue().getPath()).isEqualTo(path); | assertThat(servletRequestCaptor.getValue().getPath()).isEqualTo(path); |
} | } | ||||
] | ] | ||||
}, | }, | ||||
{ | |||||
"key": "search_global_permissions", | |||||
"description": "List global permissions. <br />Requires the following permission: 'Administer System'", | |||||
"since": "5.2", | |||||
"deprecatedSince": "6.5", | |||||
"internal": false, | |||||
"post": false, | |||||
"hasResponseExample": true, | |||||
"changelog": [], | |||||
"params": [ | |||||
{ | |||||
"key": "organization", | |||||
"description": "Key of organization, used when group name is set", | |||||
"since": "6.2", | |||||
"required": false, | |||||
"internal": true, | |||||
"exampleValue": "my-org" | |||||
} | |||||
] | |||||
}, | |||||
{ | |||||
"key": "search_project_permissions", | |||||
"description": "List project permissions. A project can be a technical project, a view or a developer.<br />Requires one of the following permissions:<ul><li>'Administer System'</li><li>'Administer' rights on the specified project</li></ul>", | |||||
"since": "5.2", | |||||
"deprecatedSince": "6.5", | |||||
"internal": false, | |||||
"post": false, | |||||
"hasResponseExample": true, | |||||
"changelog": [], | |||||
"params": [ | |||||
{ | |||||
"key": "p", | |||||
"description": "1-based page number", | |||||
"required": false, | |||||
"internal": false, | |||||
"defaultValue": "1", | |||||
"exampleValue": "42", | |||||
"deprecatedKey": "pageIndex", | |||||
"deprecatedKeySince": "5.2" | |||||
}, | |||||
{ | |||||
"key": "projectId", | |||||
"description": "Project id", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "ce4c03d6-430f-40a9-b777-ad877c00aa4d" | |||||
}, | |||||
{ | |||||
"key": "projectKey", | |||||
"description": "Project key", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "my_project" | |||||
}, | |||||
{ | |||||
"key": "ps", | |||||
"description": "Page size. Must be greater than 0.", | |||||
"required": false, | |||||
"internal": false, | |||||
"defaultValue": "25", | |||||
"exampleValue": "20", | |||||
"deprecatedKey": "pageSize", | |||||
"deprecatedKeySince": "5.2" | |||||
}, | |||||
{ | |||||
"key": "q", | |||||
"description": "Limit search to: <ul><li>project names that contain the supplied string</li><li>project keys that are exactly the same as the supplied string</li></ul>", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "apac" | |||||
}, | |||||
{ | |||||
"key": "qualifier", | |||||
"description": "Project qualifier. Filter the results with the specified qualifier. Possible values are:<ul><li>TRK - Projects</li></ul>", | |||||
"since": "5.3", | |||||
"required": false, | |||||
"internal": false, | |||||
"possibleValues": [ | |||||
"TRK" | |||||
] | |||||
} | |||||
] | |||||
}, | |||||
{ | { | ||||
"key": "search_templates", | "key": "search_templates", | ||||
"description": "List permission templates.<br />Requires the following permission: 'Administer System'.", | "description": "List permission templates.<br />Requires the following permission: 'Administer System'.", | ||||
} | } | ||||
] | ] | ||||
}, | }, | ||||
{ | |||||
"key": "unset_default", | |||||
"description": "Unset a quality gate as the default quality gate.<br>Requires the 'Administer Quality Gates' permission.", | |||||
"since": "4.3", | |||||
"internal": false, | |||||
"post": true, | |||||
"hasResponseExample": false, | |||||
"changelog": [], | |||||
"params": [ | |||||
{ | |||||
"key": "id", | |||||
"description": "ID of the quality gate to unset as default", | |||||
"required": true, | |||||
"internal": false, | |||||
"exampleValue": "1" | |||||
} | |||||
] | |||||
}, | |||||
{ | { | ||||
"key": "update_condition", | "key": "update_condition", | ||||
"description": "Update a condition attached to a quality gate.<br>Requires the 'Administer Quality Gates' permission.", | "description": "Update a condition attached to a quality gate.<br>Requires the 'Administer Quality Gates' permission.", |
public static final String PARAM_RESOLVED = "resolved"; | public static final String PARAM_RESOLVED = "resolved"; | ||||
public static final String PARAM_COMPONENT_KEYS = "componentKeys"; | public static final String PARAM_COMPONENT_KEYS = "componentKeys"; | ||||
public static final String PARAM_COMPONENT_UUIDS = "componentUuids"; | public static final String PARAM_COMPONENT_UUIDS = "componentUuids"; | ||||
public static final String PARAM_MODULE_UUIDS = "moduleUuids"; | |||||
public static final String PARAM_PROJECTS = "projects"; | public static final String PARAM_PROJECTS = "projects"; | ||||
public static final String PARAM_DIRECTORIES = "directories"; | public static final String PARAM_DIRECTORIES = "directories"; | ||||
public static final String PARAM_FILES = "files"; | public static final String PARAM_FILES = "files"; | ||||
public static final String PARAM_SEND_NOTIFICATIONS = "sendNotifications"; | public static final String PARAM_SEND_NOTIFICATIONS = "sendNotifications"; | ||||
public static final String PARAM_ASSIGNEES = "assignees"; | public static final String PARAM_ASSIGNEES = "assignees"; | ||||
/** | |||||
* @deprecated since 7.7, please use 'author' instead | |||||
*/ | |||||
@Deprecated | |||||
public static final String DEPRECATED_PARAM_AUTHORS = "authors"; | |||||
public static final String PARAM_AUTHOR = "author"; | public static final String PARAM_AUTHOR = "author"; | ||||
public static final String PARAM_SCOPES = "scopes"; | public static final String PARAM_SCOPES = "scopes"; | ||||
public static final String PARAM_CREATED_BEFORE = "createdBefore"; | public static final String PARAM_CREATED_BEFORE = "createdBefore"; | ||||
public static final String PARAM_CREATED_IN_LAST = "createdInLast"; | public static final String PARAM_CREATED_IN_LAST = "createdInLast"; | ||||
public static final String PARAM_SINCE_LEAK_PERIOD = "sinceLeakPeriod"; | public static final String PARAM_SINCE_LEAK_PERIOD = "sinceLeakPeriod"; | ||||
public static final String PARAM_PAGE_SIZE = "pageSize"; | |||||
public static final String PARAM_PAGE_INDEX = "pageIndex"; | |||||
public static final String PARAM_ASC = "asc"; | public static final String PARAM_ASC = "asc"; | ||||
public static final String PARAM_ADDITIONAL_FIELDS = "additionalFields"; | public static final String PARAM_ADDITIONAL_FIELDS = "additionalFields"; | ||||
public static final String PARAM_TIMEZONE = "timeZone"; | public static final String PARAM_TIMEZONE = "timeZone"; | ||||
/** | |||||
* @deprecated since 7.9 | |||||
*/ | |||||
@Deprecated | |||||
public static final String FACET_MODE = "facetMode"; | |||||
public static final String FACET_MODE_COUNT = "count"; | |||||
public static final String FACET_MODE_EFFORT = "effort"; | public static final String FACET_MODE_EFFORT = "effort"; | ||||
private IssuesWsParameters() { | private IssuesWsParameters() { |
.setParam("assigned", request.getAssigned()) | .setParam("assigned", request.getAssigned()) | ||||
.setParam("assignees", request.getAssignees() == null ? null : request.getAssignees().stream().collect(Collectors.joining(","))) | .setParam("assignees", request.getAssignees() == null ? null : request.getAssignees().stream().collect(Collectors.joining(","))) | ||||
.setParam("author", request.getAuthor()) | .setParam("author", request.getAuthor()) | ||||
.setParam("authors", request.getAuthors() == null ? null : request.getAuthors().stream().collect(Collectors.joining(","))) | |||||
.setParam("branch", request.getBranch()) | .setParam("branch", request.getBranch()) | ||||
.setParam("componentKeys", request.getComponentKeys() == null ? null : request.getComponentKeys().stream().collect(Collectors.joining(","))) | .setParam("componentKeys", request.getComponentKeys() == null ? null : request.getComponentKeys().stream().collect(Collectors.joining(","))) | ||||
.setParam("componentUuids", request.getComponentUuids() == null ? null : request.getComponentUuids().stream().collect(Collectors.joining(","))) | .setParam("componentUuids", request.getComponentUuids() == null ? null : request.getComponentUuids().stream().collect(Collectors.joining(","))) | ||||
.setParam("createdInLast", request.getCreatedInLast()) | .setParam("createdInLast", request.getCreatedInLast()) | ||||
.setParam("cwe", request.getCwe() == null ? null : request.getCwe().stream().collect(Collectors.joining(","))) | .setParam("cwe", request.getCwe() == null ? null : request.getCwe().stream().collect(Collectors.joining(","))) | ||||
.setParam("directories", request.getDirectories() == null ? null : request.getDirectories().stream().collect(Collectors.joining(","))) | .setParam("directories", request.getDirectories() == null ? null : request.getDirectories().stream().collect(Collectors.joining(","))) | ||||
.setParam("facetMode", request.getFacetMode()) | |||||
.setParam("facets", request.getFacets() == null ? null : request.getFacets().stream().collect(Collectors.joining(","))) | .setParam("facets", request.getFacets() == null ? null : request.getFacets().stream().collect(Collectors.joining(","))) | ||||
.setParam("fileUuids", request.getFileUuids() == null ? null : request.getFileUuids().stream().collect(Collectors.joining(","))) | .setParam("fileUuids", request.getFileUuids() == null ? null : request.getFileUuids().stream().collect(Collectors.joining(","))) | ||||
.setParam("issues", request.getIssues() == null ? null : request.getIssues().stream().collect(Collectors.joining(","))) | .setParam("issues", request.getIssues() == null ? null : request.getIssues().stream().collect(Collectors.joining(","))) | ||||
.setParam("languages", request.getLanguages() == null ? null : request.getLanguages().stream().collect(Collectors.joining(","))) | .setParam("languages", request.getLanguages() == null ? null : request.getLanguages().stream().collect(Collectors.joining(","))) | ||||
.setParam("moduleUuids", request.getModuleUuids() == null ? null : request.getModuleUuids().stream().collect(Collectors.joining(","))) | |||||
.setParam("onComponentOnly", request.getOnComponentOnly()) | .setParam("onComponentOnly", request.getOnComponentOnly()) | ||||
.setParam("owaspTop10", request.getOwaspTop10() == null ? null : request.getOwaspTop10().stream().collect(Collectors.joining(","))) | .setParam("owaspTop10", request.getOwaspTop10() == null ? null : request.getOwaspTop10().stream().collect(Collectors.joining(","))) | ||||
.setParam("p", request.getP()) | .setParam("p", request.getP()) |
private String assigned; | private String assigned; | ||||
private List<String> assignees; | private List<String> assignees; | ||||
private List<String> author; | private List<String> author; | ||||
private List<String> authors; | |||||
private String branch; | private String branch; | ||||
private List<String> componentKeys; | private List<String> componentKeys; | ||||
private List<String> componentUuids; | private List<String> componentUuids; | ||||
private List<String> fileUuids; | private List<String> fileUuids; | ||||
private List<String> issues; | private List<String> issues; | ||||
private List<String> languages; | private List<String> languages; | ||||
private List<String> moduleUuids; | |||||
private String onComponentOnly; | private String onComponentOnly; | ||||
private List<String> owaspTop10; | private List<String> owaspTop10; | ||||
private String p; | private String p; | ||||
return author; | return author; | ||||
} | } | ||||
/** | |||||
* Example value: "torvalds@linux-foundation.org" | |||||
* @deprecated since 7.7 | |||||
*/ | |||||
@Deprecated | |||||
public SearchRequest setAuthors(List<String> authors) { | |||||
this.authors = authors; | |||||
return this; | |||||
} | |||||
public List<String> getAuthors() { | |||||
return authors; | |||||
} | |||||
/** | /** | ||||
* This is part of the internal API. | * This is part of the internal API. | ||||
* Example value: "feature/my_branch" | * Example value: "feature/my_branch" | ||||
return directories; | return directories; | ||||
} | } | ||||
/** | |||||
* Possible values: | |||||
* <ul> | |||||
* <li>"count"</li> | |||||
* <li>"effort"</li> | |||||
* </ul> | |||||
*/ | |||||
public SearchRequest setFacetMode(String facetMode) { | |||||
this.facetMode = facetMode; | |||||
return this; | |||||
} | |||||
public String getFacetMode() { | |||||
return facetMode; | |||||
} | |||||
/** | /** | ||||
* Possible values: | * Possible values: | ||||
* <ul> | * <ul> | ||||
return languages; | return languages; | ||||
} | } | ||||
/** | |||||
* This is part of the internal API. | |||||
* Example value: "7d8749e8-3070-4903-9188-bdd82933bb92" | |||||
* @deprecated since 7.6 | |||||
*/ | |||||
@Deprecated | |||||
public SearchRequest setModuleUuids(List<String> moduleUuids) { | |||||
this.moduleUuids = moduleUuids; | |||||
return this; | |||||
} | |||||
public List<String> getModuleUuids() { | |||||
return moduleUuids; | |||||
} | |||||
/** | /** | ||||
* Possible values: | * Possible values: | ||||
* <ul> | * <ul> |
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import javax.annotation.Generated; | import javax.annotation.Generated; | ||||
import org.sonarqube.ws.MediaTypes; | import org.sonarqube.ws.MediaTypes; | ||||
import org.sonarqube.ws.client.BaseService; | |||||
import org.sonarqube.ws.client.GetRequest; | |||||
import org.sonarqube.ws.client.PostRequest; | |||||
import org.sonarqube.ws.client.WsConnector; | |||||
import org.sonarqube.ws.Permissions.CreateTemplateWsResponse; | import org.sonarqube.ws.Permissions.CreateTemplateWsResponse; | ||||
import org.sonarqube.ws.Permissions.WsGroupsResponse; | |||||
import org.sonarqube.ws.Permissions.WsSearchGlobalPermissionsResponse; | |||||
import org.sonarqube.ws.Permissions.SearchProjectPermissionsWsResponse; | |||||
import org.sonarqube.ws.Permissions.SearchTemplatesWsResponse; | import org.sonarqube.ws.Permissions.SearchTemplatesWsResponse; | ||||
import org.sonarqube.ws.Permissions.WsTemplateGroupsResponse; | |||||
import org.sonarqube.ws.Permissions.UpdateTemplateWsResponse; | import org.sonarqube.ws.Permissions.UpdateTemplateWsResponse; | ||||
import org.sonarqube.ws.Permissions.UsersWsResponse; | import org.sonarqube.ws.Permissions.UsersWsResponse; | ||||
import org.sonarqube.ws.Permissions.WsGroupsResponse; | |||||
import org.sonarqube.ws.Permissions.WsTemplateGroupsResponse; | |||||
import org.sonarqube.ws.client.BaseService; | |||||
import org.sonarqube.ws.client.GetRequest; | |||||
import org.sonarqube.ws.client.PostRequest; | |||||
import org.sonarqube.ws.client.WsConnector; | |||||
/** | /** | ||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/permissions">Further information about this web service online</a> | * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/permissions">Further information about this web service online</a> | ||||
).content(); | ).content(); | ||||
} | } | ||||
/** | |||||
* | |||||
* This is part of the internal API. | |||||
* This is a GET request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/permissions/search_global_permissions">Further information about this action online (including a response example)</a> | |||||
* @since 5.2 | |||||
* @deprecated since 6.5 | |||||
*/ | |||||
@Deprecated | |||||
public WsSearchGlobalPermissionsResponse searchGlobalPermissions(SearchGlobalPermissionsRequest request) { | |||||
return call( | |||||
new GetRequest(path("search_global_permissions")), | |||||
WsSearchGlobalPermissionsResponse.parser()); | |||||
} | |||||
/** | |||||
* | |||||
* This is part of the internal API. | |||||
* This is a GET request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/permissions/search_project_permissions">Further information about this action online (including a response example)</a> | |||||
* @since 5.2 | |||||
* @deprecated since 6.5 | |||||
*/ | |||||
@Deprecated | |||||
public SearchProjectPermissionsWsResponse searchProjectPermissions(SearchProjectPermissionsRequest request) { | |||||
return call( | |||||
new GetRequest(path("search_project_permissions")) | |||||
.setParam("p", request.getP()) | |||||
.setParam("projectId", request.getProjectId()) | |||||
.setParam("projectKey", request.getProjectKey()) | |||||
.setParam("ps", request.getPs()) | |||||
.setParam("q", request.getQ()) | |||||
.setParam("qualifier", request.getQualifier()), | |||||
SearchProjectPermissionsWsResponse.parser()); | |||||
} | |||||
/** | /** | ||||
* | * | ||||
* This is part of the internal API. | * This is part of the internal API. |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.sonarqube.ws.client.permissions; | |||||
import javax.annotation.Generated; | |||||
/** | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/permissions/search_global_permissions">Further information about this action online (including a response example)</a> | |||||
* @since 5.2 | |||||
*/ | |||||
@Generated("sonar-ws-generator") | |||||
public class SearchGlobalPermissionsRequest { | |||||
} |
public static final String CONTROLLER = "api/projects"; | public static final String CONTROLLER = "api/projects"; | ||||
public static final String ACTION_CREATE = "create"; | public static final String ACTION_CREATE = "create"; | ||||
public static final String ACTION_INDEX = "index"; | |||||
public static final String ACTION_SEARCH = "search"; | public static final String ACTION_SEARCH = "search"; | ||||
public static final String ACTION_UPDATE_KEY = "update_key"; | public static final String ACTION_UPDATE_KEY = "update_key"; | ||||
public static final String ACTION_BULK_UPDATE_KEY = "bulk_update_key"; | |||||
public static final String ACTION_UPDATE_VISIBILITY = "update_visibility"; | public static final String ACTION_UPDATE_VISIBILITY = "update_visibility"; | ||||
public static final String PARAM_PROJECT = "project"; | public static final String PARAM_PROJECT = "project"; |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.sonarqube.ws.client.projects; | |||||
import javax.annotation.Generated; | |||||
/** | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/projects/bulk_update_key">Further information about this action online (including a response example)</a> | |||||
* @since 6.1 | |||||
*/ | |||||
@Generated("sonar-ws-generator") | |||||
public class BulkUpdateKeyRequest { | |||||
private String dryRun; | |||||
private String from; | |||||
private String project; | |||||
private String to; | |||||
/** | |||||
* Possible values: | |||||
* <ul> | |||||
* <li>"true"</li> | |||||
* <li>"false"</li> | |||||
* <li>"yes"</li> | |||||
* <li>"no"</li> | |||||
* </ul> | |||||
*/ | |||||
public BulkUpdateKeyRequest setDryRun(String dryRun) { | |||||
this.dryRun = dryRun; | |||||
return this; | |||||
} | |||||
public String getDryRun() { | |||||
return dryRun; | |||||
} | |||||
/** | |||||
* This is a mandatory parameter. | |||||
* Example value: "_old" | |||||
*/ | |||||
public BulkUpdateKeyRequest setFrom(String from) { | |||||
this.from = from; | |||||
return this; | |||||
} | |||||
public String getFrom() { | |||||
return from; | |||||
} | |||||
/** | |||||
* This is a mandatory parameter. | |||||
* Example value: "my_old_project" | |||||
*/ | |||||
public BulkUpdateKeyRequest setProject(String project) { | |||||
this.project = project; | |||||
return this; | |||||
} | |||||
public String getProject() { | |||||
return project; | |||||
} | |||||
/** | |||||
* This is a mandatory parameter. | |||||
* Example value: "_new" | |||||
*/ | |||||
public BulkUpdateKeyRequest setTo(String to) { | |||||
this.to = to; | |||||
return this; | |||||
} | |||||
public String getTo() { | |||||
return to; | |||||
} | |||||
} |
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import javax.annotation.Generated; | import javax.annotation.Generated; | ||||
import org.sonarqube.ws.MediaTypes; | import org.sonarqube.ws.MediaTypes; | ||||
import org.sonarqube.ws.Projects.BulkUpdateKeyWsResponse; | |||||
import org.sonarqube.ws.Projects.CreateWsResponse; | import org.sonarqube.ws.Projects.CreateWsResponse; | ||||
import org.sonarqube.ws.Projects.SearchMyProjectsWsResponse; | import org.sonarqube.ws.Projects.SearchMyProjectsWsResponse; | ||||
import org.sonarqube.ws.Projects.SearchWsResponse; | import org.sonarqube.ws.Projects.SearchWsResponse; | ||||
).content(); | ).content(); | ||||
} | } | ||||
/** | |||||
* | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/projects/bulk_update_key">Further information about this action online (including a response example)</a> | |||||
* @since 6.1 | |||||
*/ | |||||
public BulkUpdateKeyWsResponse bulkUpdateKey(BulkUpdateKeyRequest request) { | |||||
return call( | |||||
new PostRequest(path("bulk_update_key")) | |||||
.setParam("dryRun", request.getDryRun()) | |||||
.setParam("from", request.getFrom()) | |||||
.setParam("project", request.getProject()) | |||||
.setParam("to", request.getTo()), | |||||
BulkUpdateKeyWsResponse.parser()); | |||||
} | |||||
/** | /** | ||||
* | * | ||||
* This is part of the internal API. | * This is part of the internal API. |
ShowWsResponse.parser()); | ShowWsResponse.parser()); | ||||
} | } | ||||
/** | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/qualitygates/unset_default">Further information about this action online (including a response example)</a> | |||||
* @since 4.3 | |||||
* @deprecated since 7.0 | |||||
*/ | |||||
@Deprecated | |||||
public String unsetDefault() { | |||||
return call( | |||||
new PostRequest(path("unset_default")) | |||||
.setMediaType(MediaTypes.JSON)).content(); | |||||
} | |||||
/** | /** | ||||
* This is part of the internal API. | * This is part of the internal API. | ||||
* This is a POST request. | * This is a POST request. |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.sonarqube.ws.client.qualitygates; | |||||
import javax.annotation.Generated; | |||||
/** | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/qualitygates/unset_default">Further information about this action online (including a response example)</a> | |||||
* @since 4.3 | |||||
*/ | |||||
@Generated("sonar-ws-generator") | |||||
public class UnsetDefaultRequest { | |||||
private String id; | |||||
/** | |||||
* This is a mandatory parameter. | |||||
* Example value: "1" | |||||
*/ | |||||
public UnsetDefaultRequest setId(String id) { | |||||
this.id = id; | |||||
return this; | |||||
} | |||||
public String getId() { | |||||
return id; | |||||
} | |||||
} |
super(wsConnector, "api/sources"); | super(wsConnector, "api/sources"); | ||||
} | } | ||||
/** | |||||
* | |||||
* This is part of the internal API. | |||||
* This is a GET request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/sources/hash">Further information about this action online (including a response example)</a> | |||||
* @since 5.0 | |||||
*/ | |||||
public String hash(HashRequest request) { | |||||
return call( | |||||
new GetRequest(path("hash")) | |||||
.setParam("key", request.getKey()) | |||||
.setMediaType(MediaTypes.JSON) | |||||
).content(); | |||||
} | |||||
/** | /** | ||||
* | * | ||||
* This is part of the internal API. | * This is part of the internal API. |
private String description; | private String description; | ||||
private String key; | private String key; | ||||
private String name; | private String name; | ||||
private String qualifier; | |||||
private String visibility; | private String visibility; | ||||
/** | /** | ||||
return name; | return name; | ||||
} | } | ||||
/** | |||||
* Possible values: | |||||
* <ul> | |||||
* <li>"VW"</li> | |||||
* <li>"APP"</li> | |||||
* </ul> | |||||
* @deprecated since 7.3 | |||||
*/ | |||||
@Deprecated | |||||
public CreateRequest setQualifier(String qualifier) { | |||||
this.qualifier = qualifier; | |||||
return this; | |||||
} | |||||
public String getQualifier() { | |||||
return qualifier; | |||||
} | |||||
/** | /** | ||||
* Possible values: | * Possible values: | ||||
* <ul> | * <ul> |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.sonarqube.ws.client.views; | |||||
import javax.annotation.Generated; | |||||
/** | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/views/mode">Further information about this action online (including a response example)</a> | |||||
* @since 2.6 | |||||
*/ | |||||
@Generated("sonar-ws-generator") | |||||
public class ModeRequest { | |||||
private String key; | |||||
private String measure; | |||||
private String regexp; | |||||
private String selectionMode; | |||||
private String value; | |||||
/** | |||||
* This is a mandatory parameter. | |||||
*/ | |||||
public ModeRequest setKey(String key) { | |||||
this.key = key; | |||||
return this; | |||||
} | |||||
public String getKey() { | |||||
return key; | |||||
} | |||||
/** | |||||
*/ | |||||
public ModeRequest setMeasure(String measure) { | |||||
this.measure = measure; | |||||
return this; | |||||
} | |||||
public String getMeasure() { | |||||
return measure; | |||||
} | |||||
/** | |||||
*/ | |||||
public ModeRequest setRegexp(String regexp) { | |||||
this.regexp = regexp; | |||||
return this; | |||||
} | |||||
public String getRegexp() { | |||||
return regexp; | |||||
} | |||||
/** | |||||
* This is a mandatory parameter. | |||||
* Possible values: | |||||
* <ul> | |||||
* <li>"MANUAL"</li> | |||||
* <li>"REGEXP"</li> | |||||
* <li>"MANUAL_MEASURE"</li> | |||||
* <li>"REST"</li> | |||||
* </ul> | |||||
*/ | |||||
public ModeRequest setSelectionMode(String selectionMode) { | |||||
this.selectionMode = selectionMode; | |||||
return this; | |||||
} | |||||
public String getSelectionMode() { | |||||
return selectionMode; | |||||
} | |||||
/** | |||||
*/ | |||||
public ModeRequest setValue(String value) { | |||||
this.value = value; | |||||
return this; | |||||
} | |||||
public String getValue() { | |||||
return value; | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info 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.sonarqube.ws.client.views; | |||||
import javax.annotation.Generated; | |||||
/** | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/views/regexp">Further information about this action online (including a response example)</a> | |||||
* @since 1.0 | |||||
*/ | |||||
@Generated("sonar-ws-generator") | |||||
public class RegexpRequest { | |||||
private String key; | |||||
private String regexp; | |||||
/** | |||||
* This is a mandatory parameter. | |||||
*/ | |||||
public RegexpRequest setKey(String key) { | |||||
this.key = key; | |||||
return this; | |||||
} | |||||
public String getKey() { | |||||
return key; | |||||
} | |||||
/** | |||||
* This is a mandatory parameter. | |||||
*/ | |||||
public RegexpRequest setRegexp(String regexp) { | |||||
this.regexp = regexp; | |||||
return this; | |||||
} | |||||
public String getRegexp() { | |||||
return regexp; | |||||
} | |||||
} |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
package org.sonarqube.ws.client.sources; | |||||
package org.sonarqube.ws.client.views; | |||||
import javax.annotation.Generated; | import javax.annotation.Generated; | ||||
/** | /** | ||||
* This is part of the internal API. | * This is part of the internal API. | ||||
* This is a POST request. | * This is a POST request. | ||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/sources/hash">Further information about this action online (including a response example)</a> | |||||
* @since 5.0 | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/views/set_none_mode">Further information about this action online (including a response example)</a> | |||||
* @since 9.1 | |||||
*/ | */ | ||||
@Generated("sonar-ws-generator") | @Generated("sonar-ws-generator") | ||||
public class HashRequest { | |||||
public class SetNoneModeRequest { | |||||
private String key; | |||||
private String portfolio; | |||||
/** | /** | ||||
* This is a mandatory parameter. | * This is a mandatory parameter. | ||||
* Example value: "my_project:/src/foo/Bar.php" | |||||
*/ | */ | ||||
public HashRequest setKey(String key) { | |||||
this.key = key; | |||||
public SetNoneModeRequest setPortfolio(String portfolio) { | |||||
this.portfolio = portfolio; | |||||
return this; | return this; | ||||
} | } | ||||
public String getKey() { | |||||
return key; | |||||
public String getPortfolio() { | |||||
return portfolio; | |||||
} | } | ||||
} | } |
.setParam("description", request.getDescription()) | .setParam("description", request.getDescription()) | ||||
.setParam("key", request.getKey()) | .setParam("key", request.getKey()) | ||||
.setParam("name", request.getName()) | .setParam("name", request.getName()) | ||||
.setParam("qualifier", request.getQualifier()) | |||||
.setParam("visibility", request.getVisibility()) | .setParam("visibility", request.getVisibility()) | ||||
.setMediaType(MediaTypes.JSON) | .setMediaType(MediaTypes.JSON) | ||||
).content(); | ).content(); | ||||
).content(); | ).content(); | ||||
} | } | ||||
/** | |||||
* | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/views/mode">Further information about this action online (including a response example)</a> | |||||
* @since 2.6 | |||||
* @deprecated since 7.4 | |||||
*/ | |||||
public void mode(ModeRequest request) { | |||||
call( | |||||
new PostRequest(path("mode")) | |||||
.setParam("key", request.getKey()) | |||||
.setParam("measure", request.getMeasure()) | |||||
.setParam("regexp", request.getRegexp()) | |||||
.setParam("selectionMode", request.getSelectionMode()) | |||||
.setParam("value", request.getValue()) | |||||
.setMediaType(MediaTypes.JSON) | |||||
).content(); | |||||
} | |||||
/** | /** | ||||
* | * | ||||
* This is part of the internal API. | * This is part of the internal API. | ||||
).content(); | ).content(); | ||||
} | } | ||||
/** | |||||
* | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/views/regexp">Further information about this action online (including a response example)</a> | |||||
* @since 1.0 | |||||
* @deprecated since 7.4 | |||||
*/ | |||||
public void regexp(RegexpRequest request) { | |||||
call( | |||||
new PostRequest(path("regexp")) | |||||
.setParam("key", request.getKey()) | |||||
.setParam("regexp", request.getRegexp()) | |||||
.setMediaType(MediaTypes.JSON) | |||||
).content(); | |||||
} | |||||
/** | /** | ||||
* | * | ||||
* This is part of the internal API. | * This is part of the internal API. | ||||
).content(); | ).content(); | ||||
} | } | ||||
/** | |||||
* | |||||
* This is part of the internal API. | |||||
* This is a POST request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/views/set_none_mode">Further information about this action online (including a response example)</a> | |||||
* @since 9.1 | |||||
*/ | |||||
public void setNoneMode(SetNoneModeRequest request) { | |||||
call( | |||||
new PostRequest(path("set_none_mode")) | |||||
.setParam("portfolio", request.getPortfolio()) | |||||
.setMediaType(MediaTypes.JSON) | |||||
).content(); | |||||
} | |||||
/** | /** | ||||
* | * | ||||
* This is part of the internal API. | * This is part of the internal API. |
optional string revision = 8; | optional string revision = 8; | ||||
} | } | ||||
} | } | ||||
// WS api/projects/prepare_bulk_update_key | |||||
message BulkUpdateKeyWsResponse { | |||||
repeated Key keys = 1; | |||||
message Key { | |||||
optional string key = 1; | |||||
optional string newKey = 2; | |||||
optional bool duplicate = 3; | |||||
} | |||||
} |