import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.db.DbSession;
-import org.sonar.db.MyBatis;
import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.es.SearchResult;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;
+import static com.google.common.base.Objects.firstNonNull;
import static org.sonar.server.es.SearchOptions.MAX_LIMIT;
public class SearchAction implements UsersWsAction {
.setDescription("Get a list of active users. Administer System permission is required to show the 'groups' field.")
.setSince("3.6")
.setHandler(this)
- .setResponseExample(getClass().getResource("example-search.json"));
+ .setResponseExample(getClass().getResource("search-example.json"));
action.addFieldsParam(UserJsonWriter.FIELDS);
action.addPagingParams(50, MAX_LIMIT);
SearchResult<UserDoc> result = userIndex.search(request.param(Param.TEXT_QUERY), options);
Multimap<String, String> groupsByLogin = ArrayListMultimap.create();
+ Map<String, Integer> tokenCountsByLogin = new HashMap<>();
DbSession dbSession = dbClient.openSession(false);
try {
- Collection<String> logins = Collections2.transform(result.getDocs(), new Function<UserDoc, String>() {
+ List<String> logins = Lists.transform(result.getDocs(), new Function<UserDoc, String>() {
@Override
public String apply(@Nonnull UserDoc input) {
return input.login();
}
});
groupsByLogin = dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, logins);
+ tokenCountsByLogin = dbClient.userTokenDao().countTokensByLogins(dbSession, logins);
} finally {
- MyBatis.closeQuietly(dbSession);
+ dbClient.closeSession(dbSession);
}
JsonWriter json = response.newJsonWriter().beginObject();
options.writeJson(json, result.getTotal());
- writeUsers(json, result, fields, groupsByLogin);
+ writeUsers(json, result, groupsByLogin, tokenCountsByLogin, fields);
json.endObject().close();
}
- private void writeUsers(JsonWriter json, SearchResult<UserDoc> result, @Nullable List<String> fields, Multimap<String, String> groupsByLogin) {
+ private void writeUsers(JsonWriter json, SearchResult<UserDoc> result, Multimap<String, String> groupsByLogin, Map<String, Integer> tokenCountsByLogin,
+ @Nullable List<String> fields) {
json.name("users").beginArray();
for (UserDoc user : result.getDocs()) {
Collection<String> groups = groupsByLogin.get(user.login());
- userWriter.write(json, user, groups, fields);
+ userWriter.write(json, user, firstNonNull(tokenCountsByLogin.get(user.login()), 0), groups, fields);
}
json.endArray();
}
private static final String FIELD_SCM_ACCOUNTS = "scmAccounts";
private static final String FIELD_GROUPS = "groups";
private static final String FIELD_ACTIVE = "active";
+ private static final String FIELD_TOKEN_COUNT = "tokenCount";
public static final Set<String> FIELDS = ImmutableSet.of(FIELD_NAME, FIELD_EMAIL, FIELD_SCM_ACCOUNTS, FIELD_GROUPS, FIELD_ACTIVE);
private static final Set<String> CONCISE_FIELDS = ImmutableSet.of(FIELD_NAME, FIELD_EMAIL, FIELD_ACTIVE);
/**
* Serializes a user to the passed JsonWriter.
*/
- public void write(JsonWriter json, User user, Collection<String> groups, Collection<String> fields) {
+ public void write(JsonWriter json, User user, Collection<String> groups, @Nullable Collection<String> fields) {
+ write(json, user, null, groups, fields);
+ }
+
+ /**
+ * Serializes a user to the passed JsonWriter.
+ */
+ public void write(JsonWriter json, User user, @Nullable Integer tokenCount, Collection<String> groups, @Nullable Collection<String> fields) {
json.beginObject();
json.prop(FIELD_LOGIN, user.login());
writeIfNeeded(json, user.name(), FIELD_NAME, fields);
writeIfNeeded(json, user.active(), FIELD_ACTIVE, fields);
writeGroupsIfNeeded(json, groups, fields);
writeScmAccountsIfNeeded(json, fields, user);
+ writeTokenCount(json, tokenCount);
json.endObject();
}
.endArray();
}
}
+
+ private static void writeTokenCount(JsonWriter json, @Nullable Integer tokenCount) {
+ if (tokenCount == null) {
+ return;
+ }
+
+ json.prop(FIELD_TOKEN_COUNT, tokenCount);
+ }
}
+++ /dev/null
-{
- "users": [
- {
- "login": "fmallet",
- "name": "Freddy Mallet",
- "active": true,
- "email": "f@m.com",
- "scmAccounts": [],
- "groups": ["sonar-users", "sonar-administrators"]
- },
- {
- "login": "sbrandhof",
- "name": "Simon",
- "active": true,
- "email": "s.brandhof@company.tld",
- "scmAccounts": ["simon.brandhof", "s.brandhof@company.tld"],
- "groups": ["sonar-users"]
- }
- ]
-}
--- /dev/null
+{
+ "users": [
+ {
+ "login": "fmallet",
+ "name": "Freddy Mallet",
+ "active": true,
+ "email": "f@m.com",
+ "scmAccounts": [],
+ "groups": [
+ "sonar-users",
+ "sonar-administrators"
+ ],
+ "tokenCount": 1
+ },
+ {
+ "login": "sbrandhof",
+ "name": "Simon",
+ "active": true,
+ "email": "s.brandhof@company.tld",
+ "scmAccounts": [
+ "simon.brandhof",
+ "s.brandhof@company.tld"
+ ],
+ "groups": [
+ "sonar-users"
+ ],
+ "tokenCount": 3
+ }
+ ]
+}
package org.sonar.server.user.ws;
import com.google.common.collect.Lists;
-import java.util.Arrays;
import java.util.List;
-import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.sonar.api.config.Settings;
-import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.System2;
import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.GroupMembershipDao;
import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserGroupDao;
import org.sonar.db.user.UserGroupDto;
-import org.sonar.server.db.DbClient;
import org.sonar.server.es.EsTester;
import org.sonar.server.tester.UserSessionRule;
-import org.sonar.db.user.GroupDao;
-import org.sonar.db.user.UserDao;
import org.sonar.server.user.index.UserDoc;
import org.sonar.server.user.index.UserIndex;
import org.sonar.server.user.index.UserIndexDefinition;
import org.sonar.server.ws.WsTester;
import org.sonar.test.DbTests;
+import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.user.UserTokenTesting.newUserToken;
@Category(DbTests.class)
public class SearchActionTest {
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
@ClassRule
public static final EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
-
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+ DbClient dbClient = db.getDbClient();
+ DbSession dbSession = db.getSession();
- WebService.Controller controller;
-
- WsTester tester;
-
+ WsTester ws;
UserIndex index;
- DbClient dbClient;
-
- DbSession session;
-
@Before
public void setUp() {
- dbTester.truncateTables();
esTester.truncateIndices();
-
- dbClient = new DbClient(dbTester.database(), dbTester.myBatis(),
- new GroupMembershipDao(dbTester.myBatis()),
- new UserDao(dbTester.myBatis(), new System2()),
- new GroupDao(new System2()),
- new UserGroupDao());
- session = dbClient.openSession(false);
-
index = new UserIndex(esTester.client());
- tester = new WsTester(new UsersWs(new SearchAction(index, dbClient, new UserJsonWriter(userSession))));
- controller = tester.controller("api/users");
- }
-
- @After
- public void tearDown() {
- session.close();
+ ws = new WsTester(new UsersWs(new SearchAction(index, dbClient, new UserJsonWriter(userSession))));
}
@Test
public void search_empty() throws Exception {
- tester.newGetRequest("api/users", "search").execute().assertJson(getClass(), "empty.json");
+ ws.newGetRequest("api/users", "search").execute().assertJson(getClass(), "empty.json");
}
@Test
public void search_without_parameters() throws Exception {
injectUsers(5);
- tester.newGetRequest("api/users", "search").execute().assertJson(getClass(), "five_users.json");
+ ws.newGetRequest("api/users", "search").execute().assertJson(getClass(), "five_users.json");
}
@Test
public void search_with_query() throws Exception {
injectUsers(5);
- tester.newGetRequest("api/users", "search").setParam("q", "user-1").execute().assertJson(getClass(), "user_one.json");
+ ws.newGetRequest("api/users", "search").setParam("q", "user-1").execute().assertJson(getClass(), "user_one.json");
}
@Test
public void search_with_paging() throws Exception {
injectUsers(10);
- tester.newGetRequest("api/users", "search").setParam(Param.PAGE_SIZE, "5").execute().assertJson(getClass(), "page_one.json");
- tester.newGetRequest("api/users", "search").setParam(Param.PAGE_SIZE, "5").setParam(Param.PAGE, "2").execute().assertJson(getClass(), "page_two.json");
+ ws.newGetRequest("api/users", "search").setParam(Param.PAGE_SIZE, "5").execute().assertJson(getClass(), "page_one.json");
+ ws.newGetRequest("api/users", "search").setParam(Param.PAGE_SIZE, "5").setParam(Param.PAGE, "2").execute().assertJson(getClass(), "page_two.json");
}
@Test
public void search_with_fields() throws Exception {
injectUsers(1);
- assertThat(tester.newGetRequest("api/users", "search").execute().outputAsString())
+ assertThat(ws.newGetRequest("api/users", "search").execute().outputAsString())
.contains("login")
.contains("name")
.contains("email")
.contains("scmAccounts")
.doesNotContain("groups");
- assertThat(tester.newGetRequest("api/users", "search").setParam(Param.FIELDS, "").execute().outputAsString())
+ assertThat(ws.newGetRequest("api/users", "search").setParam(Param.FIELDS, "").execute().outputAsString())
.contains("login")
.contains("name")
.contains("email")
.contains("scmAccounts")
.doesNotContain("groups");
- assertThat(tester.newGetRequest("api/users", "search").setParam(Param.FIELDS, "scmAccounts").execute().outputAsString())
+ assertThat(ws.newGetRequest("api/users", "search").setParam(Param.FIELDS, "scmAccounts").execute().outputAsString())
.contains("login")
.doesNotContain("name")
.doesNotContain("email")
.contains("scmAccounts")
.doesNotContain("groups");
- assertThat(tester.newGetRequest("api/users", "search").setParam(Param.FIELDS, "groups").execute().outputAsString())
+ assertThat(ws.newGetRequest("api/users", "search").setParam(Param.FIELDS, "groups").execute().outputAsString())
.contains("login")
.doesNotContain("name")
.doesNotContain("email")
loginAsAdmin();
- assertThat(tester.newGetRequest("api/users", "search").execute().outputAsString())
+ assertThat(ws.newGetRequest("api/users", "search").execute().outputAsString())
.contains("login")
.contains("name")
.contains("email")
.contains("scmAccounts")
.contains("groups");
- assertThat(tester.newGetRequest("api/users", "search").setParam(Param.FIELDS, "groups").execute().outputAsString())
+ assertThat(ws.newGetRequest("api/users", "search").setParam(Param.FIELDS, "groups").execute().outputAsString())
.contains("login")
.doesNotContain("name")
.doesNotContain("email")
public void search_with_groups() throws Exception {
List<UserDto> users = injectUsers(1);
- GroupDto group1 = dbClient.groupDao().insert(session, new GroupDto().setName("sonar-users"));
- GroupDto group2 = dbClient.groupDao().insert(session, new GroupDto().setName("sonar-admins"));
- dbClient.userGroupDao().insert(session, new UserGroupDto().setGroupId(group1.getId()).setUserId(users.get(0).getId()));
- dbClient.userGroupDao().insert(session, new UserGroupDto().setGroupId(group2.getId()).setUserId(users.get(0).getId()));
- session.commit();
+ GroupDto group1 = dbClient.groupDao().insert(dbSession, new GroupDto().setName("sonar-users"));
+ GroupDto group2 = dbClient.groupDao().insert(dbSession, new GroupDto().setName("sonar-admins"));
+ dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(group1.getId()).setUserId(users.get(0).getId()));
+ dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(group2.getId()).setUserId(users.get(0).getId()));
+ dbSession.commit();
loginAsAdmin();
- tester.newGetRequest("api/users", "search").execute().assertJson(getClass(), "user_with_groups.json");
+ ws.newGetRequest("api/users", "search").execute().assertJson(getClass(), "user_with_groups.json");
}
private List<UserDto> injectUsers(int numberOfUsers) throws Exception {
String email = String.format("user-%d@mail.com", index);
String login = String.format("user-%d", index);
String name = String.format("User %d", index);
- List<String> scmAccounts = Arrays.asList(String.format("user-%d", index));
+ List<String> scmAccounts = singletonList(String.format("user-%d", index));
- userDtos.add(dbClient.userDao().insert(session, new UserDto()
+ userDtos.add(dbClient.userDao().insert(dbSession, new UserDto()
.setActive(true)
.setCreatedAt(createdAt)
.setEmail(email)
.setName(name)
.setScmAccounts(scmAccounts)
.setUpdatedAt(createdAt);
+
+ for (int tokenIndex = 0; tokenIndex < index; tokenIndex++) {
+ dbClient.userTokenDao().insert(dbSession, newUserToken()
+ .setLogin(login)
+ .setName(String.format("%s-%d", login, tokenIndex)));
+ }
}
- session.commit();
+ dbSession.commit();
esTester.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, users);
return userDtos;
}
"email": "user-0@mail.com",
"scmAccounts": [
"user-0"
- ]
+ ],
+ "tokenCount": 0
},
{
"login": "user-1",
"email": "user-1@mail.com",
"scmAccounts": [
"user-1"
- ]
+ ],
+ "tokenCount": 1
},
{
"login": "user-2",
"email": "user-2@mail.com",
"scmAccounts": [
"user-2"
- ]
+ ],
+ "tokenCount": 2
},
{
"login": "user-3",
"email": "user-3@mail.com",
"scmAccounts": [
"user-3"
- ]
+ ],
+ "tokenCount": 3
},
{
"login": "user-4",
"email": "user-4@mail.com",
"scmAccounts": [
"user-4"
- ]
+ ],
+ "tokenCount": 4
}
]
}
"email": "user-1@mail.com",
"scmAccounts": [
"user-1"
- ]
+ ],
+ "tokenCount": 1
}
]
}
import org.sonar.db.user.UserGroupMapper;
import org.sonar.db.user.UserMapper;
import org.sonar.db.user.UserRoleDto;
+import org.sonar.db.user.UserTokenCount;
import org.sonar.db.user.UserTokenDto;
import org.sonar.db.user.UserTokenMapper;
import org.sonar.db.version.SchemaMigrationDto;
confBuilder.loadAlias("CustomMeasure", CustomMeasureDto.class);
confBuilder.loadAlias("ViewsSnapshot", ViewsSnapshotDto.class);
confBuilder.loadAlias("UserToken", UserTokenDto.class);
+ confBuilder.loadAlias("UserTokenCount", UserTokenCount.class);
// AuthorizationMapper has to be loaded before IssueMapper because this last one used it
confBuilder.loadMapper("org.sonar.db.user.AuthorizationMapper");
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.user;
+
+public class UserTokenCount {
+ private String login;
+ private Integer tokenCount;
+
+ public String getLogin() {
+ return login;
+ }
+
+ public UserTokenCount setLogin(String login) {
+ this.login = login;
+ return this;
+ }
+
+ public Integer tokenCount() {
+ return tokenCount;
+ }
+
+ public UserTokenCount setTokenCount(Integer tokenCount) {
+ this.tokenCount = tokenCount;
+ return this;
+ }
+}
*/
package org.sonar.db.user;
+import com.google.common.base.Function;
import com.google.common.base.Optional;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import javax.annotation.Nonnull;
import org.sonar.db.Dao;
+import org.sonar.db.DatabaseUtils;
import org.sonar.db.DbSession;
import org.sonar.db.RowNotFoundException;
return mapper(dbSession).selectByLogin(login);
}
+ public Map<String, Integer> countTokensByLogins(final DbSession dbSession, List<String> logins) {
+ final Map<String, Integer> result = new HashMap<>();
+ DatabaseUtils.executeLargeInputs(logins, new Function<List<String>, List<UserTokenCount>>() {
+ @Override
+ public List<UserTokenCount> apply(@Nonnull List<String> input) {
+ List<UserTokenCount> userTokenCounts = mapper(dbSession).countTokensByLogins(input);
+ for (UserTokenCount userTokenCount : userTokenCounts) {
+ result.put(userTokenCount.getLogin(), userTokenCount.tokenCount());
+ }
+ return userTokenCounts;
+ }
+ });
+
+ return result;
+ }
+
public void deleteByLogin(DbSession dbSession, String login) {
mapper(dbSession).deleteByLogin(login);
}
void deleteByLogin(String login);
void deleteByLoginAndName(@Param("login") String login, @Param("name") String name);
+
+ List<UserTokenCount> countTokensByLogins(@Param("logins") List<String> logins);
}
FROM user_tokens t
WHERE t.login=#{login}
</select>
+
+ <select id="countTokensByLogins" parameterType="map" resultType="UserTokenCount">
+ SELECT t.login as "login", count(t.name) as "tokenCount"
+ FROM user_tokens t
+ WHERE t.login in
+ <foreach collection="logins" open="(" close=")" item="login" separator=",">
+ #{login}
+ </foreach>
+ GROUP BY t.login
+ </select>
<delete id="deleteByLogin">
DELETE FROM user_tokens WHERE login=#{login}
package org.sonar.db.user;
import com.google.common.base.Optional;
+import java.util.Map;
import org.assertj.guava.api.Assertions;
import org.junit.Before;
import org.junit.Rule;
import org.sonar.db.RowNotFoundException;
import org.sonar.test.DbTests;
+import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.db.user.UserTokenTesting.newUserToken;
Assertions.assertThat(underTest.selectByLoginAndName(dbSession, "another-login", "name")).isPresent();
}
+ @Test
+ public void count_tokens_by_login() {
+ insertToken(newUserToken().setLogin("login").setName("name"));
+ insertToken(newUserToken().setLogin("login").setName("another-name"));
+
+ Map<String, Integer> result = underTest.countTokensByLogins(dbSession, newArrayList("login"));
+
+ assertThat(result.get("login")).isEqualTo(2);
+ assertThat(result.get("unknown-login")).isNull();
+ }
+
private void insertToken(UserTokenDto userToken) {
underTest.insert(dbSession, userToken);
dbSession.commit();