]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7238 Display external identity information on user
authorStas Vilchik <vilchiks@gmail.com>
Wed, 30 Mar 2016 08:09:32 +0000 (10:09 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Wed, 30 Mar 2016 11:11:11 +0000 (13:11 +0200)
server/sonar-web/src/main/js/api/users.js
server/sonar-web/src/main/js/apps/account/components/UserCard.js
server/sonar-web/src/main/js/apps/account/components/UserExternalIdentity.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/users/app.js
server/sonar-web/src/main/js/apps/users/list-item-view.js
server/sonar-web/src/main/js/apps/users/list-view.js
server/sonar-web/src/main/js/apps/users/templates/users-list-item.hbs
server/sonar-web/src/main/less/components/ui.less

index ec4b778c623a91266fec4cb3743820f909e8c866..6c00ea849455905098ce1e47db65b572ac240a24 100644 (file)
@@ -34,3 +34,8 @@ export function changePassword (login, password, previousPassword) {
 
   return post(url, data);
 }
+
+export function getIdentityProviders () {
+  const url = window.baseUrl + '/api/users/identity_providers';
+  return getJSON(url);
+}
index 0c848116e2689427f05c94261cd94c8e1226109e..b680781a718aedd1e0f974f9f6f571508c4766d1 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import { IndexLink } from 'react-router';
 
+import UserExternalIdentity from './UserExternalIdentity';
 import Avatar from '../../../components/shared/avatar';
 
 export default function UserCard ({ user }) {
@@ -35,6 +36,11 @@ export default function UserCard ({ user }) {
             <h1 id="name" className="display-inline-block">{user.name}</h1>
           </IndexLink>
           <span id="login" className="note big-spacer-left">{user.login}</span>
+          {!user.local && (
+              <span id="identity-provider" className="big-spacer-left">
+                <UserExternalIdentity user={user}/>
+              </span>
+          )}
         </div>
         <div id="email" className="little-spacer-top">{user.email}</div>
       </section>
diff --git a/server/sonar-web/src/main/js/apps/account/components/UserExternalIdentity.js b/server/sonar-web/src/main/js/apps/account/components/UserExternalIdentity.js
new file mode 100644 (file)
index 0000000..c26d041
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+import React from 'react';
+
+import { getIdentityProviders } from '../../../api/users';
+
+export default class UserExternalIdentity extends React.Component {
+  state = {
+    loading: true
+  };
+
+  componentDidMount () {
+    this.mounted = true;
+    this.fetchIdentityProviders();
+  }
+
+  componentDidUpdate (nextProps) {
+    if (nextProps.user !== this.props.user) {
+      this.this.fetchIdentityProviders();
+    }
+  }
+
+  componentWillUnmount () {
+    this.mounted = false;
+  }
+
+  fetchIdentityProviders () {
+    this.setState({ loading: true });
+    getIdentityProviders()
+        .then(r => r.identityProviders)
+        .then(providers => {
+          if (this.mounted) {
+            const identityProvider = providers
+                .find(provider => provider.key === this.props.user.externalProvider);
+            this.setState({ loading: false, identityProvider });
+          }
+        })
+        .catch(() => {
+          if (this.mounted) {
+            this.setState({ loading: false });
+          }
+        });
+  }
+
+  render () {
+    const { user } = this.props;
+    const { loading, identityProvider } = this.state;
+
+    if (loading) {
+      return null;
+    }
+
+    if (!identityProvider) {
+      return (
+          <span className="note">
+            {user.externalProvider}{': '}{user.externalIdentity}
+          </span>
+      );
+    }
+
+    return (
+        <div
+            className="identity-provider"
+            style={{ backgroundColor: identityProvider.backgroundColor }}>
+          <img src={window.baseUrl + identityProvider.iconPath} width="14" height="14"/>
+          {' '}
+          {user.externalIdentity}
+        </div>
+    );
+  }
+}
index f8d25051478b23b8532e7bf73b327e7fbc3493c2..fcbe54cafb8de4d3e19a4dc15fc43d86ddda6309 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import Marionette from 'backbone.marionette';
+
 import Layout from './layout';
 import Users from './users';
 import HeaderView from './header-view';
 import SearchView from './search-view';
 import ListView from './list-view';
 import ListFooterView from './list-footer-view';
+import { getIdentityProviders } from '../../api/users';
 
 const App = new Marionette.Application();
-const init = function () {
+
+const init = function (providers) {
   const options = window.sonarqube;
 
   // Layout
@@ -45,7 +48,7 @@ const init = function () {
   this.layout.searchRegion.show(this.searchView);
 
   // List View
-  this.listView = new ListView({ collection: this.users });
+  this.listView = new ListView({ collection: this.users, providers });
   this.layout.listRegion.show(this.listView);
 
   // List Footer View
@@ -57,7 +60,7 @@ const init = function () {
 };
 
 App.on('start', function () {
-  init.call(App);
+  getIdentityProviders().then(r => init.call(App, r.identityProviders));
 });
 
 window.sonarqube.appStarted.then(options => App.start(options));
index 6e06fd37557ac127f162a388df8b5f7b076defc8..e5b45bdaba7d54f1c44718061850247d59ee3ce9 100644 (file)
@@ -128,9 +128,16 @@ export default Marionette.ItemView.extend({
   serializeData () {
     const scmAccounts = this.model.get('scmAccounts');
     const scmAccountsLimit = scmAccounts.length > this.scmLimit ? this.scmLimit - 1 : this.scmLimit;
+
     const groups = this.model.get('groups');
     const groupsLimit = groups.length > this.groupsLimit ? this.groupsLimit - 1 : this.groupsLimit;
+
+    const externalProvider = this.model.get('externalProvider');
+    const identityProvider = this.model.get('local') ? null :
+        this.options.providers.find(provider => externalProvider === provider.key);
+
     return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+      identityProvider,
       firstScmAccounts: _.first(scmAccounts, scmAccountsLimit),
       moreScmAccountsCount: scmAccounts.length - scmAccountsLimit,
       firstGroups: _.first(groups, groupsLimit),
index 90f212af173a9210212e64098310db0c87fe1804..3bd5fd9060b25a2f56062c5ac16bc6d997b196c8 100644 (file)
@@ -33,6 +33,10 @@ export default Marionette.CompositeView.extend({
     'sync': 'hideLoading'
   },
 
+  childViewOptions () {
+    return { providers: this.options.providers };
+  },
+
   showLoading () {
     this.$el.addClass('new-loading');
   },
index fb5987fbbd848a70f46a3d095fea5873d7e35fba..d59d649d1c80e81560af42f36725d1a621f862b0 100644 (file)
@@ -9,7 +9,23 @@
     <strong class="js-user-name">{{name}}</strong>
     <span class="js-user-login note little-spacer-left">{{login}}</span>
   </div>
-  <div class="js-user-email little-spacer-top">{{email}}</div>
+
+  {{#if email}}
+    <div class="js-user-email little-spacer-top">{{email}}</div>
+  {{/if}}
+
+  {{#unless local}}
+    <div class="js-user-identity-provider little-spacer-top">
+      {{#if identityProvider}}
+        <div class="identity-provider" style="background-color: {{identityProvider.backgroundColor}}">
+          <img src="{{link identityProvider.iconPath}}" width="14" height="14"/>
+          {{externalIdentity}}
+        </div>
+      {{else}}
+        {{externalProvider}}: {{externalIdentity}}
+      {{/if}}
+    </div>
+  {{/unless}}
 </td>
 
 <td>
@@ -46,7 +62,9 @@
 
 <td class="thin nowrap text-right">
   <a class="js-user-update icon-edit little-spacer-right" title="Update Details" data-toggle="tooltip" href="#"></a>
-  <a class="js-user-change-password icon-lock little-spacer-right" title="Change Password" data-toggle="tooltip"
-     href="#"></a>
+  {{#if local}}
+    <a class="js-user-change-password icon-lock little-spacer-right" title="Change Password" data-toggle="tooltip"
+       href="#"></a>
+  {{/if}}
   <a class="js-user-deactivate icon-delete" title="Deactivate" data-toggle="tooltip" href="#"></a>
 </td>
index 15f4bb163b5676b9143c6e5812c113b9e9162bf1..a8916cb5e12050f0a2baa8daf7a26edec16f6a8b 100644 (file)
 .flash-heavy.in {
   background-color: #ffe456;
 }
+
+
+.identity-provider {
+  display: inline-block;
+  line-height: 14px;
+  padding: 3px 5px;
+  border-radius: 3px;
+  font-size: @smallFontSize;
+  color: #fff;
+}