Commit 7d5cfbca authored by Phil Hughes's avatar Phil Hughes

Merge branch 'add-work-information-to-user-api' into 'master'

Expose work information on User

See merge request gitlab-org/gitlab!29258
parents bcdeb91d aad737c4
...@@ -38,8 +38,7 @@ const populateUserInfo = user => { ...@@ -38,8 +38,7 @@ const populateUserInfo = user => {
name: userData.name, name: userData.name,
location: userData.location, location: userData.location,
bio: userData.bio, bio: userData.bio,
organization: userData.organization, workInformation: userData.work_information,
jobTitle: userData.job_title,
loaded: true, loaded: true,
}); });
} }
...@@ -71,7 +70,7 @@ export default (elements = document.querySelectorAll('.js-user-link')) => { ...@@ -71,7 +70,7 @@ export default (elements = document.querySelectorAll('.js-user-link')) => {
const user = { const user = {
location: null, location: null,
bio: null, bio: null,
organization: null, workInformation: null,
status: null, status: null,
loaded: false, loaded: false,
}; };
......
<script> <script>
import { GlPopover, GlSkeletonLoading, GlSprintf } from '@gitlab/ui'; import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue'; import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji'; import { glEmojiTag } from '../../../emoji';
import { s__ } from '~/locale';
import { isString } from 'lodash';
export default { export default {
name: 'UserPopover', name: 'UserPopover',
...@@ -12,7 +10,6 @@ export default { ...@@ -12,7 +10,6 @@ export default {
Icon, Icon,
GlPopover, GlPopover,
GlSkeletonLoading, GlSkeletonLoading,
GlSprintf,
UserAvatarImage, UserAvatarImage,
}, },
props: { props: {
...@@ -49,26 +46,7 @@ export default { ...@@ -49,26 +46,7 @@ export default {
return !this.user.name; return !this.user.name;
}, },
workInformationIsLoading() { workInformationIsLoading() {
return !this.user.loaded && this.workInformation === null; return !this.user.loaded && this.user.workInformation === null;
},
workInformation() {
const { jobTitle, organization } = this.user;
if (organization && jobTitle) {
return {
message: s__('Profile|%{job_title} at %{organization}'),
placeholders: { job_title: jobTitle, organization },
};
} else if (organization) {
return organization;
} else if (jobTitle) {
return jobTitle;
}
return null;
},
workInformationShouldUseSprintf() {
return !isString(this.workInformation);
}, },
locationIsLoading() { locationIsLoading() {
return !this.user.loaded && this.user.location === null; return !this.user.loaded && this.user.location === null;
...@@ -98,23 +76,13 @@ export default { ...@@ -98,23 +76,13 @@ export default {
<icon name="profile" class="category-icon flex-shrink-0" /> <icon name="profile" class="category-icon flex-shrink-0" />
<span ref="bio" class="ml-1">{{ user.bio }}</span> <span ref="bio" class="ml-1">{{ user.bio }}</span>
</div> </div>
<div v-if="workInformation" class="d-flex mb-1"> <div v-if="user.workInformation" class="d-flex mb-1">
<icon <icon
v-show="!workInformationIsLoading" v-show="!workInformationIsLoading"
name="work" name="work"
class="category-icon flex-shrink-0" class="category-icon flex-shrink-0"
/> />
<span ref="workInformation" class="ml-1"> <span ref="workInformation" class="ml-1">{{ user.workInformation }}</span>
<gl-sprintf v-if="workInformationShouldUseSprintf" :message="workInformation.message">
<template
v-for="(placeholder, slotName) in workInformation.placeholders"
v-slot:[slotName]
>
<span :key="slotName">{{ placeholder }}</span>
</template>
</gl-sprintf>
<span v-else>{{ workInformation }}</span>
</span>
</div> </div>
<gl-skeleton-loading <gl-skeleton-loading
v-if="workInformationIsLoading" v-if="workInformationIsLoading"
......
...@@ -3,8 +3,12 @@ ...@@ -3,8 +3,12 @@
module API module API
module Entities module Entities
class User < UserBasic class User < UserBasic
include UsersHelper
expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) }
expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title
expose :work_information do |user|
work_information(user)
end
end end
end end
end end
...@@ -10,8 +10,7 @@ const DEFAULT_PROPS = { ...@@ -10,8 +10,7 @@ const DEFAULT_PROPS = {
name: 'Administrator', name: 'Administrator',
location: 'Vienna', location: 'Vienna',
bio: null, bio: null,
organization: null, workInformation: null,
jobTitle: null,
status: null, status: null,
}, },
}; };
...@@ -59,8 +58,7 @@ describe('User Popover Component', () => { ...@@ -59,8 +58,7 @@ describe('User Popover Component', () => {
username: null, username: null,
location: null, location: null,
bio: null, bio: null,
organization: null, workInformation: null,
jobTitle: null,
status: null, status: null,
}, },
}, },
...@@ -93,7 +91,7 @@ describe('User Popover Component', () => { ...@@ -93,7 +91,7 @@ describe('User Popover Component', () => {
const findWorkInformation = () => wrapper.find({ ref: 'workInformation' }); const findWorkInformation = () => wrapper.find({ ref: 'workInformation' });
const findBio = () => wrapper.find({ ref: 'bio' }); const findBio = () => wrapper.find({ ref: 'bio' });
it('should show only bio if organization and job title are not available', () => { it('should show only bio if work information is not available', () => {
const user = { ...DEFAULT_PROPS.user, bio: 'My super interesting bio' }; const user = { ...DEFAULT_PROPS.user, bio: 'My super interesting bio' };
createWrapper({ user }); createWrapper({ user });
...@@ -102,27 +100,10 @@ describe('User Popover Component', () => { ...@@ -102,27 +100,10 @@ describe('User Popover Component', () => {
expect(findWorkInformation().exists()).toBe(false); expect(findWorkInformation().exists()).toBe(false);
}); });
it('should show only organization if job title is not available', () => { it('should show work information when it is available', () => {
const user = { ...DEFAULT_PROPS.user, organization: 'GitLab' };
createWrapper({ user });
expect(findWorkInformation().text()).toBe('GitLab');
});
it('should show only job title if organization is not available', () => {
const user = { ...DEFAULT_PROPS.user, jobTitle: 'Frontend Engineer' };
createWrapper({ user });
expect(findWorkInformation().text()).toBe('Frontend Engineer');
});
it('should show organization and job title if they are both available', () => {
const user = { const user = {
...DEFAULT_PROPS.user, ...DEFAULT_PROPS.user,
organization: 'GitLab', workInformation: 'Frontend Engineer at GitLab',
jobTitle: 'Frontend Engineer',
}; };
createWrapper({ user }); createWrapper({ user });
...@@ -130,17 +111,17 @@ describe('User Popover Component', () => { ...@@ -130,17 +111,17 @@ describe('User Popover Component', () => {
expect(findWorkInformation().text()).toBe('Frontend Engineer at GitLab'); expect(findWorkInformation().text()).toBe('Frontend Engineer at GitLab');
}); });
it('should display bio and job info in separate lines', () => { it('should display bio and work information in separate lines', () => {
const user = { const user = {
...DEFAULT_PROPS.user, ...DEFAULT_PROPS.user,
bio: 'My super interesting bio', bio: 'My super interesting bio',
organization: 'GitLab', workInformation: 'Frontend Engineer at GitLab',
}; };
createWrapper({ user }); createWrapper({ user });
expect(findBio().text()).toBe('My super interesting bio'); expect(findBio().text()).toBe('My super interesting bio');
expect(findWorkInformation().text()).toBe('GitLab'); expect(findWorkInformation().text()).toBe('Frontend Engineer at GitLab');
}); });
it('should not encode special characters in bio', () => { it('should not encode special characters in bio', () => {
...@@ -154,40 +135,6 @@ describe('User Popover Component', () => { ...@@ -154,40 +135,6 @@ describe('User Popover Component', () => {
expect(findBio().text()).toBe('I like <html> & CSS'); expect(findBio().text()).toBe('I like <html> & CSS');
}); });
it('should not encode special characters in organization', () => {
const user = {
...DEFAULT_PROPS.user,
organization: 'Me & my <funky> Company',
};
createWrapper({ user });
expect(findWorkInformation().text()).toBe('Me & my <funky> Company');
});
it('should not encode special characters in job title', () => {
const user = {
...DEFAULT_PROPS.user,
jobTitle: 'Manager & Team Lead',
};
createWrapper({ user });
expect(findWorkInformation().text()).toBe('Manager & Team Lead');
});
it('should not encode special characters when both job title and organization are set', () => {
const user = {
...DEFAULT_PROPS.user,
jobTitle: 'Manager & Team Lead',
organization: 'Me & my <funky> Company',
};
createWrapper({ user });
expect(findWorkInformation().text()).toBe('Manager & Team Lead at Me & my <funky> Company');
});
it('shows icon for bio', () => { it('shows icon for bio', () => {
const user = { const user = {
...DEFAULT_PROPS.user, ...DEFAULT_PROPS.user,
...@@ -201,10 +148,10 @@ describe('User Popover Component', () => { ...@@ -201,10 +148,10 @@ describe('User Popover Component', () => {
); );
}); });
it('shows icon for organization', () => { it('shows icon for work information', () => {
const user = { const user = {
...DEFAULT_PROPS.user, ...DEFAULT_PROPS.user,
organization: 'GitLab', workInformation: 'GitLab',
}; };
createWrapper({ user }); createWrapper({ user });
......
# frozen_string_literal: true
require 'spec_helper'
describe API::Entities::User do
let(:user) { create(:user) }
let(:current_user) { create(:user) }
subject { described_class.new(user, current_user: current_user).as_json }
it 'exposes correct attributes' do
expect(subject).to include(:bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :work_information)
end
it 'exposes created_at if the current user can read the user profile' do
allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, user).and_return(true)
expect(subject).to include(:created_at)
end
it 'does not expose created_at if the current user cannot read the user profile' do
allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, user).and_return(false)
expect(subject).not_to include(:created_at)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment