Commit b7efcb01 authored by Peter Hegman's avatar Peter Hegman Committed by peterhegman

Remove `role` field and add `job_title` field

The `role` field is removed from the edit profile page and is replaced
with a `job_title` field. This new `job_title` field is displayed on
the profile page and in the user popover. If `job_title` and
`organization` are both set they are displayed as
"#{job_title} at ${organization}"
parent 08ef4da5
......@@ -39,6 +39,7 @@ const populateUserInfo = user => {
location: userData.location,
bio: userData.bio,
organization: userData.organization,
jobTitle: userData.job_title,
loaded: true,
});
}
......
......@@ -3,6 +3,7 @@ import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji';
import { s__, sprintf } from '~/locale';
export default {
name: 'UserPopover',
......@@ -45,8 +46,21 @@ export default {
nameIsLoading() {
return !this.user.name;
},
jobInfoIsLoading() {
return !this.user.loaded && this.user.organization === null;
workInformationIsLoading() {
return !this.user.loaded && this.workInformation === null;
},
workInformation() {
const { jobTitle, organization } = this.user;
if (organization && jobTitle) {
return sprintf(s__('Profile|%{jobTitle} at %{organization}'), { jobTitle, organization });
} else if (organization) {
return organization;
} else if (jobTitle) {
return jobTitle;
}
return null;
},
locationIsLoading() {
return !this.user.loaded && this.user.location === null;
......@@ -76,12 +90,16 @@ export default {
<icon name="profile" class="category-icon flex-shrink-0" />
<span class="ml-1">{{ user.bio }}</span>
</div>
<div v-if="user.organization" class="js-organization d-flex mb-1">
<icon v-show="!jobInfoIsLoading" name="work" class="category-icon flex-shrink-0" />
<span class="ml-1">{{ user.organization }}</span>
<div v-if="workInformation" class="js-work-information d-flex mb-1">
<icon
v-show="!workInformationIsLoading"
name="work"
class="category-icon flex-shrink-0"
/>
<span class="ml-1">{{ workInformation }}</span>
</div>
<gl-skeleton-loading
v-if="jobInfoIsLoading"
v-if="workInformationIsLoading"
:lines="1"
class="animation-container-small mb-1"
/>
......
......@@ -401,3 +401,18 @@
line-height: 16px;
text-align: center;
}
@mixin middle-dot-divider {
&::after {
content: '\00B7'; // Middle Dot
padding: 0 6px;
font-weight: $gl-font-weight-bold;
}
&:last-child {
&::after {
content: '';
padding: 0;
}
}
}
......@@ -74,17 +74,12 @@
// Middle dot divider between each element in a list of items.
.middle-dot-divider {
&::after {
content: '\00B7'; // Middle Dot
padding: 0 6px;
font-weight: $gl-font-weight-bold;
}
@include middle-dot-divider;
}
&:last-child {
&::after {
content: '';
padding: 0;
}
.middle-dot-divider-sm {
@include media-breakpoint-up(sm) {
@include middle-dot-divider;
}
}
......@@ -202,8 +197,21 @@
}
.user-profile {
.cover-controls a {
margin-left: 5px;
.cover-controls {
@include media-breakpoint-down(xs) {
position: static;
display: flex;
padding: 0 0.875rem 1.25rem;
}
a {
margin-left: 0.25rem;
@include media-breakpoint-down(xs) {
margin: 0 0.125rem;
flex: 1 0 auto;
}
}
}
.profile-header {
......
......@@ -91,6 +91,21 @@ module UsersHelper
end
end
def work_information(user)
return unless user
organization = user.organization
job_title = user.job_title
if organization.present? && job_title.present?
s_('Profile|%{job_title} at %{organization}') % { job_title: job_title, organization: organization }
elsif job_title.present?
job_title
elsif organization.present?
organization
end
end
private
def get_profile_tabs
......
......@@ -90,7 +90,6 @@
.row
= render 'profiles/name', form: f, user: @user
= f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' }
= f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { prompt: _('Select your role') }, required: true, class: 'input-md'
= render_if_exists 'profiles/email_settings', form: f
= f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
......@@ -101,6 +100,7 @@
= f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) }
- else
= f.text_field :location, label: s_('Profiles|Location'), class: 'input-lg', placeholder: s_("Profiles|City, country")
= f.text_field :job_title, class: 'input-md'
= f.text_field :organization, label: s_('Profiles|Organization'), class: 'input-md', help: s_("Profiles|Who you represent or work for")
= f.text_area :bio, label: s_('Profiles|Bio'), rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters")
%hr
......
%p
%p.mb-1.mb-sm-2.mt-2.mt-sm-3
%span.middle-dot-divider
@#{@user.username}
- if can?(current_user, :read_user_profile, @user)
......
......@@ -51,10 +51,18 @@
= emoji_icon(@user.status.emoji)
= markdown_field(@user.status, :message)
= render "users/profile_basic_info"
.cover-desc.cgray
- unless @user.public_email.blank?
.profile-link-holder.middle-dot-divider
= link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link'
.cover-desc.cgray.mb-1.mb-sm-2
- unless @user.location.blank?
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mb-1.mb-sm-0
= sprite_icon('location', size: 16, css_class: 'vertical-align-sub fgray')
%span.vertical-align-middle
= @user.location
- unless work_information(@user).blank?
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline
= sprite_icon('work', size: 16, css_class: 'vertical-align-middle fgray')
%span.vertical-align-middle
= work_information(@user)
.cover-desc.cgray.mb-1.mb-sm-2
- unless @user.skype.blank?
.profile-link-holder.middle-dot-divider
= link_to "skype:#{@user.skype}", title: "Skype" do
......@@ -64,24 +72,18 @@
= link_to linkedin_url(@user), title: "LinkedIn", target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('linkedin-square')
- unless @user.twitter.blank?
.profile-link-holder.middle-dot-divider
.profile-link-holder.middle-dot-divider-sm
= link_to twitter_url(@user), title: "Twitter", target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('twitter-square')
- unless @user.website_url.blank?
.profile-link-holder.middle-dot-divider
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0
= link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow'
- unless @user.location.blank?
.profile-link-holder.middle-dot-divider
= sprite_icon('location', size: 16, css_class: 'vertical-align-sub')
= @user.location
- unless @user.organization.blank?
.profile-link-holder.middle-dot-divider
= sprite_icon('work', size: 16, css_class: 'vertical-align-sub')
= @user.organization
- unless @user.public_email.blank?
.profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0
= link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link'
- if @user.bio.present?
.cover-desc.cgray
%p.profile-user-bio
%p.profile-user-bio.font-italic
= @user.bio
- unless profile_tabs.empty?
......
......@@ -15148,6 +15148,12 @@ msgstr ""
msgid "Profiles|your account"
msgstr ""
msgid "Profile|%{jobTitle} at %{organization}"
msgstr ""
msgid "Profile|%{job_title} at %{organization}"
msgstr ""
msgid "Profiling - Performance bar"
msgstr ""
......@@ -17679,9 +17685,6 @@ msgstr ""
msgid "Select user"
msgstr ""
msgid "Select your role"
msgstr ""
msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
msgstr ""
......
......@@ -15,6 +15,11 @@ describe 'User edit profile' do
wait_for_requests if respond_to?(:wait_for_requests)
end
def visit_user
visit user_path(user)
wait_for_requests
end
it 'changes user profile' do
fill_in 'user_skype', with: 'testskype'
fill_in 'user_linkedin', with: 'testlinkedin'
......@@ -22,8 +27,8 @@ describe 'User edit profile' do
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab'
fill_in 'user_job_title', with: 'Frontend Engineer'
fill_in 'user_organization', with: 'GitLab'
select 'Data Analyst', from: 'user_role'
submit_settings
expect(user.reload).to have_attributes(
......@@ -32,8 +37,8 @@ describe 'User edit profile' do
twitter: 'testtwitter',
website_url: 'testurl',
bio: 'I <3 GitLab',
organization: 'GitLab',
role: 'data_analyst'
job_title: 'Frontend Engineer',
organization: 'GitLab'
)
expect(find('#user_location').value).to eq 'Ukraine'
......@@ -94,11 +99,6 @@ describe 'User edit profile' do
end
context 'user status', :js do
def visit_user
visit user_path(user)
wait_for_requests
end
def select_emoji(emoji_name, is_modal = false)
emoji_menu_class = is_modal ? '.js-modal-status-emoji-menu' : '.js-status-emoji-menu'
toggle_button = find('.js-toggle-emoji-menu')
......@@ -381,4 +381,34 @@ describe 'User edit profile' do
end
end
end
context 'work information', :js do
it 'shows user\'s job title and organization when both entered' do
fill_in 'user_job_title', with: 'Frontend Engineer'
fill_in 'user_organization', with: 'GitLab - work info test'
submit_settings
visit_user
expect(page).to have_content('Frontend Engineer at GitLab - work info test')
end
it 'shows user\'s job title when only job title is entered' do
fill_in 'user_job_title', with: 'Frontend Engineer - work info test'
submit_settings
visit_user
expect(page).to have_content('Frontend Engineer - work info test')
end
it 'shows user\'s organization when only organization is entered' do
fill_in 'user_organization', with: 'GitLab - work info test'
submit_settings
visit_user
expect(page).to have_content('GitLab - work info test')
end
end
end
......@@ -26,6 +26,34 @@ describe 'User page' do
expect(page).not_to have_content("This user has a private profile")
end
context 'work information' do
subject { visit(user_path(user)) }
it 'shows job title and organization details' do
user.update(organization: 'GitLab - work info test', job_title: 'Frontend Engineer')
subject
expect(page).to have_content('Frontend Engineer at GitLab - work info test')
end
it 'shows job title' do
user.update(organization: nil, job_title: 'Frontend Engineer - work info test')
subject
expect(page).to have_content('Frontend Engineer - work info test')
end
it 'shows organization details' do
user.update(organization: 'GitLab - work info test', job_title: '')
subject
expect(page).to have_content('GitLab - work info test')
end
end
end
context 'with private profile' do
......
......@@ -11,6 +11,7 @@ const DEFAULT_PROPS = {
location: 'Vienna',
bio: null,
organization: null,
jobTitle: null,
status: null,
},
};
......@@ -56,6 +57,7 @@ describe('User Popover Component', () => {
location: null,
bio: null,
organization: null,
jobTitle: null,
status: null,
},
},
......@@ -85,29 +87,52 @@ describe('User Popover Component', () => {
});
describe('job data', () => {
it('should show only bio if no organization is available', () => {
it('should show only bio if organization and job title are not available', () => {
const user = { ...DEFAULT_PROPS.user, bio: 'Engineer' };
createWrapper({ user });
expect(wrapper.text()).toContain('Engineer');
expect(wrapper.find('.js-work-information').exists()).toBe(false);
});
it('should show only organization if no bio is available', () => {
it('should show only organization if job title is not available', () => {
const user = { ...DEFAULT_PROPS.user, organization: 'GitLab' };
createWrapper({ user });
expect(wrapper.text()).toContain('GitLab');
expect(wrapper.find('.js-work-information > span').text()).toBe('GitLab');
});
it('should display bio and organization in separate lines', () => {
it('should show only job title if organization is not available', () => {
const user = { ...DEFAULT_PROPS.user, jobTitle: 'Frontend Engineer' };
createWrapper({ user });
expect(wrapper.find('.js-work-information > span').text()).toBe('Frontend Engineer');
});
it('should show organization and job title if they are both available', () => {
const user = {
...DEFAULT_PROPS.user,
organization: 'GitLab',
jobTitle: 'Frontend Engineer',
};
createWrapper({ user });
expect(wrapper.find('.js-work-information > span').text()).toBe(
'Frontend Engineer at GitLab',
);
});
it('should display bio and job info in separate lines', () => {
const user = { ...DEFAULT_PROPS.user, bio: 'Engineer', organization: 'GitLab' };
createWrapper({ user });
expect(wrapper.find('.js-bio').text()).toContain('Engineer');
expect(wrapper.find('.js-organization').text()).toContain('GitLab');
expect(wrapper.find('.js-work-information').text()).toContain('GitLab');
});
it('should not encode special characters in bio and organization', () => {
......@@ -120,7 +145,7 @@ describe('User Popover Component', () => {
createWrapper({ user });
expect(wrapper.find('.js-bio').text()).toContain('Manager & Team Lead');
expect(wrapper.find('.js-organization').text()).toContain('Me & my <funky> Company');
expect(wrapper.find('.js-work-information').text()).toContain('Me & my <funky> Company');
});
it('shows icon for bio', () => {
......
......@@ -178,4 +178,29 @@ describe UsersHelper do
end
end
end
describe '#work_information' do
it "returns job title concatinated with organization if both are present" do
user = create(:user, organization: 'GitLab', job_title: 'Frontend Engineer')
expect(helper.work_information(user)).to eq('Frontend Engineer at GitLab')
end
it "returns organization if only organization is present" do
user = create(:user, organization: 'GitLab')
expect(helper.work_information(user)).to eq('GitLab')
end
it "returns job title if only job_title is present" do
user = create(:user, job_title: 'Frontend Engineer')
expect(helper.work_information(user)).to eq('Frontend Engineer')
end
it "returns nil if job_title and organization are not present" do
expect(helper.work_information(user)).to be_nil
end
it "returns nil user paramater is nil" do
expect(helper.work_information(nil)).to be_nil
end
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