summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2017-10-27 17:05:43 +0200
committerStas Vilchik <stas.vilchik@sonarsource.com>2017-10-30 09:20:37 +0100
commit6daddd1d06a834867faccbbb6a0e60f448f2c6d4 (patch)
tree4b077a11038d5b2607e13200971961c0ec08710b
parent771c15c18356cfe02bad535bdd08ffb1c148d016 (diff)
downloadsonarqube-6daddd1d06a834867faccbbb6a0e60f448f2c6d4.tar.gz
sonarqube-6daddd1d06a834867faccbbb6a0e60f448f2c6d4.zip
SONAR-10031 Stop computing avatar hash in web app (#2769)
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/TemplateUsersAction.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java6
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/permission/ws/template/template_users-example.json11
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json13
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/user/ws/current-example.json15
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/permission/ws/template/TemplateUsersActionTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java11
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java2
-rw-r--r--server/sonar-web/package.json1
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js3
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.js2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/account/components/UserCard.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js3
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js2
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchValue.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchValue-test.js.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/users/templates/users-list-item.hbs2
-rw-r--r--server/sonar-web/src/main/js/components/issue/popups/SetAssigneePopup.js8
-rw-r--r--server/sonar-web/src/main/js/components/ui/Avatar.tsx (renamed from server/sonar-web/src/main/js/components/ui/Avatar.js)83
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.tsx (renamed from server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js)15
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.tsx.snap (renamed from server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.js.snap)11
-rw-r--r--server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js4
-rw-r--r--server/sonar-web/yarn.lock4
-rw-r--r--sonar-ws/src/main/protobuf/ws-permissions.proto1
-rw-r--r--sonar-ws/src/main/protobuf/ws-users.proto1
32 files changed, 104 insertions, 139 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java
index 8c6f146ca52..f7538691c96 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/UsersAction.java
@@ -36,11 +36,13 @@ import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.permission.PermissionQuery;
import org.sonar.db.permission.UserPermissionDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.ws.AvatarResolver;
import org.sonar.server.permission.ProjectId;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.WsPermissions;
import org.sonarqube.ws.WsPermissions.UsersWsResponse;
+import static com.google.common.base.Strings.emptyToNull;
import static java.util.Collections.emptyList;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.db.permission.PermissionQuery.DEFAULT_PAGE_SIZE;
@@ -62,11 +64,13 @@ public class UsersAction implements PermissionsWsAction {
private final DbClient dbClient;
private final UserSession userSession;
private final PermissionWsSupport support;
+ private final AvatarResolver avatarResolver;
- public UsersAction(DbClient dbClient, UserSession userSession, PermissionWsSupport support) {
+ public UsersAction(DbClient dbClient, UserSession userSession, PermissionWsSupport support, AvatarResolver avatarResolver) {
this.dbClient = dbClient;
this.userSession = userSession;
this.support = support;
+ this.avatarResolver = avatarResolver;
}
@Override
@@ -139,7 +143,7 @@ public class UsersAction implements PermissionsWsAction {
return permissionQuery.build();
}
- private static UsersWsResponse buildResponse(List<UserDto> users, List<UserPermissionDto> userPermissions, Paging paging) {
+ private UsersWsResponse buildResponse(List<UserDto> users, List<UserPermissionDto> userPermissions, Paging paging) {
Multimap<Integer, String> permissionsByUserId = TreeMultimap.create();
userPermissions.forEach(userPermission -> permissionsByUserId.put(userPermission.getUserId(), userPermission.getPermission()));
@@ -149,6 +153,7 @@ public class UsersAction implements PermissionsWsAction {
.setLogin(user.getLogin())
.addAllPermissions(permissionsByUserId.get(user.getId()));
setNullable(user.getEmail(), userResponse::setEmail);
+ setNullable(emptyToNull(user.getEmail()), u -> userResponse.setAvatar(avatarResolver.create(user)));
setNullable(user.getName(), userResponse::setName);
});
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/TemplateUsersAction.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/TemplateUsersAction.java
index aadaaae16b2..0c37f59f1c0 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/TemplateUsersAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/permission/ws/template/TemplateUsersAction.java
@@ -35,12 +35,14 @@ import org.sonar.db.permission.PermissionQuery;
import org.sonar.db.permission.template.PermissionTemplateDto;
import org.sonar.db.permission.template.PermissionTemplateUserDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.ws.AvatarResolver;
import org.sonar.server.permission.ws.PermissionWsSupport;
import org.sonar.server.permission.ws.PermissionsWsAction;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.WsPermissions;
import org.sonarqube.ws.WsPermissions.UsersWsResponse;
+import static com.google.common.base.Strings.emptyToNull;
import static org.sonar.api.server.ws.WebService.Param.PAGE;
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
@@ -60,11 +62,13 @@ public class TemplateUsersAction implements PermissionsWsAction {
private final DbClient dbClient;
private final UserSession userSession;
private final PermissionWsSupport support;
+ private final AvatarResolver avatarResolver;
- public TemplateUsersAction(DbClient dbClient, UserSession userSession, PermissionWsSupport support) {
+ public TemplateUsersAction(DbClient dbClient, UserSession userSession, PermissionWsSupport support, AvatarResolver avatarResolver) {
this.dbClient = dbClient;
this.userSession = userSession;
this.support = support;
+ this.avatarResolver = avatarResolver;
}
@Override
@@ -122,7 +126,7 @@ public class TemplateUsersAction implements PermissionsWsAction {
return query.build();
}
- private static WsPermissions.UsersWsResponse buildResponse(List<UserDto> users, List<PermissionTemplateUserDto> permissionTemplateUsers, Paging paging) {
+ private WsPermissions.UsersWsResponse buildResponse(List<UserDto> users, List<PermissionTemplateUserDto> permissionTemplateUsers, Paging paging) {
Multimap<Integer, String> permissionsByUserId = TreeMultimap.create();
permissionTemplateUsers.forEach(userPermission -> permissionsByUserId.put(userPermission.getUserId(), userPermission.getPermission()));
@@ -133,6 +137,7 @@ public class TemplateUsersAction implements PermissionsWsAction {
.addAllPermissions(permissionsByUserId.get(user.getId()));
setNullable(user.getEmail(), userResponse::setEmail);
setNullable(user.getName(), userResponse::setName);
+ setNullable(emptyToNull(user.getEmail()), u -> userResponse.setAvatar(avatarResolver.create(user)));
});
responseBuilder.getPagingBuilder()
.setPageIndex(paging.pageIndex())
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java
index 2529b920e63..9ddfdc9190b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java
@@ -30,6 +30,7 @@ import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.permission.OrganizationPermission;
import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.ws.AvatarResolver;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.WsUsers.CurrentWsResponse;
@@ -47,11 +48,13 @@ public class CurrentAction implements UsersWsAction {
private final UserSession userSession;
private final DbClient dbClient;
private final DefaultOrganizationProvider defaultOrganizationProvider;
+ private final AvatarResolver avatarResolver;
- public CurrentAction(UserSession userSession, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider) {
+ public CurrentAction(UserSession userSession, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, AvatarResolver avatarResolver) {
this.userSession = userSession;
this.dbClient = dbClient;
this.defaultOrganizationProvider = defaultOrganizationProvider;
+ this.avatarResolver = avatarResolver;
}
@Override
@@ -95,6 +98,7 @@ public class CurrentAction implements UsersWsAction {
.setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build())
.setShowOnboardingTutorial(!user.isOnboarded());
setNullable(emptyToNull(user.getEmail()), builder::setEmail);
+ setNullable(emptyToNull(user.getEmail()), u -> builder.setAvatar(avatarResolver.create(user)));
setNullable(user.getExternalIdentity(), builder::setExternalIdentity);
setNullable(user.getExternalIdentityProvider(), builder::setExternalProvider);
return builder.build();
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/template/template_users-example.json b/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/template/template_users-example.json
index 2e21d9a21f8..f33861b4444 100644
--- a/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/template/template_users-example.json
+++ b/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/template/template_users-example.json
@@ -9,18 +9,15 @@
"login": "admin",
"name": "Administrator",
"email": "admin@admin.com",
- "permissions": [
- "codeviewer"
- ]
+ "avatar": "64e1b8d34f425d19e1ee2ea7236d3028",
+ "permissions": ["codeviewer"]
},
{
"login": "george.orwell",
"name": "George Orwell",
"email": "george.orwell@1984.net",
- "permissions": [
- "admin",
- "codeviewer"
- ]
+ "avatar": "583af86a274c1027ef078cada831babf",
+ "permissions": ["admin", "codeviewer"]
}
]
}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json b/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json
index eef7bf0f1fe..1f3e6ae34cb 100644
--- a/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json
+++ b/server/sonar-server/src/main/resources/org/sonar/server/permission/ws/users-example.json
@@ -9,20 +9,15 @@
"login": "admin",
"name": "Administrator",
"email": "admin@admin.com",
- "permissions": [
- "admin",
- "gateadmin",
- "profileadmin"
- ]
+ "avatar": "64e1b8d34f425d19e1ee2ea7236d3028",
+ "permissions": ["admin", "gateadmin", "profileadmin"]
},
{
"login": "george.orwell",
"name": "George Orwell",
"email": "george.orwell@1984.net",
- "permissions": [
- "scan"
- ]
+ "avatar": "583af86a274c1027ef078cada831babf",
+ "permissions": ["scan"]
}
]
}
-
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/user/ws/current-example.json b/server/sonar-server/src/main/resources/org/sonar/server/user/ws/current-example.json
index 77420e5fe9c..359d2f79961 100644
--- a/server/sonar-server/src/main/resources/org/sonar/server/user/ws/current-example.json
+++ b/server/sonar-server/src/main/resources/org/sonar/server/user/ws/current-example.json
@@ -3,21 +3,14 @@
"login": "obiwan.kenobi",
"name": "Obiwan Kenobi",
"email": "obiwan.kenobi@starwars.com",
+ "avatar": "f5aa64437a1821ffe8b563099d506aef",
"local": true,
"externalIdentity": "obiwan.kenobi",
"externalProvider": "sonarqube",
"showOnboardingTutorial": false,
- "scmAccounts": [
- "obiwan:github",
- "obiwan:bitbucket"
- ],
- "groups": [
- "Jedi", "Rebel"
- ],
+ "scmAccounts": ["obiwan:github", "obiwan:bitbucket"],
+ "groups": ["Jedi", "Rebel"],
"permissions": {
- "global": [
- "profileadmin",
- "scan"
- ]
+ "global": ["profileadmin", "scan"]
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsTest.java
index 39071061930..e3675357ce8 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsTest.java
@@ -23,6 +23,7 @@ import org.junit.Before;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
+import org.sonar.server.issue.ws.AvatarResolverImpl;
import org.sonar.server.permission.ws.template.TemplateGroupsAction;
import org.sonar.server.permission.ws.template.TemplateUsersAction;
import org.sonar.server.user.UserSession;
@@ -43,7 +44,7 @@ public class PermissionsWsTest {
PermissionWsSupport permissionWsSupport = mock(PermissionWsSupport.class);
ws = new WsTester(new PermissionsWs(
- new TemplateUsersAction(dbClient, userSession, permissionWsSupport),
+ new TemplateUsersAction(dbClient, userSession, permissionWsSupport, new AvatarResolverImpl()),
new TemplateGroupsAction(dbClient, userSession, permissionWsSupport)));
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java
index 3227f86cf66..c424b43fc99 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/UsersActionTest.java
@@ -31,6 +31,7 @@ import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.issue.ws.AvatarResolverImpl;
import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.countMatches;
@@ -58,7 +59,7 @@ public class UsersActionTest extends BasePermissionWsTest<UsersAction> {
@Override
protected UsersAction buildWsAction() {
- return new UsersAction(db.getDbClient(), userSession, newPermissionWsSupport());
+ return new UsersAction(db.getDbClient(), userSession, newPermissionWsSupport(), new AvatarResolverImpl());
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/template/TemplateUsersActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/template/TemplateUsersActionTest.java
index 69c2eadd95e..959d68b549a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/permission/ws/template/TemplateUsersActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/permission/ws/template/TemplateUsersActionTest.java
@@ -30,6 +30,7 @@ import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.issue.ws.AvatarResolverImpl;
import org.sonar.server.permission.ws.BasePermissionWsTest;
import org.sonar.server.ws.TestRequest;
import org.sonarqube.ws.WsPermissions;
@@ -51,7 +52,7 @@ public class TemplateUsersActionTest extends BasePermissionWsTest<TemplateUsersA
@Override
protected TemplateUsersAction buildWsAction() {
- return new TemplateUsersAction(db.getDbClient(), userSession, newPermissionWsSupport());
+ return new TemplateUsersAction(db.getDbClient(), userSession, newPermissionWsSupport(), new AvatarResolverImpl());
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java
index a5b9f4b3f10..c58427b9cf4 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java
@@ -27,6 +27,7 @@ import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.ws.AvatarResolverImpl;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.tester.UserSessionRule;
@@ -52,7 +53,7 @@ public class CurrentActionTest {
private DbClient dbClient = db.getDbClient();
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
- private WsActionTester ws = new WsActionTester(new CurrentAction(userSessionRule, dbClient, defaultOrganizationProvider));
+ private WsActionTester ws = new WsActionTester(new CurrentAction(userSessionRule, dbClient, defaultOrganizationProvider, new AvatarResolverImpl()));
@Test
public void return_user_info() {
@@ -70,9 +71,9 @@ public class CurrentActionTest {
CurrentWsResponse response = call();
assertThat(response)
- .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::getEmail, CurrentWsResponse::getLocal,
+ .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::getEmail, CurrentWsResponse::getAvatar, CurrentWsResponse::getLocal,
CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getScmAccountsList, CurrentWsResponse::getShowOnboardingTutorial)
- .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", "obiwan.kenobi@starwars.com", true, "obiwan", "sonarqube",
+ .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", "obiwan.kenobi@starwars.com", "f5aa64437a1821ffe8b563099d506aef", true, "obiwan", "sonarqube",
newArrayList("obiwan:github", "obiwan:bitbucket"), true);
}
@@ -91,9 +92,9 @@ public class CurrentActionTest {
CurrentWsResponse response = call();
assertThat(response)
- .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::getLocal,
+ .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::hasAvatar, CurrentWsResponse::getLocal,
CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider)
- .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", true, "obiwan", "sonarqube");
+ .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube");
assertThat(response.hasEmail()).isFalse();
assertThat(response.getScmAccountsList()).isEmpty();
assertThat(response.getGroupsList()).isEmpty();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
index 24b0d526394..de5e7a08608 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
@@ -45,7 +45,7 @@ public class UsersWsTest {
WsTester tester = new WsTester(new UsersWs(
new CreateAction(mock(DbClient.class), mock(UserUpdater.class), userSessionRule),
new UpdateAction(mock(UserUpdater.class), userSessionRule, mock(UserJsonWriter.class), mock(DbClient.class)),
- new CurrentAction(userSessionRule, mock(DbClient.class), mock(DefaultOrganizationProvider.class)),
+ new CurrentAction(userSessionRule, mock(DbClient.class), mock(DefaultOrganizationProvider.class), mock(AvatarResolver.class)),
new ChangePasswordAction(mock(DbClient.class), mock(UserUpdater.class), userSessionRule),
new SearchAction(userSessionRule, mock(UserIndex.class), mock(DbClient.class), mock(AvatarResolver.class))));
controller = tester.controller("api/users");
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json
index 070b2310f07..67eaef0baae 100644
--- a/server/sonar-web/package.json
+++ b/server/sonar-web/package.json
@@ -8,7 +8,6 @@
"babel-polyfill": "6.26.0",
"backbone": "1.2.3",
"backbone.marionette": "2.4.3",
- "blueimp-md5": "1.1.1",
"classnames": "2.2.5",
"clipboard": "1.7.1",
"create-react-class": "15.6.2",
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js
index 68c45c921ec..761cff9cde4 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js
@@ -29,6 +29,7 @@ import { translate } from '../../../../helpers/l10n';
/*::
type CurrentUser = {
+ avatar?: string,
email?: string,
isLoggedIn: boolean,
name: string
@@ -123,7 +124,7 @@ export default class GlobalNavUser extends React.PureComponent {
className={classNames('dropdown js-user-authenticated', { open: this.state.open })}
ref={node => (this.node = node)}>
<a className="dropdown-toggle navbar-avatar" href="#" onClick={this.toggleDropdown}>
- <Avatar email={currentUser.email} name={currentUser.name} size={24} />
+ <Avatar hash={currentUser.avatar} name={currentUser.name} size={24} />
</a>
{this.state.open && (
<ul className="dropdown-menu dropdown-menu-right">
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.js b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.js
index 54f0f55ab25..4fb4f28f2d2 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.js
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.js
@@ -21,7 +21,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import GlobalNavUser from '../GlobalNavUser';
-const currentUser = { isLoggedIn: true, name: 'foo', email: 'foo@bar.baz' };
+const currentUser = { avatar: 'abcd1234', isLoggedIn: true, name: 'foo', email: 'foo@bar.baz' };
const organizations = [
{ key: 'myorg', name: 'MyOrg' },
{ key: 'foo', name: 'Foo' },
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap
index e77bc5d2d8d..d603a20a60f 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap
@@ -10,7 +10,7 @@ exports[`should not render the users organizations when they are not activated 1
onClick={[Function]}
>
<Connect(Avatar)
- email="foo@bar.baz"
+ hash="abcd1234"
name="foo"
size={24}
/>
@@ -83,7 +83,7 @@ exports[`should render the right interface for logged in user 1`] = `
onClick={[Function]}
>
<Connect(Avatar)
- email="foo@bar.baz"
+ hash="abcd1234"
name="foo"
size={24}
/>
@@ -144,7 +144,7 @@ exports[`should render the users organizations 1`] = `
onClick={[Function]}
>
<Connect(Avatar)
- email="foo@bar.baz"
+ hash="abcd1234"
name="foo"
size={24}
/>
@@ -283,7 +283,7 @@ exports[`should update the component correctly when the user changes to anonymou
onClick={[Function]}
>
<Connect(Avatar)
- email="foo@bar.baz"
+ hash="abcd1234"
name="foo"
size={24}
/>
diff --git a/server/sonar-web/src/main/js/apps/account/components/UserCard.js b/server/sonar-web/src/main/js/apps/account/components/UserCard.js
index f6d071696a6..26dbe6e1f96 100644
--- a/server/sonar-web/src/main/js/apps/account/components/UserCard.js
+++ b/server/sonar-web/src/main/js/apps/account/components/UserCard.js
@@ -32,7 +32,7 @@ export default class UserCard extends React.PureComponent {
return (
<div className="account-user">
<div id="avatar" className="pull-left account-user-avatar">
- <Avatar email={user.email} name={user.name} size={60} />
+ <Avatar hash={user.avatar} name={user.name} size={60} />
</div>
<h1 id="name" className="pull-left">
{user.name}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js
index 025d92df51b..a8c14f3c0c0 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js
@@ -272,10 +272,9 @@ export default class BulkChangeModal extends React.PureComponent {
renderAssigneeOption = (option /*: { avatar?: string, email?: string, label: string } */) => (
<span>
- {(option.avatar != null || option.email != null) && (
+ {option.avatar != null && (
<Avatar
className="little-spacer-right"
- email={option.email}
hash={option.avatar}
name={option.label}
size={16}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
index 832d3050075..7c5c784dfd6 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/MembersListItem.js
@@ -48,7 +48,7 @@ export default class MembersListItem extends React.PureComponent {
return (
<tr>
<td className="thin nowrap">
- <Avatar hash={member.avatar} email={member.email} name={member.name} size={AVATAR_SIZE} />
+ <Avatar hash={member.avatar} name={member.name} size={AVATAR_SIZE} />
</td>
<td className="nowrap text-middle">
<strong>{member.name}</strong>
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.js b/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.js
index 0b0bf92735a..d0b97ae588b 100644
--- a/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.js
+++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.js
@@ -63,7 +63,7 @@ export default class UserHolder extends React.PureComponent {
<td className="nowrap">
{!isCreator && (
<Avatar
- email={user.email}
+ hash={user.avatar}
name={user.name}
size={36}
className="text-middle big-spacer-right"
diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js
index 46f0794ddac..3fecdcc4592 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js
+++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js
@@ -64,7 +64,7 @@ export default class UsersSelectSearchOption extends React.PureComponent {
onMouseEnter={this.handleMouseEnter}
onMouseMove={this.handleMouseMove}
title={user.name}>
- <Avatar hash={user.avatar} email={user.email} name={user.name} size={AVATAR_SIZE} />
+ <Avatar hash={user.avatar} name={user.name} size={AVATAR_SIZE} />
<strong className="spacer-left">{this.props.children}</strong>
<span className="note little-spacer-left">{user.login}</span>
</div>
diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchValue.js b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchValue.js
index 4354b75b1e0..347d9359b14 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchValue.js
+++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchValue.js
@@ -41,7 +41,7 @@ export default class UsersSelectSearchValue extends React.PureComponent {
{user &&
user.login && (
<div className="Select-value-label">
- <Avatar hash={user.avatar} email={user.email} name={user.name} size={AVATAR_SIZE} />
+ <Avatar hash={user.avatar} name={user.name} size={AVATAR_SIZE} />
<strong className="spacer-left">{this.props.children}</strong>
<span className="note little-spacer-left">{user.login}</span>
</div>
diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap
index b3e752014e7..e75246d2acb 100644
--- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap
@@ -8,7 +8,6 @@ exports[`should render correctly with email instead of hash 1`] = `
title="Administrator"
>
<Connect(Avatar)
- email="admin@admin.ch"
name="Administrator"
size={16}
/>
diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchValue-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchValue-test.js.snap
index 1387a28aec2..1aad7300b2a 100644
--- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchValue-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchValue-test.js.snap
@@ -36,7 +36,6 @@ exports[`should render correctly with email instead of hash 1`] = `
className="Select-value-label"
>
<Connect(Avatar)
- email="admin@admin.ch"
name="Administrator"
size={16}
/>
diff --git a/server/sonar-web/src/main/js/apps/users/templates/users-list-item.hbs b/server/sonar-web/src/main/js/apps/users/templates/users-list-item.hbs
index 44c303a7db1..63136b96402 100644
--- a/server/sonar-web/src/main/js/apps/users/templates/users-list-item.hbs
+++ b/server/sonar-web/src/main/js/apps/users/templates/users-list-item.hbs
@@ -1,5 +1,5 @@
<td class="thin nowrap">
- <div>{{avatarHelper email name 36}}</div>
+ <div>{{avatarHelper avatar name 36}}</div>
</td>
<td>
diff --git a/server/sonar-web/src/main/js/components/issue/popups/SetAssigneePopup.js b/server/sonar-web/src/main/js/components/issue/popups/SetAssigneePopup.js
index 9c6b6187c65..365ed241de1 100644
--- a/server/sonar-web/src/main/js/components/issue/popups/SetAssigneePopup.js
+++ b/server/sonar-web/src/main/js/components/issue/popups/SetAssigneePopup.js
@@ -148,13 +148,7 @@ export default class SetAssigneePopup extends React.PureComponent {
{this.state.users.map(user => (
<SelectListItem key={user.login} item={user.login}>
{!!user.login && (
- <Avatar
- className="spacer-right"
- email={user.email}
- hash={user.avatar}
- name={user.name}
- size={16}
- />
+ <Avatar className="spacer-right" hash={user.avatar} name={user.name} size={16} />
)}
<span
className="vertical-middle"
diff --git a/server/sonar-web/src/main/js/components/ui/Avatar.js b/server/sonar-web/src/main/js/components/ui/Avatar.tsx
index 448fb782f4b..d9f09b24076 100644
--- a/server/sonar-web/src/main/js/components/ui/Avatar.js
+++ b/server/sonar-web/src/main/js/components/ui/Avatar.tsx
@@ -17,46 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
+import * as React from 'react';
import { connect } from 'react-redux';
-import md5 from 'blueimp-md5';
-import classNames from 'classnames';
+import * as classNames from 'classnames';
import { getGlobalSettingValue } from '../../store/rootReducer';
-function stringToColor(str) {
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
- }
- let color = '#';
- for (let i = 0; i < 3; i++) {
- const value = (hash >> (i * 8)) & 0xff;
- color += ('00' + value.toString(16)).substr(-2);
- }
- return color;
+interface Props {
+ className?: string;
+ enableGravatar: boolean;
+ gravatarServerUrl: string;
+ hash?: string;
+ name: string;
+ size: number;
}
-function getTextColor(background) {
- const rgb = parseInt(background.substr(1), 16);
- const r = (rgb >> 16) & 0xff;
- const g = (rgb >> 8) & 0xff;
- const b = (rgb >> 0) & 0xff;
- const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
- return luma > 140 ? '#222' : '#fff';
-}
-
-class Avatar extends React.PureComponent {
- static propTypes = {
- enableGravatar: PropTypes.bool.isRequired,
- gravatarServerUrl: PropTypes.string.isRequired,
- email: PropTypes.string,
- hash: PropTypes.string,
- name: PropTypes.string.isRequired,
- size: PropTypes.number.isRequired,
- className: PropTypes.string
- };
-
+class Avatar extends React.PureComponent<Props> {
renderFallback() {
const className = classNames(this.props.className, 'rounded');
const color = stringToColor(this.props.name);
@@ -90,30 +65,27 @@ class Avatar extends React.PureComponent {
}
render() {
- if (!this.props.enableGravatar) {
+ if (!this.props.enableGravatar || !this.props.hash) {
return this.renderFallback();
}
- const emailHash = this.props.hash || md5((this.props.email || '').toLowerCase()).trim();
const url = this.props.gravatarServerUrl
- .replace('{EMAIL_MD5}', emailHash)
- .replace('{SIZE}', this.props.size * 2);
-
- const className = classNames(this.props.className, 'rounded');
+ .replace('{EMAIL_MD5}', this.props.hash)
+ .replace('{SIZE}', String(this.props.size * 2));
return (
<img
- className={className}
+ className={classNames(this.props.className, 'rounded')}
src={url}
width={this.props.size}
height={this.props.size}
- alt={this.props.email}
+ alt={this.props.name}
/>
);
}
}
-const mapStateToProps = state => ({
+const mapStateToProps = (state: any) => ({
enableGravatar: (getGlobalSettingValue(state, 'sonar.lf.enableGravatar') || {}).value === 'true',
gravatarServerUrl: (getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl') || {}).value
});
@@ -121,3 +93,26 @@ const mapStateToProps = state => ({
export default connect(mapStateToProps)(Avatar);
export const unconnectedAvatar = Avatar;
+
+/* eslint-disable no-bitwise, no-mixed-operators */
+function stringToColor(str: string) {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ let color = '#';
+ for (let i = 0; i < 3; i++) {
+ const value = (hash >> (i * 8)) & 0xff;
+ color += ('00' + value.toString(16)).substr(-2);
+ }
+ return color;
+}
+
+function getTextColor(background: string) {
+ const rgb = parseInt(background.substr(1), 16);
+ const r = (rgb >> 16) & 0xff;
+ const g = (rgb >> 8) & 0xff;
+ const b = (rgb >> 0) & 0xff;
+ const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+ return luma > 140 ? '#222' : '#fff';
+}
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js b/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.tsx
index 044e07240b1..e42d8c0d454 100644
--- a/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.js
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.tsx
@@ -17,25 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import * as React from 'react';
import { shallow } from 'enzyme';
-import React from 'react';
import { unconnectedAvatar as Avatar } from '../Avatar';
const gravatarServerUrl = 'http://example.com/{EMAIL_MD5}.jpg?s={SIZE}';
-it.skip('should render', () => {
- const avatar = shallow(
- <Avatar
- enableGravatar={true}
- gravatarServerUrl={gravatarServerUrl}
- email="mail@example.com"
- name="Foo"
- size={20}
- />
- );
- expect(avatar).toMatchSnapshot();
-});
-
it('should be able to render with hash only', () => {
const avatar = shallow(
<Avatar
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.js.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.tsx.snap
index d51b5ecabcb..6dd7bc59142 100644
--- a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.js.snap
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Avatar-test.tsx.snap
@@ -24,19 +24,10 @@ exports[`falls back to dummy avatar 1`] = `
exports[`should be able to render with hash only 1`] = `
<img
+ alt="Foo"
className="rounded"
height={30}
src="http://example.com/7daf6c79d4802916d83f6266e24850af.jpg?s=60"
width={30}
/>
`;
-
-exports[`should render 1`] = `
-<img
- alt="mail@example.com"
- className="rounded"
- height={20}
- src="http://example.com/7daf6c79d4802916d83f6266e24850af.jpg?s=40"
- width={20}
-/>
-`;
diff --git a/server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js b/server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js
index 03e2d4190a1..1d975f433cf 100644
--- a/server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js
+++ b/server/sonar-web/src/main/js/helpers/handlebars/avatarHelper.js
@@ -23,11 +23,11 @@ const Handlebars = require('handlebars/runtime');
const WithStore = require('../../components/shared/WithStore').default;
const Avatar = require('../../components/ui/Avatar').default;
-module.exports = function(email, name, size) {
+module.exports = function(hash, name, size) {
return new Handlebars.default.SafeString(
renderToString(
<WithStore>
- <Avatar email={email} name={name} size={size} />
+ <Avatar hash={hash} name={name} size={size} />
</WithStore>
)
);
diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock
index 8bf05a358c7..d7a273aa92a 100644
--- a/server/sonar-web/yarn.lock
+++ b/server/sonar-web/yarn.lock
@@ -1296,10 +1296,6 @@ bluebird@^3.4.7:
version "3.5.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
-blueimp-md5@1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-1.1.1.tgz#cf84ba18285f5c8835dae8ddae5af6468ceace17"
-
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
diff --git a/sonar-ws/src/main/protobuf/ws-permissions.proto b/sonar-ws/src/main/protobuf/ws-permissions.proto
index 2ed5c93bb22..6b6b1e2e501 100644
--- a/sonar-ws/src/main/protobuf/ws-permissions.proto
+++ b/sonar-ws/src/main/protobuf/ws-permissions.proto
@@ -120,6 +120,7 @@ message User {
optional string name = 2;
optional string email = 3;
repeated string permissions = 4;
+ optional string avatar = 5;
}
message OldGroup {
diff --git a/sonar-ws/src/main/protobuf/ws-users.proto b/sonar-ws/src/main/protobuf/ws-users.proto
index c71a3395f2d..cf5a9d96456 100644
--- a/sonar-ws/src/main/protobuf/ws-users.proto
+++ b/sonar-ws/src/main/protobuf/ws-users.proto
@@ -106,6 +106,7 @@ message CurrentWsResponse {
repeated string groups = 9;
optional Permissions permissions = 10;
optional bool showOnboardingTutorial = 11;
+ optional string avatar = 12;
message Permissions {
repeated string global = 1;