Commit 61cec768 authored by Jarka Košanová's avatar Jarka Košanová

Merge branch 'add-user-job-title-column' into 'master'

Backend changes to add User Job Title

Closes #207043

See merge request gitlab-org/gitlab!25483
parents a0a26f49 3845276c
...@@ -117,6 +117,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -117,6 +117,7 @@ class ProfilesController < Profiles::ApplicationController
:private_profile, :private_profile,
:include_private_contributions, :include_private_contributions,
:timezone, :timezone,
:job_title,
status: [:emoji, :message] status: [:emoji, :message]
) )
end end
......
...@@ -162,6 +162,7 @@ class User < ApplicationRecord ...@@ -162,6 +162,7 @@ class User < ApplicationRecord
has_one :status, class_name: 'UserStatus' has_one :status, class_name: 'UserStatus'
has_one :user_preference has_one :user_preference
has_one :user_detail
# #
# Validations # Validations
...@@ -259,8 +260,10 @@ class User < ApplicationRecord ...@@ -259,8 +260,10 @@ class User < ApplicationRecord
delegate :sourcegraph_enabled, :sourcegraph_enabled=, to: :user_preference delegate :sourcegraph_enabled, :sourcegraph_enabled=, to: :user_preference
delegate :setup_for_company, :setup_for_company=, to: :user_preference delegate :setup_for_company, :setup_for_company=, to: :user_preference
delegate :render_whitespace_in_code, :render_whitespace_in_code=, to: :user_preference delegate :render_whitespace_in_code, :render_whitespace_in_code=, to: :user_preference
delegate :job_title, :job_title=, to: :user_detail, allow_nil: true
accepts_nested_attributes_for :user_preference, update_only: true accepts_nested_attributes_for :user_preference, update_only: true
accepts_nested_attributes_for :user_detail, update_only: true
state_machine :state, initial: :active do state_machine :state, initial: :active do
event :block do event :block do
...@@ -1619,6 +1622,10 @@ class User < ApplicationRecord ...@@ -1619,6 +1622,10 @@ class User < ApplicationRecord
super.presence || build_user_preference super.presence || build_user_preference
end end
def user_detail
super.presence || build_user_detail
end
def todos_limited_to(ids) def todos_limited_to(ids)
todos.where(id: ids) todos.where(id: ids)
end end
......
# frozen_string_literal: true
class UserDetail < ApplicationRecord
belongs_to :user
validates :job_title, presence: true, length: { maximum: 200 }
end
---
title: Add support for user Job Title
merge_request: 25483
author:
type: added
...@@ -20,6 +20,8 @@ en: ...@@ -20,6 +20,8 @@ en:
token: "Grafana HTTP API Token" token: "Grafana HTTP API Token"
grafana_url: "Grafana API URL" grafana_url: "Grafana API URL"
grafana_enabled: "Grafana integration enabled" grafana_enabled: "Grafana integration enabled"
user/user_detail:
job_title: 'Job title'
views: views:
pagination: pagination:
previous: "Prev" previous: "Prev"
......
# frozen_string_literal: true
class CreateUserDetails < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
with_lock_retries do
create_table :user_details, id: false do |t|
t.references :user, index: false, foreign_key: { on_delete: :cascade }, null: false, primary_key: true
t.string :job_title, limit: 200, default: "", null: false
end
end
add_index :user_details, :user_id, unique: true
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_02_26_162723) do ActiveRecord::Schema.define(version: 2020_02_27_165129) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -4170,6 +4170,11 @@ ActiveRecord::Schema.define(version: 2020_02_26_162723) do ...@@ -4170,6 +4170,11 @@ ActiveRecord::Schema.define(version: 2020_02_26_162723) do
t.index ["user_id", "key"], name: "index_user_custom_attributes_on_user_id_and_key", unique: true t.index ["user_id", "key"], name: "index_user_custom_attributes_on_user_id_and_key", unique: true
end end
create_table "user_details", primary_key: "user_id", force: :cascade do |t|
t.string "job_title", limit: 200, default: "", null: false
t.index ["user_id"], name: "index_user_details_on_user_id", unique: true
end
create_table "user_interacted_projects", id: false, force: :cascade do |t| create_table "user_interacted_projects", id: false, force: :cascade do |t|
t.integer "user_id", null: false t.integer "user_id", null: false
t.integer "project_id", null: false t.integer "project_id", null: false
...@@ -5028,6 +5033,7 @@ ActiveRecord::Schema.define(version: 2020_02_26_162723) do ...@@ -5028,6 +5033,7 @@ ActiveRecord::Schema.define(version: 2020_02_26_162723) do
add_foreign_key "u2f_registrations", "users" add_foreign_key "u2f_registrations", "users"
add_foreign_key "user_callouts", "users", on_delete: :cascade add_foreign_key "user_callouts", "users", on_delete: :cascade
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
add_foreign_key "user_details", "users", on_delete: :cascade
add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade
add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade
add_foreign_key "user_preferences", "users", on_delete: :cascade add_foreign_key "user_preferences", "users", on_delete: :cascade
......
...@@ -95,6 +95,7 @@ GET /users ...@@ -95,6 +95,7 @@ GET /users
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "", "organization": "",
"job_title": "",
"last_sign_in_at": "2012-06-01T11:41:01Z", "last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z", "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1, "theme_id": 1,
...@@ -132,6 +133,7 @@ GET /users ...@@ -132,6 +133,7 @@ GET /users
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "", "organization": "",
"job_title": "",
"last_sign_in_at": null, "last_sign_in_at": null,
"confirmed_at": "2012-05-30T16:53:06.148Z", "confirmed_at": "2012-05-30T16:53:06.148Z",
"theme_id": 1, "theme_id": 1,
...@@ -247,7 +249,8 @@ Parameters: ...@@ -247,7 +249,8 @@ Parameters:
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "" "organization": "",
"job_title": "Operations Specialist"
} }
``` ```
...@@ -282,6 +285,7 @@ Example Responses: ...@@ -282,6 +285,7 @@ Example Responses:
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "", "organization": "",
"job_title": "Operations Specialist",
"last_sign_in_at": "2012-06-01T11:41:01Z", "last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z", "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1, "theme_id": 1,
...@@ -545,6 +549,7 @@ GET /user ...@@ -545,6 +549,7 @@ GET /user
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"organization": "", "organization": "",
"job_title": "",
"last_sign_in_at": "2012-06-01T11:41:01Z", "last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z", "confirmed_at": "2012-05-23T09:05:22Z",
"theme_id": 1, "theme_id": 1,
......
...@@ -4,7 +4,7 @@ module API ...@@ -4,7 +4,7 @@ module API
module Entities module Entities
class User < UserBasic class User < UserBasic
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 expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title
end end
end end
end end
...@@ -89,6 +89,16 @@ describe ProfilesController, :request_store do ...@@ -89,6 +89,16 @@ describe ProfilesController, :request_store do
expect(user.reload.status.message).to eq('Working hard!') expect(user.reload.status.message).to eq('Working hard!')
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
end end
it 'allows updating user specified job title' do
title = 'Marketing Executive'
sign_in(user)
put :update, params: { user: { job_title: title } }
expect(user.reload.job_title).to eq(title)
expect(response).to have_gitlab_http_status(:found)
end
end end
describe 'PUT update_username' do describe 'PUT update_username' do
......
# frozen_string_literal: true
FactoryBot.define do
factory :user_detail do
user
job_title { 'VP of Sales' }
end
end
# frozen_string_literal: true
require 'spec_helper'
describe UserDetail do
it { is_expected.to belong_to(:user) }
describe 'validations' do
describe 'job_title' do
it { is_expected.to validate_presence_of(:job_title) }
it { is_expected.to validate_length_of(:job_title).is_at_most(200) }
end
end
end
...@@ -29,6 +29,7 @@ describe User, :do_not_mock_admin_mode do ...@@ -29,6 +29,7 @@ describe User, :do_not_mock_admin_mode do
it { is_expected.to have_one(:namespace) } it { is_expected.to have_one(:namespace) }
it { is_expected.to have_one(:status) } it { is_expected.to have_one(:status) }
it { is_expected.to have_one(:max_access_level_membership) } it { is_expected.to have_one(:max_access_level_membership) }
it { is_expected.to have_one(:user_detail) }
it { is_expected.to have_many(:snippets).dependent(:destroy) } it { is_expected.to have_many(:snippets).dependent(:destroy) }
it { is_expected.to have_many(:members) } it { is_expected.to have_many(:members) }
it { is_expected.to have_many(:project_members) } it { is_expected.to have_many(:project_members) }
...@@ -4318,4 +4319,19 @@ describe User, :do_not_mock_admin_mode do ...@@ -4318,4 +4319,19 @@ describe User, :do_not_mock_admin_mode do
expect(user.hook_attrs).to eq(user_attributes) expect(user.hook_attrs).to eq(user_attributes)
end end
end end
describe 'user detail' do
context 'when user is initialized' do
let(:user) { build(:user) }
it { expect(user.user_detail).to be_present }
it { expect(user.user_detail).not_to be_persisted }
end
context 'when user detail exists' do
let(:user) { create(:user, job_title: 'Engineer') }
it { expect(user.user_detail).to be_persisted }
end
end
end end
...@@ -330,6 +330,21 @@ describe API::Users, :do_not_mock_admin_mode do ...@@ -330,6 +330,21 @@ describe API::Users, :do_not_mock_admin_mode do
expect(json_response.keys).not_to include 'last_sign_in_ip' expect(json_response.keys).not_to include 'last_sign_in_ip'
end end
context 'when job title is present' do
let(:job_title) { 'Fullstack Engineer' }
before do
create(:user_detail, user: user, job_title: job_title)
end
it 'returns job title of a user' do
get api("/users/#{user.id}", user)
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response['job_title']).to eq(job_title)
end
end
context 'when authenticated as admin' do context 'when authenticated as admin' do
it 'includes the `is_admin` field' do it 'includes the `is_admin` field' do
get api("/users/#{user.id}", admin) get api("/users/#{user.id}", admin)
......
...@@ -64,6 +64,13 @@ describe Users::UpdateService do ...@@ -64,6 +64,13 @@ describe Users::UpdateService do
end.not_to change { user.name } end.not_to change { user.name }
end end
it 'updates user detail with provided attributes' do
result = update_user(user, job_title: 'Backend Engineer')
expect(result).to eq(status: :success)
expect(user.job_title).to eq('Backend Engineer')
end
def update_user(user, opts) def update_user(user, opts)
described_class.new(user, opts.merge(user: user)).execute described_class.new(user, opts.merge(user: user)).execute
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