@@ -25,6 +25,7 @@ CREATE TABLE "ORGANIZATION_MEMBERS" ( | |||
CONSTRAINT "PK_ORGANIZATION_MEMBERS" PRIMARY KEY ("ORGANIZATION_UUID", "USER_ID") | |||
); | |||
CREATE INDEX "IX_ORG_MEMBERS_ON_USER_ID" ON "ORGANIZATION_MEMBERS" ("USER_ID"); | |||
CREATE TABLE "GROUPS_USERS" ( | |||
"USER_ID" INTEGER, |
@@ -0,0 +1,47 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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.platform.db.migration.version.v73; | |||
import java.sql.SQLException; | |||
import org.sonar.db.Database; | |||
import org.sonar.server.platform.db.migration.SupportsBlueGreen; | |||
import org.sonar.server.platform.db.migration.def.IntegerColumnDef; | |||
import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; | |||
import org.sonar.server.platform.db.migration.step.DdlChange; | |||
@SupportsBlueGreen | |||
public class AddIndexOnOrganizationMembers extends DdlChange { | |||
public AddIndexOnOrganizationMembers(Database db) { | |||
super(db); | |||
} | |||
@Override | |||
public void execute(Context context) throws SQLException { | |||
context.execute( | |||
new CreateIndexBuilder(getDialect()) | |||
.setTable("organization_members") | |||
.setName("ix_org_members_on_user_id") | |||
.addColumn(IntegerColumnDef.newIntegerColumnDefBuilder() | |||
.setColumnName("user_id") | |||
.setIsNullable(false) | |||
.build()) | |||
.build()); | |||
} | |||
} |
@@ -39,6 +39,7 @@ public class DbVersion73 implements DbVersion { | |||
.add(2209, "Fix missing quality profiles on organizations", FixMissingQualityProfilesOnOrganizations.class) | |||
.add(2210, "Add 'securityhotspotadmin' permission to templates characteristics already having 'issueadmin'", PopulateHotspotAdminPermissionOnTemplatesCharacteristics.class) | |||
.add(2211, "Set SUBSCRIPTION not nullable in ORGANIZATIONS", SetSubscriptionOnOrganizationsNotNullable.class) | |||
.add(2212, "Add index on ORGANIZATION_MEMBERS", AddIndexOnOrganizationMembers.class) | |||
; | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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.platform.db.migration.version.v73; | |||
import java.sql.SQLException; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.db.CoreDbTester; | |||
public class AddIndexOnOrganizationMembersTest { | |||
private static final String TABLE_LOADED_TEMPLATES = "organization_members"; | |||
@Rule | |||
public CoreDbTester db = CoreDbTester.createForSchema(AddIndexOnOrganizationMembersTest.class, "organization_members.sql"); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private AddIndexOnOrganizationMembers underTest = new AddIndexOnOrganizationMembers(db.database()); | |||
@Test | |||
public void execute_adds_index_ix_loaded_templates_type() throws SQLException { | |||
underTest.execute(); | |||
db.assertIndex(TABLE_LOADED_TEMPLATES, "ix_org_members_on_user_id", "user_id"); | |||
} | |||
@Test | |||
public void execute_is_not_reentrant() throws SQLException { | |||
underTest.execute(); | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("Fail to execute"); | |||
underTest.execute(); | |||
} | |||
} |
@@ -35,6 +35,6 @@ public class DbVersion73Test { | |||
@Test | |||
public void verify_migration_count() { | |||
verifyMigrationCount(underTest, 12); | |||
verifyMigrationCount(underTest, 13); | |||
} | |||
} |
@@ -0,0 +1,6 @@ | |||
CREATE TABLE "ORGANIZATION_MEMBERS" ( | |||
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL, | |||
"USER_ID" INTEGER NOT NULL, | |||
CONSTRAINT "PK_ORGANIZATION_MEMBERS" PRIMARY KEY ("ORGANIZATION_UUID", "USER_ID") | |||
); |
@@ -47,6 +47,11 @@ public class SafeModeUserSession extends AbstractUserSession { | |||
return false; | |||
} | |||
@Override | |||
protected boolean hasMembershipImpl(OrganizationDto organizationDto) { | |||
return false; | |||
} | |||
@CheckForNull | |||
@Override | |||
public String getLogin() { | |||
@@ -90,9 +95,4 @@ public class SafeModeUserSession extends AbstractUserSession { | |||
public boolean isSystemAdministrator() { | |||
return false; | |||
} | |||
@Override | |||
public boolean hasMembershipImpl(OrganizationDto organization) { | |||
return false; | |||
} | |||
} |
@@ -19,6 +19,9 @@ | |||
*/ | |||
package org.sonar.server.issue.ws; | |||
import com.google.common.base.Optional; | |||
import com.google.common.collect.ImmutableList; | |||
import com.google.common.collect.ImmutableMap; | |||
import com.google.common.io.Resources; | |||
import java.util.List; | |||
import java.util.Map; | |||
@@ -38,11 +41,14 @@ import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.issue.IssueFinder; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonarqube.ws.Issues.ChangelogWsResponse; | |||
import org.sonarqube.ws.Issues.ChangelogWsResponse.Changelog; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static com.google.common.base.Strings.emptyToNull; | |||
import static org.sonar.api.utils.DateUtils.formatDateTime; | |||
import static org.sonar.core.util.Protobuf.setNullable; | |||
@@ -60,11 +66,13 @@ public class ChangelogAction implements IssuesWsAction { | |||
private final DbClient dbClient; | |||
private final IssueFinder issueFinder; | |||
private final AvatarResolver avatarFactory; | |||
private final UserSession userSession; | |||
public ChangelogAction(DbClient dbClient, IssueFinder issueFinder, AvatarResolver avatarFactory) { | |||
public ChangelogAction(DbClient dbClient, IssueFinder issueFinder, AvatarResolver avatarFactory, UserSession userSession) { | |||
this.dbClient = dbClient; | |||
this.issueFinder = issueFinder; | |||
this.avatarFactory = avatarFactory; | |||
this.userSession = userSession; | |||
} | |||
@Override | |||
@@ -153,11 +161,25 @@ public class ChangelogAction implements IssuesWsAction { | |||
private final Map<String, ComponentDto> files; | |||
ChangeLogResults(DbSession dbSession, String issueKey) { | |||
IssueDto dbIssue = issueFinder.getByKey(dbSession, issueKey); | |||
this.changes = dbClient.issueChangeDao().selectChangelogByIssue(dbSession, dbIssue.getKey()); | |||
List<String> userUuids = changes.stream().filter(change -> change.userUuid() != null).map(FieldDiffs::userUuid).collect(MoreCollectors.toList()); | |||
this.users = dbClient.userDao().selectByUuids(dbSession, userUuids).stream().collect(MoreCollectors.uniqueIndex(UserDto::getUuid)); | |||
this.files = dbClient.componentDao().selectByUuids(dbSession, getFileUuids(changes)).stream().collect(MoreCollectors.uniqueIndex(ComponentDto::uuid, Function.identity())); | |||
IssueDto issue = issueFinder.getByKey(dbSession, issueKey); | |||
if (isMember(dbSession, issue)) { | |||
this.changes = dbClient.issueChangeDao().selectChangelogByIssue(dbSession, issue.getKey()); | |||
List<String> userUuids = changes.stream().filter(change -> change.userUuid() != null).map(FieldDiffs::userUuid).collect(MoreCollectors.toList()); | |||
this.users = dbClient.userDao().selectByUuids(dbSession, userUuids).stream().collect(MoreCollectors.uniqueIndex(UserDto::getUuid)); | |||
this.files = dbClient.componentDao().selectByUuids(dbSession, getFileUuids(changes)).stream().collect(MoreCollectors.uniqueIndex(ComponentDto::uuid, Function.identity())); | |||
} else { | |||
changes = ImmutableList.of(); | |||
users = ImmutableMap.of(); | |||
files = ImmutableMap.of(); | |||
} | |||
} | |||
private boolean isMember(DbSession dbSession, IssueDto issue) { | |||
Optional<ComponentDto> project = dbClient.componentDao().selectByUuid(dbSession, issue.getProjectUuid()); | |||
checkState(project.isPresent(), "Cannot find the project with uuid %s from issue.id %s", issue.getProjectUuid(), issue.getId()); | |||
java.util.Optional<OrganizationDto> organization = dbClient.organizationDao().selectByUuid(dbSession, project.get().getOrganizationUuid()); | |||
checkState(organization.isPresent(), "Cannot find the organization with uuid %s from issue.id %s", project.get().getOrganizationUuid(), issue.getId()); | |||
return userSession.hasMembership(organization.get()); | |||
} | |||
private Set<String> getFileUuids(List<FieldDiffs> changes) { |
@@ -30,12 +30,14 @@ import java.util.HashSet; | |||
import java.util.LinkedHashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.Stream; | |||
import javax.annotation.Nullable; | |||
import org.elasticsearch.action.search.SearchResponse; | |||
import org.elasticsearch.search.SearchHit; | |||
import org.sonar.api.config.Configuration; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rule.Severity; | |||
@@ -52,14 +54,15 @@ import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.core.util.stream.MoreCollectors; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.es.Facets; | |||
import org.sonar.server.es.SearchOptions; | |||
import org.sonar.server.issue.index.IssueQuery; | |||
import org.sonar.server.issue.index.IssueQueryFactory; | |||
import org.sonar.server.issue.SearchRequest; | |||
import org.sonar.server.issue.index.IssueIndex; | |||
import org.sonar.server.issue.index.IssueQuery; | |||
import org.sonar.server.issue.index.IssueQueryFactory; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonarqube.ws.Issues.SearchWsResponse; | |||
@@ -74,12 +77,13 @@ import static java.util.Optional.ofNullable; | |||
import static java.util.stream.Collectors.toList; | |||
import static org.sonar.api.utils.Paging.forPageIndex; | |||
import static org.sonar.core.util.stream.MoreCollectors.toSet; | |||
import static org.sonar.process.ProcessProperties.Property.SONARCLOUD_ENABLED; | |||
import static org.sonar.server.es.SearchOptions.MAX_LIMIT; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_INSECURE_INTERACTION; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_POROUS_DEFENSES; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.SANS_TOP_25_RISKY_RESOURCE; | |||
import static org.sonar.server.issue.index.IssueQuery.SORT_BY_ASSIGNEE; | |||
import static org.sonar.server.issue.index.IssueIndexDefinition.UNKNOWN_STANDARD; | |||
import static org.sonar.server.issue.index.IssueQuery.SORT_BY_ASSIGNEE; | |||
import static org.sonar.server.issue.index.IssueQueryFactory.UNKNOWN; | |||
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; | |||
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; | |||
@@ -149,10 +153,11 @@ public class SearchAction implements IssuesWsAction { | |||
private final SearchResponseFormat searchResponseFormat; | |||
private final System2 system2; | |||
private final DbClient dbClient; | |||
private final boolean isOnSonarCloud; | |||
public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory, | |||
SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat, System2 system2, | |||
DbClient dbClient) { | |||
public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory, SearchResponseLoader searchResponseLoader, | |||
SearchResponseFormat searchResponseFormat, Configuration config, System2 system2, DbClient dbClient) { | |||
this.isOnSonarCloud = config.getBoolean(SONARCLOUD_ENABLED.getKey()).orElse(false); | |||
this.userSession = userSession; | |||
this.issueIndex = issueIndex; | |||
this.issueQueryFactory = issueQueryFactory; | |||
@@ -376,10 +381,9 @@ public class SearchAction implements IssuesWsAction { | |||
} | |||
private List<String> getLogins(Request request) { | |||
List<String> assigneeLogins = request.paramAsStrings(PARAM_ASSIGNEES); | |||
List<String> onlyLogins = new ArrayList<>(); | |||
for (String login : ofNullable(assigneeLogins).orElse(emptyList())) { | |||
if (LOGIN_MYSELF.equals(login)) { | |||
if (userSession.getLogin() == null) { | |||
@@ -433,13 +437,18 @@ public class SearchAction implements IssuesWsAction { | |||
.filter(FACETS_REQUIRING_PROJECT_OR_ORGANIZATION::contains) | |||
.collect(toSet()); | |||
checkArgument(facetsRequiringProjectOrOrganizationParameter.isEmpty() || | |||
(!query.projectUuids().isEmpty()) || query.organizationUuid() != null, "Facet(s) '%s' require to also filter by project or organization", | |||
(!query.projectUuids().isEmpty()) || query.organizationUuid() != null, "Facet(s) '%s' require to also filter by project or organization", | |||
COMA_JOINER.join(facetsRequiringProjectOrOrganizationParameter)); | |||
} | |||
SearchResponseData preloadedData = new SearchResponseData(emptyList()); | |||
preloadedData.setRules(ImmutableList.copyOf(query.rules())); | |||
SearchResponseData data = searchResponseLoader.load(preloadedData, collector, facets); | |||
if (userSession.isLoggedIn()) { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
data.setUserOrganizationUuids(dbClient.organizationMemberDao().selectOrganizationUuidsByUser(dbSession, userSession.getUserId())); | |||
} | |||
} | |||
// format response | |||
// Filter and reorder facets according to the requested ordered names. | |||
@@ -536,10 +545,36 @@ public class SearchAction implements IssuesWsAction { | |||
} | |||
} | |||
private static SearchOptions createSearchOptionsFromRequest(SearchRequest request) { | |||
private SearchOptions createSearchOptionsFromRequest(SearchRequest request) { | |||
SearchOptions options = new SearchOptions(); | |||
options.setPage(request.getPage(), request.getPageSize()); | |||
options.addFacets(request.getFacets()); | |||
List<String> facets = request.getFacets(); | |||
if (facets == null || facets.isEmpty()) { | |||
return options; | |||
} | |||
List<String> requestedFacets = new ArrayList<>(facets.size()); | |||
requestedFacets.addAll(facets); | |||
if (isOnSonarCloud) { | |||
Optional<OrganizationDto> organizationDto = Optional.empty(); | |||
String organizationKey = request.getOrganization(); | |||
if (organizationKey != null) { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
organizationDto = dbClient.organizationDao().selectByKey(dbSession, organizationKey); | |||
} | |||
} | |||
if (!organizationDto.isPresent() || !userSession.hasMembership(organizationDto.get())) { | |||
// In order to display the authors facet, the organization parameter must be set and the user | |||
// must be member of this organization | |||
requestedFacets.remove(PARAM_AUTHORS); | |||
} | |||
} | |||
options.addFacets(requestedFacets); | |||
return options; | |||
} |
@@ -57,6 +57,7 @@ public class SearchResponseData { | |||
private final ListMultimap<String, String> actionsByIssueKey = ArrayListMultimap.create(); | |||
private final ListMultimap<String, Transition> transitionsByIssueKey = ArrayListMultimap.create(); | |||
private final Set<String> updatableComments = new HashSet<>(); | |||
private final Set<String> userOrganizationUuids = new HashSet<>(); | |||
public SearchResponseData(IssueDto issue) { | |||
checkNotNull(issue); | |||
@@ -173,6 +174,14 @@ public class SearchResponseData { | |||
this.organizationKeysByUuid.put(organizationDto.getUuid(), organizationDto.getKey()); | |||
} | |||
public void setUserOrganizationUuids(Set<String> organizationUuids) { | |||
this.userOrganizationUuids.addAll(organizationUuids); | |||
} | |||
public Set<String> getUserOrganizationUuids() { | |||
return this.userOrganizationUuids; | |||
} | |||
@CheckForNull | |||
public UserDto getUserByUuid(@Nullable String userUuid) { | |||
UserDto userDto = usersByUuid.get(userUuid); |
@@ -82,8 +82,7 @@ public class SearchResponseFormat { | |||
this.avatarFactory = avatarFactory; | |||
} | |||
public SearchWsResponse formatSearch(Set<SearchAdditionalField> fields, SearchResponseData data, | |||
Paging paging, @Nullable Facets facets) { | |||
public SearchWsResponse formatSearch(Set<SearchAdditionalField> fields, SearchResponseData data, Paging paging, @Nullable Facets facets) { | |||
SearchWsResponse.Builder response = SearchWsResponse.newBuilder(); | |||
formatPaging(paging, response); | |||
@@ -195,7 +194,11 @@ public class SearchResponseFormat { | |||
setNullable(dto.getLine(), issueBuilder::setLine); | |||
setNullable(emptyToNull(dto.getChecksum()), issueBuilder::setHash); | |||
completeIssueLocations(dto, issueBuilder, data); | |||
issueBuilder.setAuthor(nullToEmpty(dto.getAuthorLogin())); | |||
// Filter author only if user is member of the organization | |||
if (data.getUserOrganizationUuids().contains(component.getOrganizationUuid())) { | |||
issueBuilder.setAuthor(nullToEmpty(dto.getAuthorLogin())); | |||
} | |||
setNullable(dto.getIssueCreationDate(), issueBuilder::setCreationDate, DateUtils::formatDateTime); | |||
setNullable(dto.getIssueUpdateDate(), issueBuilder::setUpdateDate, DateUtils::formatDateTime); | |||
setNullable(dto.getIssueCloseDate(), issueBuilder::setCloseDate, DateUtils::formatDateTime); |
@@ -33,6 +33,7 @@ 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.db.organization.OrganizationDto; | |||
import org.sonar.db.protobuf.DbFileSources; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.source.HtmlSourceDecorator; | |||
@@ -138,19 +139,24 @@ public class LinesAction implements SourcesWsAction { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
ComponentDto file = loadComponent(dbSession, request); | |||
userSession.checkComponentPermission(UserRole.CODEVIEWER, file); | |||
int from = request.mandatoryParamAsInt(PARAM_FROM); | |||
int to = MoreObjects.firstNonNull(request.paramAsInt(PARAM_TO), Integer.MAX_VALUE); | |||
Iterable<DbFileSources.Line> lines = checkFoundWithOptional(sourceService.getLines(dbSession, file.uuid(), from, to), "No source found for file '%s'", file.getDbKey()); | |||
try (JsonWriter json = response.newJsonWriter()) { | |||
json.beginObject(); | |||
writeSource(lines, json); | |||
writeSource(lines, json, isMemberOfOrganization(dbSession, file)); | |||
json.endObject(); | |||
} | |||
} | |||
} | |||
private boolean isMemberOfOrganization(DbSession dbSession, ComponentDto file) { | |||
OrganizationDto organizationDto = dbClient.organizationDao().selectByUuid(dbSession, file.getOrganizationUuid()) | |||
.orElseThrow(() -> new IllegalStateException(String.format("Organization with uuid '%s' not found", file.getOrganizationUuid()))); | |||
return !userSession.hasMembership(organizationDto); | |||
} | |||
private ComponentDto loadComponent(DbSession dbSession, Request wsRequest) { | |||
String componentKey = wsRequest.param(PARAM_KEY); | |||
String componentId = wsRequest.param(PARAM_UUID); | |||
@@ -166,14 +172,16 @@ public class LinesAction implements SourcesWsAction { | |||
return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); | |||
} | |||
private void writeSource(Iterable<DbFileSources.Line> lines, JsonWriter json) { | |||
private void writeSource(Iterable<DbFileSources.Line> lines, JsonWriter json, boolean filterScmAuthors) { | |||
json.name("sources").beginArray(); | |||
for (DbFileSources.Line line : lines) { | |||
json.beginObject() | |||
.prop("line", line.getLine()) | |||
.prop("code", htmlSourceDecorator.getDecoratedSourceAsHtml(line.getSource(), line.getHighlighting(), line.getSymbols())) | |||
.prop("scmAuthor", line.getScmAuthor()) | |||
.prop("scmRevision", line.getScmRevision()); | |||
if (!filterScmAuthors) { | |||
json.prop("scmAuthor", line.getScmAuthor()); | |||
} | |||
if (line.hasScmDate()) { | |||
json.prop("scmDate", DateUtils.formatDateTime(new Date(line.getScmDate()))); | |||
} |
@@ -75,11 +75,11 @@ public abstract class AbstractUserSession implements UserSession { | |||
protected abstract boolean hasProjectUuidPermission(String permission, String projectUuid); | |||
@Override | |||
public final boolean hasMembership(OrganizationDto organization) { | |||
return isRoot() || hasMembershipImpl(organization); | |||
public final boolean hasMembership(OrganizationDto organizationDto) { | |||
return isRoot() || hasMembershipImpl(organizationDto); | |||
} | |||
protected abstract boolean hasMembershipImpl(OrganizationDto organization); | |||
protected abstract boolean hasMembershipImpl(OrganizationDto organizationDto); | |||
@Override | |||
public final List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) { |
@@ -124,7 +124,7 @@ public final class DoPrivileged { | |||
} | |||
@Override | |||
public boolean hasMembershipImpl(OrganizationDto organization) { | |||
public boolean hasMembershipImpl(OrganizationDto organizationDto) { | |||
return true; | |||
} | |||
} |
@@ -228,18 +228,18 @@ public class ServerUserSession extends AbstractUserSession { | |||
} | |||
@Override | |||
public boolean hasMembershipImpl(OrganizationDto organization) { | |||
return isMember(organization); | |||
public boolean hasMembershipImpl(OrganizationDto organizationDto) { | |||
return isMember(organizationDto.getUuid()); | |||
} | |||
private boolean isMember(OrganizationDto organization) { | |||
private boolean isMember(String organizationUuid) { | |||
if (!isLoggedIn()) { | |||
return false; | |||
} | |||
if (isRoot()) { | |||
return true; | |||
} | |||
String organizationUuid = organization.getUuid(); | |||
if (organizationMembership.contains(organizationUuid)) { | |||
return true; | |||
} |
@@ -166,8 +166,8 @@ public class ThreadLocalUserSession implements UserSession { | |||
} | |||
@Override | |||
public boolean hasMembership(OrganizationDto organization) { | |||
return get().hasMembership(organization); | |||
public boolean hasMembership(OrganizationDto organizationDto) { | |||
return get().hasMembership(organizationDto); | |||
} | |||
@Override |
@@ -32,6 +32,7 @@ import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.rule.RuleDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.db.user.UserTesting; | |||
@@ -67,7 +68,7 @@ public class ChangelogActionTest { | |||
private ComponentDto project; | |||
private ComponentDto file; | |||
private WsActionTester tester = new WsActionTester(new ChangelogAction(db.getDbClient(), new IssueFinder(db.getDbClient(), userSession), new AvatarResolverImpl())); | |||
private WsActionTester tester = new WsActionTester(new ChangelogAction(db.getDbClient(), new IssueFinder(db.getDbClient(), userSession), new AvatarResolverImpl(), userSession)); | |||
@Before | |||
public void setUp() throws Exception { | |||
@@ -79,7 +80,9 @@ public class ChangelogActionTest { | |||
public void return_changelog() { | |||
UserDto user = insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(user.getUuid()).setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -92,14 +95,30 @@ public class ChangelogActionTest { | |||
assertThat(result.getChangelogList().get(0).getDiffsList()).extracting(Diff::getKey, Diff::getOldValue, Diff::getNewValue).containsOnly(tuple("severity", "MAJOR", "BLOCKER")); | |||
} | |||
@Test | |||
public void return_empty_changelog_when_not_member() { | |||
UserDto user = insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john") | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(user.getUuid()).setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
assertThat(result.getChangelogList()).hasSize(0); | |||
} | |||
@Test | |||
public void changelog_of_file_move_contains_file_names() { | |||
RuleDto rule = db.rules().insertRule(newRuleDto()); | |||
ComponentDto project = db.components().insertPrivateProject(db.organizations().insert()); | |||
OrganizationDto org = db.organizations().insert(); | |||
ComponentDto project = db.components().insertPrivateProject(org); | |||
ComponentDto file1 = db.components().insertComponent(newFileDto(project)); | |||
ComponentDto file2 = db.components().insertComponent(newFileDto(project)); | |||
IssueDto issueDto = db.issues().insertIssue(newDto(rule, file2, project)); | |||
userSession.logIn("john").addProjectPermission(USER, project, file1, file2); | |||
userSession.logIn("john") | |||
.addMembership(org) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setDiff("file", file1.uuid(), file2.uuid()).setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -114,7 +133,9 @@ public class ChangelogActionTest { | |||
@Test | |||
public void changelog_of_file_move_is_empty_when_files_does_not_exists() { | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setDiff("file", "UNKNOWN_1", "UNKNOWN_2").setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -128,7 +149,9 @@ public class ChangelogActionTest { | |||
public void return_changelog_on_user_without_email() { | |||
UserDto user = db.users().insertUser(UserTesting.newUserDto("john", "John", null)); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(user.getUuid()).setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -142,7 +165,9 @@ public class ChangelogActionTest { | |||
@Test | |||
public void return_changelog_not_having_user() { | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(null).setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -157,7 +182,9 @@ public class ChangelogActionTest { | |||
@Test | |||
public void return_changelog_on_none_existing_user() { | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid("UNKNOWN").setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -173,7 +200,9 @@ public class ChangelogActionTest { | |||
public void return_multiple_diffs() { | |||
UserDto user = insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(user.getUuid()) | |||
.setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date()) | |||
.setDiff("status", "RESOLVED", "CLOSED").setCreationDate(new Date())); | |||
@@ -189,7 +218,9 @@ public class ChangelogActionTest { | |||
public void return_changelog_when_no_old_value() { | |||
UserDto user = insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(user.getUuid()).setDiff("severity", null, "BLOCKER").setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -202,7 +233,9 @@ public class ChangelogActionTest { | |||
public void return_changelog_when_no_new_value() { | |||
UserDto user = insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(user.getUuid()).setDiff("severity", "MAJOR", null).setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -215,7 +248,9 @@ public class ChangelogActionTest { | |||
public void return_many_changelog() { | |||
UserDto user = insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, | |||
new FieldDiffs().setUserUuid(user.getUuid()).setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date()), | |||
new FieldDiffs().setDiff("status", "RESOLVED", "CLOSED").setCreationDate(new Date())); | |||
@@ -229,7 +264,9 @@ public class ChangelogActionTest { | |||
public void replace_technical_debt_key_by_effort() { | |||
UserDto user = insertUser(); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs().setUserUuid(user.getUuid()).setDiff("technicalDebt", "10", "20").setCreationDate(new Date())); | |||
ChangelogWsResponse result = call(issueDto.getKey()); | |||
@@ -261,7 +298,9 @@ public class ChangelogActionTest { | |||
public void test_example() { | |||
UserDto user = db.users().insertUser(newUserDto("john.smith", "John Smith", "john@smith.com")); | |||
IssueDto issueDto = db.issues().insertIssue(newIssue()); | |||
userSession.logIn("john").addProjectPermission(USER, project, file); | |||
userSession.logIn("john") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(USER, project, file); | |||
db.issues().insertFieldDiffs(issueDto, new FieldDiffs() | |||
.setUserUuid(user.getUuid()) | |||
.setDiff("severity", "MAJOR", "BLOCKER").setCreationDate(new Date()) |
@@ -25,6 +25,7 @@ import java.util.Date; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.api.resources.Languages; | |||
import org.sonar.api.resources.Qualifiers; | |||
import org.sonar.api.rule.RuleKey; | |||
@@ -37,13 +38,14 @@ import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.issue.IssueFieldsSetter; | |||
import org.sonar.server.issue.index.IssueQueryFactory; | |||
import org.sonar.server.issue.TransitionService; | |||
import org.sonar.server.issue.index.IssueIndex; | |||
import org.sonar.server.issue.index.IssueIndexer; | |||
import org.sonar.server.issue.index.IssueIteratorFactory; | |||
import org.sonar.server.issue.index.IssueQueryFactory; | |||
import org.sonar.server.issue.workflow.FunctionExecutor; | |||
import org.sonar.server.issue.workflow.IssueWorkflow; | |||
import org.sonar.server.permission.index.PermissionIndexerTester; | |||
@@ -105,8 +107,8 @@ public class SearchActionComponentsTest { | |||
private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl()); | |||
private PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer); | |||
private WsActionTester ws = new WsActionTester(new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat, System2.INSTANCE, | |||
dbClient)); | |||
private WsActionTester ws = new WsActionTester(new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat, | |||
new MapSettings().asConfig(), System2.INSTANCE, dbClient)); | |||
@Test | |||
public void search_all_issues_when_no_parameter() { | |||
@@ -644,7 +646,7 @@ public class SearchActionComponentsTest { | |||
} | |||
@Test | |||
public void search_by_author() throws Exception { | |||
public void search_by_author() { | |||
ComponentDto project = db.components().insertPublicProject(p -> p.setDbKey("PK1")); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "F1").setDbKey("FK1")); | |||
RuleDefinitionDto rule = db.rules().insert(r -> r.setRuleKey(RuleKey.of("xoo", "x1"))); | |||
@@ -653,6 +655,10 @@ public class SearchActionComponentsTest { | |||
allowAnyoneOnProjects(project); | |||
indexIssues(); | |||
UserDto user = db.users().insertUser(); | |||
db.organizations().addMember(db.getDefaultOrganization(), user); | |||
userSession.logIn(user).addMembership(db.getDefaultOrganization()); | |||
ws.newRequest() | |||
.setParam(IssuesWsParameters.PARAM_AUTHORS, "leia") | |||
.setParam(WebService.Param.FACETS, "authors") |
@@ -27,6 +27,7 @@ import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.api.resources.Languages; | |||
import org.sonar.api.rule.RuleStatus; | |||
import org.sonar.api.rules.RuleType; | |||
@@ -98,28 +99,28 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES; | |||
public class SearchActionTest { | |||
@Rule | |||
public UserSessionRule userSessionRule = standalone(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
@Rule | |||
public EsTester es = EsTester.create(); | |||
@Rule | |||
public ExpectedException expectedException = none(); | |||
private DbClient dbClient = db.getDbClient(); | |||
private DbSession session = db.getSession(); | |||
private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule)); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient)); | |||
private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSessionRule); | |||
private IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter(); | |||
private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter); | |||
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSessionRule, dbClient, new TransitionService(userSessionRule, issueWorkflow)); | |||
private Languages languages = new Languages(); | |||
private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl()); | |||
private WsActionTester ws = new WsActionTester(new SearchAction(userSessionRule, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat, System2.INSTANCE, | |||
dbClient)); | |||
private StartupIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer); | |||
@Rule | |||
public UserSessionRule userSession = standalone(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
@Rule | |||
public EsTester es = EsTester.create(); | |||
@Rule | |||
public ExpectedException expectedException = none(); | |||
private DbClient dbClient = db.getDbClient(); | |||
private DbSession session = db.getSession(); | |||
private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession)); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient)); | |||
private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession); | |||
private IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter(); | |||
private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter); | |||
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow)); | |||
private Languages languages = new Languages(); | |||
private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl()); | |||
private WsActionTester ws = new WsActionTester(new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat, | |||
new MapSettings().asConfig(), System2.INSTANCE, dbClient)); | |||
private StartupIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer); | |||
private OrganizationDto defaultOrganization; | |||
private OrganizationDto otherOrganization1; | |||
@@ -248,6 +249,7 @@ public class SearchActionTest { | |||
@Test | |||
public void response_contains_all_fields_except_additional_fields() { | |||
UserDto simon = db.users().insertUser(u -> u.setLogin("simon").setName("Simon").setEmail("simon@email.com")); | |||
db.organizations().addMember(otherOrganization2, simon); | |||
ComponentDto project = insertComponent(ComponentTesting.newPublicProjectDto(otherOrganization2, "PROJECT_ID").setDbKey("PROJECT_KEY")); | |||
indexPermissions(); | |||
@@ -269,9 +271,34 @@ public class SearchActionTest { | |||
db.issues().insertIssue(issue); | |||
indexIssues(); | |||
userSession.logIn(simon); | |||
ws.newRequest().execute().assertJson(this.getClass(), "response_contains_all_fields_except_additional_fields.json"); | |||
} | |||
@Test | |||
public void hide_author_if_not_member_of_organization() { | |||
ComponentDto project = insertComponent(ComponentTesting.newPublicProjectDto(otherOrganization2, "PROJECT_ID").setDbKey("PROJECT_KEY")); | |||
indexPermissions(); | |||
ComponentDto file = insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); | |||
IssueDto issue = newDto(newExternalRule(), file, project) | |||
.setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2") | |||
.setEffort(10L) | |||
.setLine(42) | |||
.setChecksum("a227e508d6646b55a086ee11d63b21e9") | |||
.setMessage("the message") | |||
.setStatus(STATUS_RESOLVED) | |||
.setResolution(RESOLUTION_FIXED) | |||
.setSeverity("MAJOR") | |||
.setAuthorLogin("John") | |||
.setTags(asList("bug", "owasp")) | |||
.setIssueCreationDate(DateUtils.parseDateTime("2014-09-04T00:00:00+0100")) | |||
.setIssueUpdateDate(DateUtils.parseDateTime("2017-12-04T00:00:00+0100")); | |||
db.issues().insertIssue(issue); | |||
indexIssues(); | |||
ws.newRequest().execute().assertJson(this.getClass(), "author_is_hidden.json"); | |||
} | |||
@Test | |||
public void issue_with_cross_file_locations() { | |||
UserDto simon = db.users().insertUser(); | |||
@@ -369,7 +396,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn(john); | |||
userSession.logIn(john); | |||
ws.newRequest() | |||
.setParam("additionalFields", "comments,users") | |||
.execute() | |||
@@ -405,7 +432,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn(john); | |||
userSession.logIn(john); | |||
TestResponse result = ws.newRequest().setParam(PARAM_HIDE_COMMENTS, "true").execute(); | |||
result.assertJson(this.getClass(), "issue_with_comment_hidden.json"); | |||
assertThat(result.getInput()).doesNotContain(fabrice.getLogin()); | |||
@@ -426,7 +453,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn("john"); | |||
userSession.logIn("john"); | |||
ws.newRequest() | |||
.setParam("additionalFields", "_all").execute() | |||
.assertJson(this.getClass(), "load_additional_fields.json"); | |||
@@ -450,7 +477,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn("john") | |||
userSession.logIn("john") | |||
.addProjectPermission(ISSUE_ADMIN, project); // granted by Anyone | |||
ws.newRequest() | |||
.setParam("additionalFields", "_all").execute() | |||
@@ -468,7 +495,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn("john") | |||
userSession.logIn("john") | |||
.addProjectPermission(ISSUE_ADMIN, project); // granted by Anyone | |||
indexPermissions(); | |||
@@ -552,7 +579,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn("john"); | |||
userSession.logIn("john"); | |||
ws.newRequest() | |||
.setParam("resolved", "false") | |||
.setParam(PARAM_COMPONENT_KEYS, project.getKey()) | |||
@@ -579,7 +606,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn(john); | |||
userSession.logIn(john); | |||
ws.newRequest() | |||
.setParam("resolved", "false") | |||
.setParam(PARAM_COMPONENT_KEYS, project.getKey()) | |||
@@ -607,7 +634,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn(john); | |||
userSession.logIn(john); | |||
ws.newRequest() | |||
.setParam(PARAM_COMPONENT_KEYS, project.getKey()) | |||
.setParam("resolved", "false") | |||
@@ -623,7 +650,7 @@ public class SearchActionTest { | |||
// login looks like an invalid regexp | |||
UserDto user = db.users().insertUser(u -> u.setLogin("foo[").setName("foo").setEmail("foo@email.com")); | |||
userSessionRule.logIn(user); | |||
userSession.logIn(user); | |||
// should not fail | |||
ws.newRequest() | |||
@@ -670,7 +697,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn(john); | |||
userSession.logIn(john); | |||
ws.newRequest() | |||
.setParam("resolved", "false") | |||
@@ -718,7 +745,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn(john); | |||
userSession.logIn(john); | |||
Issues.SearchWsResponse response = ws.newRequest() | |||
.setParam("resolved", "false") | |||
@@ -732,7 +759,7 @@ public class SearchActionTest { | |||
@Test | |||
public void filter_by_assigned_to_me_unauthenticated() { | |||
UserDto poy = db.users().insertUser(u -> u.setLogin("poy").setName("poypoy").setEmail("poypoy@email.com")); | |||
userSessionRule.logIn(poy); | |||
userSession.logIn(poy); | |||
// TODO : check test title w julien | |||
@@ -803,7 +830,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn(john); | |||
userSession.logIn(john); | |||
ws.newRequest() | |||
.setParam("resolved", "false") | |||
.setParam("assignees", "alice") | |||
@@ -925,7 +952,7 @@ public class SearchActionTest { | |||
session.commit(); | |||
indexIssues(); | |||
userSessionRule.logIn("john"); | |||
userSession.logIn("john"); | |||
ws.newRequest() | |||
.setParam("resolved", "false") | |||
.setParam(WebService.Param.FACETS, "severities") | |||
@@ -979,7 +1006,7 @@ public class SearchActionTest { | |||
.setResourceId(project.getId()) | |||
.setRole(permission)); | |||
session.commit(); | |||
userSessionRule.logIn().addProjectPermission(permission, project); | |||
userSession.logIn().addProjectPermission(permission, project); | |||
} | |||
private ComponentDto insertComponent(ComponentDto component) { |
@@ -0,0 +1,167 @@ | |||
package org.sonar.server.issue.ws;/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 com.google.gson.JsonElement; | |||
import com.google.gson.JsonParser; | |||
import java.time.Clock; | |||
import java.util.Arrays; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.api.resources.Languages; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.Durations; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.issue.IssueFieldsSetter; | |||
import org.sonar.server.issue.TransitionService; | |||
import org.sonar.server.issue.index.IssueIndex; | |||
import org.sonar.server.issue.index.IssueIndexer; | |||
import org.sonar.server.issue.index.IssueIteratorFactory; | |||
import org.sonar.server.issue.index.IssueQueryFactory; | |||
import org.sonar.server.issue.workflow.FunctionExecutor; | |||
import org.sonar.server.issue.workflow.IssueWorkflow; | |||
import org.sonar.server.permission.index.PermissionIndexerTester; | |||
import org.sonar.server.permission.index.WebAuthorizationTypeSupport; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.WsActionTester; | |||
import org.sonar.server.ws.WsResponseCommonFormat; | |||
import org.sonar.test.JsonAssert; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.api.server.ws.WebService.Param.FACETS; | |||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ORGANIZATION; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PROJECT_UUIDS; | |||
public class SearchActionTestOnSonarCloud { | |||
@Rule | |||
public UserSessionRule userSession = UserSessionRule.standalone(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
@Rule | |||
public EsTester es = EsTester.create(); | |||
private MapSettings mapSettings = new MapSettings() | |||
.setProperty("sonar.sonarcloud.enabled", true); | |||
private DbClient dbClient = db.getDbClient(); | |||
private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession)); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient)); | |||
//private ViewIndexer viewIndexer = new ViewIndexer(dbClient, es.client()); | |||
private IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession); | |||
private IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter(); | |||
private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter); | |||
private SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow)); | |||
private Languages languages = new Languages(); | |||
private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), new WsResponseCommonFormat(languages), languages, new AvatarResolverImpl()); | |||
private PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer); | |||
private WsActionTester ws = new WsActionTester(new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat, | |||
mapSettings.asConfig(), System2.INSTANCE, dbClient)); | |||
private OrganizationDto organization; | |||
private UserDto user; | |||
private ComponentDto project; | |||
@Before | |||
public void setup() { | |||
organization = db.organizations().insert(o -> o.setKey("org-1")); | |||
user = db.users().insertUser(); | |||
project = db.components().insertPublicProject(organization, p -> p.setDbKey("PK1")); | |||
ComponentDto file = db.components().insertComponent(newFileDto(project, null, "F1").setDbKey("FK1")); | |||
RuleDefinitionDto rule = db.rules().insert(r -> r.setRuleKey(RuleKey.of("xoo", "x1"))); | |||
db.issues().insert(rule, project, file, i -> i.setAuthorLogin("leia").setKee("2bd4eac2-b650-4037-80bc-7b112bd4eac2")); | |||
db.issues().insert(rule, project, file, i -> i.setAuthorLogin("luke@skywalker.name").setKee("82fd47d4-b650-4037-80bc-7b1182fd47d4")); | |||
db.commit(); | |||
allowAnyoneOnProjects(project); | |||
indexIssues(); | |||
} | |||
@Test | |||
public void authors_facet_is_hidden_if_organization_is_not_set() { | |||
db.organizations().addMember(organization, user); | |||
userSession | |||
.logIn(user) | |||
.addMembership(organization); | |||
String input = ws.newRequest() | |||
.setParam(PARAM_PROJECT_UUIDS, project.uuid()) | |||
.setParam(FACETS, "authors") | |||
.execute() | |||
.getInput(); | |||
JsonAssert.assertJson(input).isSimilarTo(this.getClass().getResource(this.getClass().getSimpleName() + "/no_authors_facet.json")); | |||
JsonElement gson = new JsonParser().parse(input); | |||
assertThat(gson.getAsJsonObject().get("facets")).isNull(); | |||
} | |||
@Test | |||
public void authors_facet_is_hidden_if_user_is_not_a_member_of_the_organization() { | |||
userSession | |||
.logIn(user); | |||
String input = ws.newRequest() | |||
.setParam(PARAM_PROJECT_UUIDS, project.uuid()) | |||
.setParam(FACETS, "authors") | |||
.execute() | |||
.getInput(); | |||
JsonAssert.assertJson(input).isSimilarTo(this.getClass().getResource(this.getClass().getSimpleName() + "/no_author_and_no_authors_facet.json")); | |||
JsonElement gson = new JsonParser().parse(input); | |||
assertThat(gson.getAsJsonObject().get("facets")).isNull(); | |||
} | |||
@Test | |||
public void authors_facet_is_shown_if_organization_is_set_and_user_is_member_of_the_organization() { | |||
db.organizations().addMember(organization, user); | |||
userSession | |||
.logIn(user) | |||
.addMembership(organization); | |||
ws.newRequest() | |||
.setParam(PARAM_PROJECT_UUIDS, project.uuid()) | |||
.setParam(FACETS, "authors") | |||
.setParam(PARAM_ORGANIZATION, organization.getKey()) | |||
.execute() | |||
.assertJson(this.getClass(), "with_authors_facet.json"); | |||
} | |||
private void indexIssues() { | |||
issueIndexer.indexOnStartup(null); | |||
} | |||
private void allowAnyoneOnProjects(ComponentDto... projects) { | |||
userSession.registerComponents(projects); | |||
Arrays.stream(projects).forEach(p -> permissionIndexer.allowOnlyAnyone(p)); | |||
} | |||
} |
@@ -19,12 +19,10 @@ | |||
*/ | |||
package org.sonar.server.source.ws; | |||
import java.io.IOException; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.mockito.invocation.InvocationOnMock; | |||
import org.mockito.stubbing.Answer; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.web.UserRole; | |||
@@ -32,8 +30,10 @@ import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDao; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.protobuf.DbFileSources; | |||
import org.sonar.db.source.FileSourceDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.component.TestComponentFinder; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
@@ -42,6 +42,7 @@ import org.sonar.server.source.SourceService; | |||
import org.sonar.server.source.index.FileSourceTesting; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.ws.WsTester; | |||
import org.sonar.server.ws.WsTester.TestRequest; | |||
import static java.lang.String.format; | |||
import static org.mockito.ArgumentMatchers.anyString; | |||
@@ -52,74 +53,60 @@ import static org.sonar.db.component.ComponentTesting.newFileDto; | |||
public class LinesActionTest { | |||
private static final String PROJECT_UUID = "abcd"; | |||
private static final String FILE_UUID = "efgh"; | |||
private static final String FILE_KEY = "Foo.java"; | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public DbTester db = DbTester.create(System2.INSTANCE); | |||
@Rule | |||
public UserSessionRule userSession = UserSessionRule.standalone(); | |||
private SourceService sourceService; | |||
private HtmlSourceDecorator htmlSourceDecorator; | |||
private ComponentDao componentDao; | |||
private ComponentDto project; | |||
private ComponentDto file; | |||
private ComponentDao componentDao = new ComponentDao(); | |||
private ComponentDto privateProject; | |||
private OrganizationDto organization; | |||
private WsTester wsTester; | |||
@Before | |||
public void setUp() { | |||
htmlSourceDecorator = mock(HtmlSourceDecorator.class); | |||
when(htmlSourceDecorator.getDecoratedSourceAsHtml(anyString(), anyString(), anyString())).then(new Answer<String>() { | |||
@Override | |||
public String answer(InvocationOnMock invocationOnMock) { | |||
return "<p>" + invocationOnMock.getArguments()[0] + "</p>"; | |||
} | |||
}); | |||
sourceService = new SourceService(db.getDbClient(), htmlSourceDecorator); | |||
componentDao = new ComponentDao(); | |||
HtmlSourceDecorator htmlSourceDecorator = mock(HtmlSourceDecorator.class); | |||
when(htmlSourceDecorator.getDecoratedSourceAsHtml(anyString(), anyString(), anyString())).then((Answer<String>) | |||
invocationOnMock -> "<p>" + invocationOnMock.getArguments()[0] + "</p>"); | |||
SourceService sourceService = new SourceService(db.getDbClient(), htmlSourceDecorator); | |||
wsTester = new WsTester(new SourcesWs( | |||
new LinesAction(TestComponentFinder.from(db), db.getDbClient(), sourceService, htmlSourceDecorator, userSession))); | |||
project = ComponentTesting.newPrivateProjectDto(db.organizations().insert(), PROJECT_UUID); | |||
file = newFileDto(project, null, FILE_UUID).setDbKey(FILE_KEY); | |||
organization = db.organizations().insert(); | |||
privateProject = ComponentTesting.newPrivateProjectDto(organization); | |||
} | |||
@Test | |||
public void show_source() throws Exception { | |||
insertFileWithData(FileSourceTesting.newFakeData(3).build()); | |||
setUserWithValidPermission(); | |||
ComponentDto file = insertFileWithData(FileSourceTesting.newFakeData(3).build(), privateProject); | |||
setUserWithValidPermission(file); | |||
WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("uuid", FILE_UUID); | |||
TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("uuid", file.uuid()); | |||
request.execute().assertJson(getClass(), "show_source.json"); | |||
} | |||
@Test | |||
public void fail_to_show_source_if_no_source_found() throws Exception { | |||
setUserWithValidPermission(); | |||
insertFile(); | |||
ComponentDto file = insertFile(privateProject); | |||
setUserWithValidPermission(file); | |||
expectedException.expect(NotFoundException.class); | |||
wsTester.newGetRequest("api/sources", "lines").setParam("uuid", FILE_UUID).execute(); | |||
wsTester.newGetRequest("api/sources", "lines").setParam("uuid", file.uuid()).execute(); | |||
} | |||
@Test | |||
public void show_paginated_lines() throws Exception { | |||
setUserWithValidPermission(); | |||
insertFileWithData(FileSourceTesting.newFakeData(3).build()); | |||
ComponentDto file = insertFileWithData(FileSourceTesting.newFakeData(3).build(), privateProject); | |||
setUserWithValidPermission(file); | |||
WsTester.TestRequest request = wsTester | |||
wsTester | |||
.newGetRequest("api/sources", "lines") | |||
.setParam("uuid", FILE_UUID) | |||
.setParam("uuid", file.uuid()) | |||
.setParam("from", "3") | |||
.setParam("to", "3"); | |||
request.execute().assertJson(getClass(), "show_paginated_lines.json"); | |||
.setParam("to", "3") | |||
.execute() | |||
.assertJson(getClass(), "show_paginated_lines.json"); | |||
} | |||
@Test | |||
@@ -133,13 +120,16 @@ public class LinesActionTest { | |||
.setFileUuid(file.uuid()) | |||
.setSourceData(FileSourceTesting.newFakeData(3).build())); | |||
db.commit(); | |||
userSession.logIn("login").addProjectPermission(UserRole.CODEVIEWER, project, file); | |||
WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines") | |||
.setParam("key", file.getKey()) | |||
.setParam("branch", file.getBranch()); | |||
userSession.logIn("login") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(UserRole.CODEVIEWER, project, file); | |||
request.execute().assertJson(getClass(), "show_source.json"); | |||
wsTester.newGetRequest("api/sources", "lines") | |||
.setParam("key", file.getKey()) | |||
.setParam("branch", file.getBranch()) | |||
.execute() | |||
.assertJson(getClass(), "show_source.json"); | |||
} | |||
@Test | |||
@@ -153,13 +143,16 @@ public class LinesActionTest { | |||
.setFileUuid(file.uuid()) | |||
.setSourceData(FileSourceTesting.newFakeData(3).build())); | |||
db.commit(); | |||
userSession.logIn("login").addProjectPermission(UserRole.CODEVIEWER, project, file); | |||
WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines") | |||
.setParam("key", file.getKey()) | |||
.setParam("pullRequest", file.getPullRequest()); | |||
userSession.logIn("login") | |||
.addMembership(db.getDefaultOrganization()) | |||
.addProjectPermission(UserRole.CODEVIEWER, project, file); | |||
request.execute().assertJson(getClass(), "show_source.json"); | |||
wsTester.newGetRequest("api/sources", "lines") | |||
.setParam("key", file.getKey()) | |||
.setParam("pullRequest", file.getPullRequest()) | |||
.execute() | |||
.assertJson(getClass(), "show_source.json"); | |||
} | |||
@Test | |||
@@ -167,7 +160,7 @@ public class LinesActionTest { | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Either 'uuid' or 'key' must be provided"); | |||
WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines"); | |||
TestRequest request = wsTester.newGetRequest("api/sources", "lines"); | |||
request.execute(); | |||
} | |||
@@ -176,7 +169,7 @@ public class LinesActionTest { | |||
expectedException.expect(NotFoundException.class); | |||
expectedException.expectMessage("Component key 'Foo.java' not found"); | |||
WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("key", FILE_KEY); | |||
TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("key", "Foo.java"); | |||
request.execute(); | |||
} | |||
@@ -185,50 +178,50 @@ public class LinesActionTest { | |||
expectedException.expect(NotFoundException.class); | |||
expectedException.expectMessage("Component id 'ABCD' not found"); | |||
WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("uuid", "ABCD"); | |||
TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("uuid", "ABCD"); | |||
request.execute(); | |||
} | |||
@Test | |||
public void fail_when_file_is_removed() throws Exception { | |||
ComponentDto file = newFileDto(project).setDbKey("file-key").setEnabled(false); | |||
db.components().insertComponents(project, file); | |||
setUserWithValidPermission(); | |||
ComponentDto file = newFileDto(privateProject).setDbKey("file-key").setEnabled(false); | |||
db.components().insertComponents(privateProject, file); | |||
setUserWithValidPermission(file); | |||
expectedException.expect(NotFoundException.class); | |||
expectedException.expectMessage("Component key 'file-key' not found"); | |||
WsTester.TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("key", "file-key"); | |||
TestRequest request = wsTester.newGetRequest("api/sources", "lines").setParam("key", "file-key"); | |||
request.execute(); | |||
} | |||
@Test(expected = ForbiddenException.class) | |||
public void check_permission() throws Exception { | |||
insertFileWithData(FileSourceTesting.newFakeData(1).build()); | |||
ComponentDto file = insertFileWithData(FileSourceTesting.newFakeData(1).build(), privateProject); | |||
userSession.logIn("login"); | |||
wsTester.newGetRequest("api/sources", "lines") | |||
.setParam("uuid", FILE_UUID) | |||
.setParam("uuid", file.uuid()) | |||
.execute(); | |||
} | |||
@Test | |||
public void display_deprecated_fields() throws Exception { | |||
insertFileWithData(FileSourceTesting.newFakeData(1).build()); | |||
setUserWithValidPermission(); | |||
ComponentDto file = insertFileWithData(FileSourceTesting.newFakeData(1).build(), privateProject); | |||
setUserWithValidPermission(file); | |||
WsTester.TestRequest request = wsTester | |||
wsTester | |||
.newGetRequest("api/sources", "lines") | |||
.setParam("uuid", FILE_UUID); | |||
request.execute().assertJson(getClass(), "display_deprecated_fields.json"); | |||
.setParam("uuid", file.uuid()) | |||
.execute() | |||
.assertJson(getClass(), "display_deprecated_fields.json"); | |||
} | |||
@Test | |||
public void use_deprecated_overall_coverage_fields_if_exists() throws Exception { | |||
DbFileSources.Data.Builder dataBuilder = DbFileSources.Data.newBuilder(); | |||
insertFileWithData(dataBuilder.addLines(newLineBuilder() | |||
ComponentDto file = insertFileWithData(dataBuilder.addLines(newLineBuilder() | |||
.setDeprecatedOverallLineHits(1) | |||
.setDeprecatedOverallConditions(2) | |||
.setDeprecatedOverallCoveredConditions(3) | |||
@@ -237,31 +230,31 @@ public class LinesActionTest { | |||
.setDeprecatedUtCoveredConditions(3) | |||
.setDeprecatedItLineHits(1) | |||
.setDeprecatedItConditions(2) | |||
.setDeprecatedItCoveredConditions(3)).build()); | |||
setUserWithValidPermission(); | |||
.setDeprecatedItCoveredConditions(3)).build(), privateProject); | |||
setUserWithValidPermission(file); | |||
WsTester.TestRequest request = wsTester | |||
wsTester | |||
.newGetRequest("api/sources", "lines") | |||
.setParam("uuid", FILE_UUID); | |||
request.execute().assertJson(getClass(), "convert_deprecated_data.json"); | |||
.setParam("uuid", file.uuid()) | |||
.execute() | |||
.assertJson(getClass(), "convert_deprecated_data.json"); | |||
} | |||
@Test | |||
public void use_deprecated_ut_coverage_fields_if_exists() throws Exception { | |||
DbFileSources.Data.Builder dataBuilder = DbFileSources.Data.newBuilder(); | |||
insertFileWithData(dataBuilder.addLines(newLineBuilder() | |||
ComponentDto file = insertFileWithData(dataBuilder.addLines(newLineBuilder() | |||
.setDeprecatedUtLineHits(1) | |||
.setDeprecatedUtConditions(2) | |||
.setDeprecatedUtCoveredConditions(3) | |||
.setDeprecatedItLineHits(1) | |||
.setDeprecatedItConditions(2) | |||
.setDeprecatedItCoveredConditions(3)).build()); | |||
setUserWithValidPermission(); | |||
.setDeprecatedItCoveredConditions(3)).build(), privateProject); | |||
setUserWithValidPermission(file); | |||
WsTester.TestRequest request = wsTester | |||
TestRequest request = wsTester | |||
.newGetRequest("api/sources", "lines") | |||
.setParam("uuid", FILE_UUID); | |||
.setParam("uuid", file.uuid()); | |||
request.execute().assertJson(getClass(), "convert_deprecated_data.json"); | |||
} | |||
@@ -269,15 +262,15 @@ public class LinesActionTest { | |||
@Test | |||
public void use_deprecated_it_coverage_fields_if_exists() throws Exception { | |||
DbFileSources.Data.Builder dataBuilder = DbFileSources.Data.newBuilder(); | |||
insertFileWithData(dataBuilder.addLines(newLineBuilder() | |||
ComponentDto file = insertFileWithData(dataBuilder.addLines(newLineBuilder() | |||
.setDeprecatedItLineHits(1) | |||
.setDeprecatedItConditions(2) | |||
.setDeprecatedItCoveredConditions(3)).build()); | |||
setUserWithValidPermission(); | |||
.setDeprecatedItCoveredConditions(3)).build(), privateProject); | |||
setUserWithValidPermission(file); | |||
WsTester.TestRequest request = wsTester | |||
TestRequest request = wsTester | |||
.newGetRequest("api/sources", "lines") | |||
.setParam("uuid", FILE_UUID); | |||
.setParam("uuid", file.uuid()); | |||
request.execute().assertJson(getClass(), "convert_deprecated_data.json"); | |||
} | |||
@@ -342,22 +335,66 @@ public class LinesActionTest { | |||
.execute(); | |||
} | |||
private void insertFileWithData(DbFileSources.Data fileData) throws IOException { | |||
insertFile(); | |||
@Test | |||
public void hide_scmAuthors_if_not_member_of_organization() throws Exception { | |||
OrganizationDto org = db.organizations().insert(); | |||
ComponentDto publicProject = db.components().insertPublicProject(org); | |||
userSession.registerComponents(publicProject); | |||
DbFileSources.Data data = DbFileSources.Data.newBuilder() | |||
.addLines(newLineBuilder().setScmAuthor("isaac@asimov.com")) | |||
.build(); | |||
ComponentDto file = insertFileWithData(data, publicProject); | |||
wsTester.newGetRequest("api/sources", "lines") | |||
.setParam("uuid", file.uuid()) | |||
.execute() | |||
.assertJson(getClass(), "hide_scmAuthors.json"); | |||
} | |||
@Test | |||
public void show_scmAuthors_if_member_of_organization() throws Exception { | |||
OrganizationDto org = db.organizations().insert(); | |||
ComponentDto publicProject = db.components().insertPublicProject(org); | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user) | |||
.registerComponents(publicProject) | |||
.addMembership(org); | |||
DbFileSources.Data data = DbFileSources.Data.newBuilder() | |||
.addLines(newLineBuilder().setScmAuthor("isaac@asimov.com")) | |||
.build(); | |||
ComponentDto file = insertFileWithData(data, publicProject); | |||
wsTester.newGetRequest("api/sources", "lines") | |||
.setParam("uuid", file.uuid()) | |||
.execute() | |||
.assertJson(getClass(), "show_scmAuthors.json"); | |||
} | |||
private ComponentDto insertFileWithData(DbFileSources.Data fileData, ComponentDto project) { | |||
ComponentDto file = insertFile(project); | |||
db.getDbClient().fileSourceDao().insert(db.getSession(), new FileSourceDto() | |||
.setProjectUuid(PROJECT_UUID) | |||
.setFileUuid(FILE_UUID) | |||
.setProjectUuid(project.projectUuid()) | |||
.setFileUuid(file.uuid()) | |||
.setSourceData(fileData)); | |||
db.commit(); | |||
return file; | |||
} | |||
private void setUserWithValidPermission() { | |||
userSession.logIn("login").addProjectPermission(UserRole.CODEVIEWER, project, file); | |||
private void setUserWithValidPermission(ComponentDto file) { | |||
userSession.logIn("login") | |||
.addProjectPermission(UserRole.CODEVIEWER, privateProject, file) | |||
.addMembership(organization); | |||
} | |||
private void insertFile() { | |||
componentDao.insert(db.getSession(), project, file); | |||
private ComponentDto insertFile(ComponentDto project) { | |||
ComponentDto file = newFileDto(project); | |||
componentDao.insert(db.getSession(), file); | |||
db.getSession().commit(); | |||
return file; | |||
} | |||
private DbFileSources.Line.Builder newLineBuilder() { |
@@ -110,8 +110,8 @@ public abstract class AbstractMockUserSession<T extends AbstractMockUserSession> | |||
} | |||
@Override | |||
protected boolean hasMembershipImpl(OrganizationDto organization) { | |||
return organizationMembership.contains(organization.getUuid()); | |||
protected boolean hasMembershipImpl(OrganizationDto organizationDto) { | |||
return organizationMembership.contains(organizationDto.getUuid()); | |||
} | |||
public void addOrganizationMembership(OrganizationDto organization) { |
@@ -65,7 +65,7 @@ public class AnonymousMockUserSession extends AbstractMockUserSession<AnonymousM | |||
} | |||
@Override | |||
public boolean hasMembershipImpl(OrganizationDto organization) { | |||
public boolean hasMembershipImpl(OrganizationDto organizationDto) { | |||
return false; | |||
} | |||
} |
@@ -118,7 +118,7 @@ public class TestUserSessionFactory implements UserSessionFactory { | |||
} | |||
@Override | |||
public boolean hasMembershipImpl(OrganizationDto organization) { | |||
public boolean hasMembershipImpl(OrganizationDto organizationDto) { | |||
throw notImplemented(); | |||
} | |||
@@ -0,0 +1,24 @@ | |||
{ | |||
"issues": [ | |||
{ | |||
"organization": "my-org-2", | |||
"key": "82fd47d4-b650-4037-80bc-7b112bd4eac2", | |||
"rule": "external_xoo:x1", | |||
"severity": "MAJOR", | |||
"component": "FILE_KEY", | |||
"resolution": "FIXED", | |||
"status": "RESOLVED", | |||
"message": "the message", | |||
"effort": "10min", | |||
"line": 42, | |||
"hash": "a227e508d6646b55a086ee11d63b21e9", | |||
"tags": [ | |||
"bug", | |||
"owasp" | |||
], | |||
"creationDate": "2014-09-04T01:00:00+0200", | |||
"updateDate": "2017-12-04T00:00:00+0100", | |||
"externalRuleEngine" : "xoo" | |||
} | |||
] | |||
} |
@@ -0,0 +1,20 @@ | |||
{ | |||
"total": 2, | |||
"p": 1, | |||
"issues": [ | |||
{ | |||
"organization": "org-1", | |||
"key": "82fd47d4-b650-4037-80bc-7b1182fd47d4", | |||
"rule": "xoo:x1", | |||
"component": "FK1", | |||
"project": "PK1" | |||
}, | |||
{ | |||
"organization": "org-1", | |||
"key": "2bd4eac2-b650-4037-80bc-7b112bd4eac2", | |||
"rule": "xoo:x1", | |||
"component": "FK1", | |||
"project": "PK1" | |||
} | |||
] | |||
} |
@@ -0,0 +1,22 @@ | |||
{ | |||
"total": 2, | |||
"p": 1, | |||
"issues": [ | |||
{ | |||
"organization": "org-1", | |||
"key": "82fd47d4-b650-4037-80bc-7b1182fd47d4", | |||
"rule": "xoo:x1", | |||
"component": "FK1", | |||
"project": "PK1", | |||
"author": "luke@skywalker.name" | |||
}, | |||
{ | |||
"organization": "org-1", | |||
"key": "2bd4eac2-b650-4037-80bc-7b112bd4eac2", | |||
"rule": "xoo:x1", | |||
"component": "FK1", | |||
"project": "PK1", | |||
"author": "leia" | |||
} | |||
] | |||
} |
@@ -0,0 +1,37 @@ | |||
{ | |||
"total": 2, | |||
"p": 1, | |||
"issues": [ | |||
{ | |||
"organization": "org-1", | |||
"key": "82fd47d4-b650-4037-80bc-7b1182fd47d4", | |||
"rule": "xoo:x1", | |||
"component": "FK1", | |||
"project": "PK1", | |||
"author": "luke@skywalker.name" | |||
}, | |||
{ | |||
"organization": "org-1", | |||
"key": "2bd4eac2-b650-4037-80bc-7b112bd4eac2", | |||
"rule": "xoo:x1", | |||
"component": "FK1", | |||
"project": "PK1", | |||
"author": "leia" | |||
} | |||
], | |||
"facets": [ | |||
{ | |||
"property": "authors", | |||
"values": [ | |||
{ | |||
"val": "leia", | |||
"count": 1 | |||
}, | |||
{ | |||
"val": "luke@skywalker.name", | |||
"count": 1 | |||
} | |||
] | |||
} | |||
] | |||
} |
@@ -0,0 +1,11 @@ | |||
{ | |||
"sources": [ | |||
{ | |||
"line": 1, | |||
"code": "\u003cp\u003eSOURCE_1\u003c/p\u003e", | |||
"scmRevision": "REVISION_1", | |||
"scmDate": "1974-10-03T03:40:00+0100", | |||
"duplicated": false | |||
} | |||
] | |||
} |
@@ -0,0 +1,12 @@ | |||
{ | |||
"sources": [ | |||
{ | |||
"line": 1, | |||
"code": "\u003cp\u003eSOURCE_1\u003c/p\u003e", | |||
"scmAuthor": "isaac@asimov.com", | |||
"scmRevision": "REVISION_1", | |||
"scmDate": "1974-10-03T03:40:00+0100", | |||
"duplicated": false | |||
} | |||
] | |||
} |
@@ -26,5 +26,5 @@ interface Props { | |||
} | |||
export default function ExploreIssues(props: Props) { | |||
return <AppContainer myIssues={false} {...props} />; | |||
return <AppContainer hideAuthorFacet={true} myIssues={false} {...props} />; | |||
} |
@@ -52,7 +52,14 @@ import { | |||
serializeQuery, | |||
STANDARDS | |||
} from '../utils'; | |||
import { Component, CurrentUser, Issue, Paging, BranchLike } from '../../../app/types'; | |||
import { | |||
Component, | |||
CurrentUser, | |||
Issue, | |||
Paging, | |||
BranchLike, | |||
Organization | |||
} from '../../../app/types'; | |||
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; | |||
import Dropdown from '../../../components/controls/Dropdown'; | |||
import ListFooter from '../../../components/controls/ListFooter'; | |||
@@ -92,10 +99,12 @@ interface Props { | |||
component?: Component; | |||
currentUser: CurrentUser; | |||
fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<FetchIssuesPromise>; | |||
hideAuthorFacet?: boolean; | |||
location: { pathname: string; query: RawQuery }; | |||
myIssues?: boolean; | |||
onBranchesChange: () => void; | |||
organization?: { key: string }; | |||
userOrganizations: Organization[]; | |||
} | |||
export interface State { | |||
@@ -407,7 +416,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
requestFacets = false, | |||
requestOrganizations = true | |||
): Promise<FetchIssuesPromise> => { | |||
const { component, organization } = this.props; | |||
const { component } = this.props; | |||
const { myIssues, openFacets, query } = this.state; | |||
const facets = requestFacets | |||
@@ -418,13 +427,17 @@ export default class App extends React.PureComponent<Props, State> { | |||
.join(',') | |||
: undefined; | |||
const organizationKey = | |||
(component && component.organization) || | |||
(this.props.organization && this.props.organization.key); | |||
const parameters = { | |||
...getBranchLikeQuery(this.props.branchLike), | |||
componentKeys: component && component.key, | |||
s: 'FILE_LINE', | |||
...serializeQuery(query), | |||
ps: '100', | |||
organization: organization && organization.key, | |||
organization: organizationKey, | |||
facets, | |||
...additional | |||
}; | |||
@@ -860,9 +873,21 @@ export default class App extends React.PureComponent<Props, State> { | |||
} | |||
renderFacets() { | |||
const { component, currentUser } = this.props; | |||
const { component, currentUser, userOrganizations } = this.props; | |||
const { query } = this.state; | |||
const organizationKey = | |||
(component && component.organization) || | |||
(this.props.organization && this.props.organization.key); | |||
const userOrganization = | |||
!isSonarCloud() || | |||
userOrganizations.find(o => { | |||
return o.key === organizationKey; | |||
}); | |||
const hideAuthorFacet = | |||
this.props.hideAuthorFacet || (isSonarCloud() && this.props.myIssues) || !userOrganization; | |||
return ( | |||
<div className="layout-page-filters"> | |||
{currentUser.isLoggedIn && | |||
@@ -876,6 +901,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
<Sidebar | |||
component={component} | |||
facets={this.state.facets} | |||
hideAuthorFacet={hideAuthorFacet} | |||
loading={this.state.loading} | |||
loadingFacets={this.state.loadingFacets} | |||
myIssues={this.state.myIssues} |
@@ -22,9 +22,13 @@ import { Dispatch } from 'redux'; | |||
import { uniq } from 'lodash'; | |||
import { searchIssues } from '../../../api/issues'; | |||
import { getOrganizations } from '../../../api/organizations'; | |||
import { CurrentUser } from '../../../app/types'; | |||
import { CurrentUser, Organization } from '../../../app/types'; | |||
import throwGlobalError from '../../../app/utils/throwGlobalError'; | |||
import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; | |||
import { | |||
getCurrentUser, | |||
areThereCustomOrganizations, | |||
getMyOrganizations | |||
} from '../../../store/rootReducer'; | |||
import { lazyLoad } from '../../../components/lazyLoad'; | |||
import { parseIssueFromResponse } from '../../../helpers/issues'; | |||
import { RawQuery } from '../../../helpers/query'; | |||
@@ -32,10 +36,12 @@ import { receiveOrganizations } from '../../../store/organizations/duck'; | |||
interface StateProps { | |||
currentUser: CurrentUser; | |||
userOrganizations: Organization[]; | |||
} | |||
const mapStateToProps = (state: any): StateProps => ({ | |||
currentUser: getCurrentUser(state) | |||
currentUser: getCurrentUser(state), | |||
userOrganizations: getMyOrganizations(state) | |||
}); | |||
const fetchIssueOrganizations = (organizationKeys: string[]) => (dispatch: Dispatch<any>) => { | |||
@@ -83,6 +89,7 @@ const mapDispatchToProps = { fetchIssues: fetchIssues as any } as DispatchProps; | |||
interface OwnProps { | |||
location: { pathname: string; query: RawQuery }; | |||
hideAuthorFacet?: boolean; | |||
myIssues?: boolean; | |||
} | |||
@@ -46,6 +46,7 @@ import { Component } from '../../../app/types'; | |||
export interface Props { | |||
component: Component | undefined; | |||
facets: { [facet: string]: Facet }; | |||
hideAuthorFacet?: boolean; | |||
loading?: boolean; | |||
loadingFacets: { [key: string]: boolean }; | |||
myIssues: boolean; | |||
@@ -62,14 +63,14 @@ export interface Props { | |||
export default class Sidebar extends React.PureComponent<Props> { | |||
render() { | |||
const { component, facets, openFacets, query } = this.props; | |||
const { component, facets, hideAuthorFacet, openFacets, query } = this.props; | |||
const displayProjectsFacet = | |||
!component || !['TRK', 'BRC', 'DIR', 'DEV_PRJ'].includes(component.qualifier); | |||
const displayModulesFacet = component !== undefined && component.qualifier !== 'DIR'; | |||
const displayDirectoriesFacet = component !== undefined && component.qualifier !== 'DIR'; | |||
const displayFilesFacet = component !== undefined; | |||
const displayAuthorFacet = !component || component.qualifier !== 'DEV'; | |||
const displayAuthorFacet = !hideAuthorFacet && (!component || component.qualifier !== 'DEV'); | |||
const organizationKey = | |||
(component && component.organization) || |
@@ -158,6 +158,10 @@ export function mapFacet(facet: string) { | |||
} | |||
export function parseFacets(facets: RawFacet[]) { | |||
if (!facets) { | |||
return {}; | |||
} | |||
// for readability purpose | |||
const propertyMapping: { [x: string]: string } = { | |||
fileUuids: 'files', |
@@ -49,7 +49,7 @@ export default class LineSCM extends React.PureComponent<Props> { | |||
const { line, popupOpen, previousLine } = this.props; | |||
const hasPopup = !!line.line; | |||
const cell = isSCMChanged(line, previousLine) && ( | |||
<div className="source-line-scm-inner" data-author={line.scmAuthor} /> | |||
<div className="source-line-scm-inner" data-author={line.scmAuthor || '…'} /> | |||
); | |||
return hasPopup ? ( | |||
<td | |||
@@ -76,8 +76,8 @@ export default class LineSCM extends React.PureComponent<Props> { | |||
function isSCMChanged(s: SourceLine, p: SourceLine | undefined) { | |||
let changed = true; | |||
if (p != null && s.scmAuthor != null && p.scmAuthor != null) { | |||
changed = s.scmAuthor !== p.scmAuthor || s.scmDate !== p.scmDate; | |||
if (p != null && s.scmRevision != null && p.scmRevision != null) { | |||
changed = s.scmRevision !== p.scmRevision || s.scmDate !== p.scmDate; | |||
} | |||
return changed; | |||
} |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import { SourceLine } from '../../../app/types'; | |||
import { DropdownOverlay } from '../../controls/Dropdown'; | |||
import DateFormatter from '../../intl/DateFormatter'; | |||
@@ -28,12 +29,13 @@ interface Props { | |||
} | |||
export default function SCMPopup({ line }: Props) { | |||
const hasAuthor = line.scmAuthor !== ''; | |||
return ( | |||
<DropdownOverlay placement={PopupPlacement.RightTop}> | |||
<div className="source-viewer-bubble-popup abs-width-400"> | |||
<div>{line.scmAuthor}</div> | |||
{hasAuthor && <div>{line.scmAuthor}</div>} | |||
{line.scmDate && ( | |||
<div className="spacer-top"> | |||
<div className={classNames({ 'spacer-top': hasAuthor })}> | |||
<DateFormatter date={line.scmDate} /> | |||
</div> | |||
)} |
@@ -32,7 +32,7 @@ it('render scm details', () => { | |||
}); | |||
it('render scm details for the first line', () => { | |||
const line = { line: 3, scmAuthor: 'foo', scmDate: '2017-01-01' }; | |||
const line = { line: 3, scmRevision: 'foo', scmAuthor: 'foo', scmDate: '2017-01-01' }; | |||
const wrapper = shallow( | |||
<LineSCM line={line} onPopupToggle={jest.fn()} popupOpen={false} previousLine={undefined} /> | |||
); | |||
@@ -40,8 +40,17 @@ it('render scm details for the first line', () => { | |||
}); | |||
it('does not render scm details', () => { | |||
const line = { line: 3, scmAuthor: 'foo', scmDate: '2017-01-01' }; | |||
const previousLine = { line: 2, scmAuthor: 'foo', scmDate: '2017-01-01' }; | |||
const line = { line: 3, scmRevision: 'foo', scmAuthor: 'foo', scmDate: '2017-01-01' }; | |||
const previousLine = { line: 2, scmRevision: 'foo', scmAuthor: 'foo', scmDate: '2017-01-01' }; | |||
const wrapper = shallow( | |||
<LineSCM line={line} onPopupToggle={jest.fn()} popupOpen={false} previousLine={previousLine} /> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('renders ellipsis when no author info', () => { | |||
const line = { line: 3, scmRevision: 'foo', scmDate: '2017-01-01' }; | |||
const previousLine = { line: 2, scmRevision: 'bar', scmDate: '2017-01-01' }; | |||
const wrapper = shallow( | |||
<LineSCM line={line} onPopupToggle={jest.fn()} popupOpen={false} previousLine={previousLine} /> | |||
); |
@@ -18,6 +18,7 @@ exports[`does not render scm details 1`] = ` | |||
"line": 3, | |||
"scmAuthor": "foo", | |||
"scmDate": "2017-01-01", | |||
"scmRevision": "foo", | |||
} | |||
} | |||
/> | |||
@@ -75,6 +76,7 @@ exports[`render scm details for the first line 1`] = ` | |||
"line": 3, | |||
"scmAuthor": "foo", | |||
"scmDate": "2017-01-01", | |||
"scmRevision": "foo", | |||
} | |||
} | |||
/> | |||
@@ -87,3 +89,34 @@ exports[`render scm details for the first line 1`] = ` | |||
</Toggler> | |||
</td> | |||
`; | |||
exports[`renders ellipsis when no author info 1`] = ` | |||
<td | |||
className="source-meta source-line-scm" | |||
data-line-number={3} | |||
onClick={[Function]} | |||
role="button" | |||
tabIndex={0} | |||
> | |||
<Toggler | |||
onRequestClose={[Function]} | |||
open={false} | |||
overlay={ | |||
<SCMPopup | |||
line={ | |||
Object { | |||
"line": 3, | |||
"scmDate": "2017-01-01", | |||
"scmRevision": "foo", | |||
} | |||
} | |||
/> | |||
} | |||
> | |||
<div | |||
className="source-line-scm-inner" | |||
data-author="…" | |||
/> | |||
</Toggler> | |||
</td> | |||
`; |