Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-03 00:09:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-03 00:09:22 +0300
commit65be6f9dd4d92590294f18c6212a075585467f6d (patch)
tree4d59ef2507b490450379b0954be041c7beca826d
parentfee10148072e2e96d14034f099985a441a844c6e (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/services/user_preferences/update_service.rb20
-rw-r--r--app/views/projects/merge_requests/show.html.haml1
-rw-r--r--changelogs/unreleased/284116-sync-single-file-mode-user-preference.yml5
-rw-r--r--doc/api/users.md22
-rw-r--r--doc/integration/omniauth.md68
-rw-r--r--lib/api/entities/user_preferences.rb9
-rw-r--r--lib/api/users.rb23
-rw-r--r--package.json2
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb13
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb10
-rw-r--r--spec/controllers/projects_controller_spec.rb20
-rw-r--r--spec/experiments/new_project_readme_experiment_spec.rb4
-rw-r--r--spec/frontend/pipelines/graph_shared/links_layer_spec.js13
-rw-r--r--spec/frontend/vue_alerts_spec.js8
-rw-r--r--spec/mailers/notify_spec.rb4
-rw-r--r--spec/requests/api/users_preferences_spec.rb65
-rw-r--r--spec/services/user_preferences/update_service_spec.rb33
-rw-r--r--yarn.lock8
21 files changed, 260 insertions, 75 deletions
diff --git a/Gemfile b/Gemfile
index a66edbc23ce..af6fe3c562e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -488,7 +488,7 @@ gem 'flipper', '~> 0.17.1'
gem 'flipper-active_record', '~> 0.17.1'
gem 'flipper-active_support_cache_store', '~> 0.17.1'
gem 'unleash', '~> 0.1.5'
-gem 'gitlab-experiment', '~> 0.5.1'
+gem 'gitlab-experiment', '~> 0.5.2'
# Structured logging
gem 'lograge', '~> 0.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1a14028230f..258f439053c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -443,7 +443,7 @@ GEM
numerizer (~> 0.2)
gitlab-dangerfiles (1.1.0)
danger-gitlab
- gitlab-experiment (0.5.1)
+ gitlab-experiment (0.5.2)
activesupport (>= 3.0)
scientist (~> 1.6, >= 1.6.0)
gitlab-fog-azure-rm (1.0.1)
@@ -1424,7 +1424,7 @@ DEPENDENCIES
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 1.1.0)
- gitlab-experiment (~> 0.5.1)
+ gitlab-experiment (~> 0.5.2)
gitlab-fog-azure-rm (~> 1.0.1)
gitlab-fog-google (~> 1.13)
gitlab-labkit (~> 0.16.2)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d7c7d79f2dc..654703d2353 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -110,6 +110,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
@file_by_file_default = current_user&.view_diffs_file_by_file
@coverage_path = coverage_reports_project_merge_request_path(@project, @merge_request, format: :json) if @merge_request.has_coverage_reports?
+ @update_current_user_path = expose_path(api_v4_user_preferences_path)
@endpoint_metadata_url = endpoint_metadata_url(@project, @merge_request)
set_pipeline_variables
diff --git a/app/services/user_preferences/update_service.rb b/app/services/user_preferences/update_service.rb
new file mode 100644
index 00000000000..a1ee35d4580
--- /dev/null
+++ b/app/services/user_preferences/update_service.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module UserPreferences
+ class UpdateService < BaseService
+ def initialize(user, params = {})
+ @preferences = user.user_preference
+ @params = params.to_h.dup.with_indifferent_access
+ end
+
+ def execute
+ if @preferences.update(@params)
+ ServiceResponse.success(
+ message: 'Preference was updated',
+ payload: { preferences: @preferences })
+ else
+ ServiceResponse.error(message: 'Could not update preference')
+ end
+ end
+ end
+end
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 24594b732a4..e266c79df9c 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -88,6 +88,7 @@
endpoint_coverage: @coverage_path,
help_page_path: suggest_changes_help_path,
current_user_data: @current_user_data,
+ update_current_user_path: @update_current_user_path,
project_path: project_path(@merge_request.project),
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
is_fluid_layout: fluid_layout.to_s,
diff --git a/changelogs/unreleased/284116-sync-single-file-mode-user-preference.yml b/changelogs/unreleased/284116-sync-single-file-mode-user-preference.yml
new file mode 100644
index 00000000000..32cfc95f594
--- /dev/null
+++ b/changelogs/unreleased/284116-sync-single-file-mode-user-preference.yml
@@ -0,0 +1,5 @@
+---
+title: Create UserPreferences API
+merge_request: 55033
+author:
+type: added
diff --git a/doc/api/users.md b/doc/api/users.md
index b8917f3e215..a613add10bf 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -677,6 +677,28 @@ Example response:
}
```
+## User preference modification
+
+Update the current user's preferences.
+
+```plaintext
+PUT /user/preferences
+```
+
+```json
+{
+ "id": 1,
+ "user_id": 1
+ "view_diffs_file_by_file": true
+}
+```
+
+Parameters:
+
+| Attribute | Required | Description |
+| :--------------------------- | :------- | :---------------------------------------------------------- |
+| `view_diffs_file_by_file` | Yes | Flag indicating the user sees only one file diff per page. |
+
## Set user status
Set the status of the current user.
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index e0dd1d37e74..45d44582607 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -22,40 +22,50 @@ of the configured mechanisms.
## Supported Providers
-This is a list of the current supported OmniAuth providers. Before proceeding
-on each provider's documentation, make sure to first read this document as it
-contains some settings that are common for all providers.
-
-- [GitHub](github.md)
-- [Bitbucket](bitbucket.md)
-- [GitLab.com](gitlab.md)
-- [Google](google.md)
-- [Facebook](facebook.md)
-- [Twitter](twitter.md)
-- [Shibboleth](shibboleth.md)
-- [SAML](saml.md)
-- [Crowd](../administration/auth/crowd.md)
-- [Azure](azure.md)
-- [Auth0](auth0.md)
-- [Authentiq](../administration/auth/authentiq.md)
-- [OAuth2Generic](oauth2_generic.md)
-- [JWT](../administration/auth/jwt.md)
-- [OpenID Connect](../administration/auth/oidc.md)
-- [Salesforce](salesforce.md)
-- [AWS Cognito](../administration/auth/cognito.md)
+This is a list of the current supported OmniAuth providers. Before proceeding on each provider's documentation,
+make sure to first read this document as it contains some settings that are common for all providers.
+
+|Provider documentation |OmniAuth provider name |
+|-----------------------------------------------------------------|--------------------------|
+|[Atlassian Crowd](../administration/auth/crowd.md) |`crowd` |
+|[Atlassian](../administration/auth/atlassian.md) |`atlassian_oauth2` |
+|[Auth0](auth0.md) |`auth0` |
+|[Authentiq](../administration/auth/authentiq.md) |`authentiq` |
+|[AWS Cognito](../administration/auth/cognito.md) |`cognito` |
+|[Azure v2](azure.md#microsoft-azure-oauth2-omniauth-provider-v2) |`azure_activedirectory_v2`|
+|[Azure v1](azure.md) |`azure_oauth2` |
+|[Bitbucket Cloud](bitbucket.md) |`bitbucket` |
+|[CAS](cas.md) |`cas3` |
+|[Facebook](facebook.md) |`facebook` |
+|[Generic OAuth2](oauth2_generic.md) |`oauth2_generic` |
+|[GitHub](github.md) |`github` |
+|[GitLab.com](gitlab.md) |`gitlab` |
+|[Google](google.md) |`google_oauth2` |
+|[JWT](../administration/auth/jwt.md) |`jwt` |
+|[Kerberos](kerberos.md) |`kerberos` |
+|[OpenID Connect](../administration/auth/oidc.md) |`openid_connect` |
+|[Salesforce](salesforce.md) |`salesforce` |
+|[SAML](saml.md) |`saml` |
+|[Shibboleth](shibboleth.md) |`shibboleth` |
+|[Twitter](twitter.md) |`twitter` |
## Initial OmniAuth Configuration
-Before configuring individual OmniAuth providers there are a few global settings
-that are in common for all providers that we need to consider.
+The OmniAuth provider names from the table above are needed to configure a few global settings that are in common for all providers.
NOTE:
Starting from GitLab 11.4, OmniAuth is enabled by default. If you're using an
earlier version, you must explicitly enable it.
-- `allow_single_sign_on` allows you to specify the providers you want to allow to
- automatically create an account. It defaults to `false`. If `false` users must
- be created manually or they can't sign in by using OmniAuth.
+- `allow_single_sign_on` allows you to specify the providers that automatically
+ create a GitLab account. For example, if you wish to enable Azure (v2) and Google,
+ in Omnibus, specify a list of provider names:
+
+ ```ruby
+ gitlab_rails['omniauth_allow_single_sign_on'] = ['azure_activedirectory_v2', 'google_oauth2']
+ ```
+
+ The value defaults to `false`. If `false` users must be created manually, or they can't sign in by using OmniAuth.
- `auto_link_ldap_user` can be used if you have [LDAP / ActiveDirectory](../administration/auth/ldap/index.md)
integration enabled. It defaults to `false`. When enabled, users automatically
created through an OmniAuth provider have their LDAP identity created in GitLab as well.
@@ -325,20 +335,20 @@ You can add the `auto_sign_in_with_provider` setting to your GitLab
configuration to redirect login requests to your OmniAuth provider for
authentication. This removes the need to click a button before actually signing in.
-For example, when using the Azure integration, set the following to enable auto
+For example, when using the [Azure v2 integration](azure.md#microsoft-azure-oauth2-omniauth-provider-v2), set the following to enable auto
sign-in:
For Omnibus package:
```ruby
-gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'azure_oauth2'
+gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'azure_activedirectory_v2'
```
For installations from source:
```yaml
omniauth:
- auto_sign_in_with_provider: azure_oauth2
+ auto_sign_in_with_provider: azure_activedirectory_v2
```
Keep in mind that every sign-in attempt is redirected to the OmniAuth
diff --git a/lib/api/entities/user_preferences.rb b/lib/api/entities/user_preferences.rb
new file mode 100644
index 00000000000..7a6df9b6c59
--- /dev/null
+++ b/lib/api/entities/user_preferences.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class UserPreferences < Grape::Entity
+ expose :id, :user_id, :view_diffs_file_by_file
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index b985dce3b87..078ba7542a3 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -996,6 +996,29 @@ module API
present paginate(current_user.emails), with: Entities::Email
end
+ desc "Update the current user's preferences" do
+ success Entities::UserPreferences
+ detail 'This feature was introduced in GitLab 13.10.'
+ end
+ params do
+ requires :view_diffs_file_by_file, type: Boolean, desc: 'Flag indicating the user sees only one file diff per page'
+ end
+ put "preferences", feature_category: :users do
+ authenticate!
+
+ preferences = current_user.user_preference
+
+ attrs = declared_params(include_missing: false)
+
+ service = ::UserPreferences::UpdateService.new(current_user, attrs).execute
+
+ if service.success?
+ present preferences, with: Entities::UserPreferences
+ else
+ render_api_error!('400 Bad Request', 400)
+ end
+ end
+
desc 'Get a single email address owned by the currently authenticated user' do
success Entities::Email
end
diff --git a/package.json b/package.json
index 8b3758a7bf7..b9801833a2d 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.185.0",
"@gitlab/tributejs": "1.0.0",
- "@gitlab/ui": "28.15.0",
+ "@gitlab/ui": "28.18.2",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4",
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index f8e5dc96489..992b76ed24a 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -74,8 +74,9 @@ RSpec.describe Projects::IssuesController do
end
it 'assigns the candidate experience and tracks the event' do
- expect(experiment(:null_hypothesis)).to track('index').on_any_instance.for(:candidate)
+ expect(experiment(:null_hypothesis)).to track('index').for(:candidate)
.with_context(project: project)
+ .on_next_instance
get :index, params: { namespace_id: project.namespace, project_id: project }
end
@@ -218,10 +219,10 @@ RSpec.describe Projects::IssuesController do
end
it 'assigns the candidate experience and tracks the event' do
- expect(experiment(:invite_member_link)).to track(:view, property: project.root_ancestor.id.to_s)
- .on_any_instance
- .for(:invite_member_link)
- .with_context(namespace: project.root_ancestor)
+ expect(experiment(:invite_members_in_comment)).to track(:view, property: project.root_ancestor.id.to_s)
+ .for(:invite_member_link)
+ .with_context(namespace: project.root_ancestor)
+ .on_next_instance
get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
end
@@ -229,7 +230,7 @@ RSpec.describe Projects::IssuesController do
context 'when user can not invite' do
it 'does not track the event' do
- expect(experiment(:invite_member_link)).not_to track(:view)
+ expect(experiment(:invite_members_in_comment)).not_to track(:view)
get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 5d26597c29d..d452f69d6fb 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -48,10 +48,10 @@ RSpec.describe Projects::MergeRequestsController do
end
it 'assigns the candidate experience and tracks the event' do
- expect(experiment(:invite_member_link)).to track(:view, property: project.root_ancestor.id.to_s)
- .on_any_instance
- .for(:invite_member_link)
- .with_context(namespace: project.root_ancestor)
+ expect(experiment(:invite_members_in_comment)).to track(:view, property: project.root_ancestor.id.to_s)
+ .for(:invite_member_link)
+ .with_context(namespace: project.root_ancestor)
+ .on_next_instance
go
end
@@ -59,7 +59,7 @@ RSpec.describe Projects::MergeRequestsController do
context 'when user can not invite' do
it 'does not track the event' do
- expect(experiment(:invite_member_link)).not_to track(:view)
+ expect(experiment(:invite_members_in_comment)).not_to track(:view)
go
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 4e8172edd3e..f04f840ab2b 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -221,22 +221,16 @@ RSpec.describe ProjectsController do
allow(controller).to receive(:record_experiment_user)
end
- context 'when user can push to default branch' do
+ context 'when user can push to default branch', :experiment do
let(:user) { empty_project.owner }
- it 'creates an "view_project_show" experiment tracking event', :snowplow do
- allow_next_instance_of(ApplicationExperiment) do |e|
- allow(e).to receive(:should_track?).and_return(true)
- end
+ it 'creates an "view_project_show" experiment tracking event' do
+ expect(experiment(:empty_repo_upload)).to track(
+ :view_project_show,
+ property: 'empty'
+ ).on_next_instance
get :show, params: { namespace_id: empty_project.namespace, id: empty_project }
-
- expect_snowplow_event(
- category: 'empty_repo_upload',
- action: 'view_project_show',
- property: 'empty',
- context: [{ schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/1-0-0', data: anything }]
- )
end
end
@@ -449,7 +443,7 @@ RSpec.describe ProjectsController do
:created,
property: 'blank',
value: 1
- ).on_any_instance.with_context(actor: user)
+ ).with_context(actor: user).on_next_instance
post :create, params: { project: project_params }
end
diff --git a/spec/experiments/new_project_readme_experiment_spec.rb b/spec/experiments/new_project_readme_experiment_spec.rb
index 17e28cf6e7f..87446394bff 100644
--- a/spec/experiments/new_project_readme_experiment_spec.rb
+++ b/spec/experiments/new_project_readme_experiment_spec.rb
@@ -7,10 +7,6 @@ RSpec.describe NewProjectReadmeExperiment, :experiment do
let(:actor) { User.new(id: 42, created_at: Time.current) }
- before do
- stub_experiments(new_project_readme: :control)
- end
-
describe "exclusions" do
let(:threshold) { described_class::MAX_ACCOUNT_AGE }
diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
index 43d8fe28893..5e5365eef30 100644
--- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
@@ -1,4 +1,5 @@
-import { GlAlert, GlButton } from '@gitlab/ui';
+import { GlAlert } from '@gitlab/ui';
+import { fireEvent, within } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils';
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
@@ -7,8 +8,10 @@ import { generateResponse, mockPipelineResponse } from '../graph/mock_data';
describe('links layer component', () => {
let wrapper;
+ const withinComponent = () => within(wrapper.element);
const findAlert = () => wrapper.find(GlAlert);
- const findShowAnyways = () => findAlert().find(GlButton);
+ const findShowAnyways = () =>
+ withinComponent().getByText(wrapper.vm.$options.i18n.showLinksAnyways);
const findLinksInner = () => wrapper.find(LinksInner);
const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo');
@@ -103,13 +106,13 @@ describe('links layer component', () => {
});
it('renders the disable button', () => {
- expect(findShowAnyways().exists()).toBe(true);
- expect(findShowAnyways().text()).toBe(wrapper.vm.$options.i18n.showLinksAnyways);
+ expect(findShowAnyways()).not.toBe(null);
});
it('shows links when override is clicked', async () => {
expect(findLinksInner().exists()).toBe(false);
- await findShowAnyways().trigger('click');
+ fireEvent(findShowAnyways(), new MouseEvent('click', { bubbles: true }));
+ await wrapper.vm.$nextTick();
expect(findLinksInner().exists()).toBe(true);
});
});
diff --git a/spec/frontend/vue_alerts_spec.js b/spec/frontend/vue_alerts_spec.js
index 16eb2d44e4d..05b73415544 100644
--- a/spec/frontend/vue_alerts_spec.js
+++ b/spec/frontend/vue_alerts_spec.js
@@ -42,15 +42,17 @@ describe('VueAlerts', () => {
const findJsHooks = () => document.querySelectorAll('.js-vue-alert');
const findAlerts = () => document.querySelectorAll('.gl-alert');
- const findAlertDismiss = (alert) => alert.querySelector('.gl-alert-dismiss');
+ const findAlertDismiss = (alert) => alert.querySelector('.gl-dismiss-btn');
const serializeAlert = (alert) => ({
title: alert.querySelector('.gl-alert-title').textContent.trim(),
html: alert.querySelector('.gl-alert-body div').innerHTML,
- dismissible: Boolean(alert.querySelector('.gl-alert-dismiss')),
+ dismissible: Boolean(alert.querySelector('.gl-dismiss-btn')),
primaryButtonText: alert.querySelector('.gl-alert-action').textContent.trim(),
primaryButtonLink: alert.querySelector('.gl-alert-action').href,
- variant: [...alert.classList].find((x) => x.match('gl-alert-')).replace('gl-alert-', ''),
+ variant: [...alert.classList]
+ .find((x) => x.match(/gl-alert-(?!not-dismissible)/))
+ .replace('gl-alert-', ''),
});
it('starts with only JsHooks', () => {
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 4d65a312832..94a081ae0c9 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -799,14 +799,14 @@ RSpec.describe Notify do
is_expected.to have_link('Join now', href: invite_url(project_member.invite_token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE))
end
- it 'contains invite link for the avatar', :experiment do
+ it 'contains invite link for the avatar' do
stub_experiments('members/invite_email': :avatar)
is_expected.not_to have_content('You are invited!')
is_expected.not_to have_body_text 'What is a GitLab'
end
- it 'contains invite link for the avatar', :experiment do
+ it 'contains invite link for the avatar' do
stub_experiments('members/invite_email': :permission_info)
is_expected.not_to have_content('You are invited!')
diff --git a/spec/requests/api/users_preferences_spec.rb b/spec/requests/api/users_preferences_spec.rb
new file mode 100644
index 00000000000..db03786ed2a
--- /dev/null
+++ b/spec/requests/api/users_preferences_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Users do
+ let_it_be(:user) { create(:user) }
+
+ describe 'PUT /user/preferences/' do
+ context "with correct attributes and a logged in user" do
+ it 'returns a success status and the value has been changed' do
+ put api("/user/preferences", user), params: { view_diffs_file_by_file: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['view_diffs_file_by_file']).to eq(true)
+ expect(user.reload.view_diffs_file_by_file).to be_truthy
+ end
+ end
+
+ context "missing a preference" do
+ it 'returns a bad request status' do
+ put api("/user/preferences", user), params: {}
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context "without a logged in user" do
+ it 'returns an unauthorized status' do
+ put api("/user/preferences"), params: { view_diffs_file_by_file: true }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context "with an unsupported preference" do
+ it 'returns a bad parameter' do
+ put api("/user/preferences", user), params: { jawn: true }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context "with an unsupported value" do
+ it 'returns a bad parameter' do
+ put api("/user/preferences", user), params: { view_diffs_file_by_file: 3 }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context "with an update service failure" do
+ it 'returns a bad request' do
+ bad_service = double("Failed Service", success?: false)
+
+ allow_next_instance_of(::UserPreferences::UpdateService) do |instance|
+ allow(instance).to receive(:execute).and_return(bad_service)
+ end
+
+ put api("/user/preferences", user), params: { view_diffs_file_by_file: true }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+end
diff --git a/spec/services/user_preferences/update_service_spec.rb b/spec/services/user_preferences/update_service_spec.rb
new file mode 100644
index 00000000000..59089a4a7af
--- /dev/null
+++ b/spec/services/user_preferences/update_service_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe UserPreferences::UpdateService do
+ let(:user) { create(:user) }
+ let(:params) { { view_diffs_file_by_file: false } }
+
+ describe '#execute' do
+ subject(:service) { described_class.new(user, params) }
+
+ context 'successfully updating the record' do
+ it 'updates the preference and returns a success' do
+ result = service.execute
+
+ expect(result.status).to eq(:success)
+ expect(result.payload[:preferences].view_diffs_file_by_file).to eq(params[:view_diffs_file_by_file])
+ end
+ end
+
+ context 'unsuccessfully updating the record' do
+ before do
+ allow(user.user_preference).to receive(:update).and_return(false)
+ end
+
+ it 'returns an error' do
+ result = service.execute
+
+ expect(result.status).to eq(:error)
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index ff16d25fedf..44d2c0d0ee4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -907,10 +907,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
-"@gitlab/ui@28.15.0":
- version "28.15.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-28.15.0.tgz#e78a1c0724c7cc8880fcff8161e529ca7bcaf6e8"
- integrity sha512-muz1tX3nQmu9dMv7GbTNIkWkEwYhvnLPhtwtnrt8eyRGQ0zIUWLEzdoSiwvMNLAqT2JB8kxahoavR5iSFAYtXA==
+"@gitlab/ui@28.18.2":
+ version "28.18.2"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-28.18.2.tgz#b840a44d4f3094e838d2f7409ca8b578064e2a6f"
+ integrity sha512-KRd/gtZj0885C0cGZiEK4jV5Cdlss62z4d0ii/p45Q6KjwmAC9au946a8pgbtBMCvDmybGxvsMmH4U2MmjNDvQ==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"