Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
fee1014807
commit
65be6f9dd4
21 changed files with 259 additions and 74 deletions
2
Gemfile
2
Gemfile
|
@ -488,7 +488,7 @@ gem 'flipper', '~> 0.17.1'
|
||||||
gem 'flipper-active_record', '~> 0.17.1'
|
gem 'flipper-active_record', '~> 0.17.1'
|
||||||
gem 'flipper-active_support_cache_store', '~> 0.17.1'
|
gem 'flipper-active_support_cache_store', '~> 0.17.1'
|
||||||
gem 'unleash', '~> 0.1.5'
|
gem 'unleash', '~> 0.1.5'
|
||||||
gem 'gitlab-experiment', '~> 0.5.1'
|
gem 'gitlab-experiment', '~> 0.5.2'
|
||||||
|
|
||||||
# Structured logging
|
# Structured logging
|
||||||
gem 'lograge', '~> 0.5'
|
gem 'lograge', '~> 0.5'
|
||||||
|
|
|
@ -443,7 +443,7 @@ GEM
|
||||||
numerizer (~> 0.2)
|
numerizer (~> 0.2)
|
||||||
gitlab-dangerfiles (1.1.0)
|
gitlab-dangerfiles (1.1.0)
|
||||||
danger-gitlab
|
danger-gitlab
|
||||||
gitlab-experiment (0.5.1)
|
gitlab-experiment (0.5.2)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
scientist (~> 1.6, >= 1.6.0)
|
scientist (~> 1.6, >= 1.6.0)
|
||||||
gitlab-fog-azure-rm (1.0.1)
|
gitlab-fog-azure-rm (1.0.1)
|
||||||
|
@ -1424,7 +1424,7 @@ DEPENDENCIES
|
||||||
github-markup (~> 1.7.0)
|
github-markup (~> 1.7.0)
|
||||||
gitlab-chronic (~> 0.10.5)
|
gitlab-chronic (~> 0.10.5)
|
||||||
gitlab-dangerfiles (~> 1.1.0)
|
gitlab-dangerfiles (~> 1.1.0)
|
||||||
gitlab-experiment (~> 0.5.1)
|
gitlab-experiment (~> 0.5.2)
|
||||||
gitlab-fog-azure-rm (~> 1.0.1)
|
gitlab-fog-azure-rm (~> 1.0.1)
|
||||||
gitlab-fog-google (~> 1.13)
|
gitlab-fog-google (~> 1.13)
|
||||||
gitlab-labkit (~> 0.16.2)
|
gitlab-labkit (~> 0.16.2)
|
||||||
|
|
|
@ -110,6 +110,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
||||||
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
|
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
|
||||||
@file_by_file_default = current_user&.view_diffs_file_by_file
|
@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?
|
@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)
|
@endpoint_metadata_url = endpoint_metadata_url(@project, @merge_request)
|
||||||
|
|
||||||
set_pipeline_variables
|
set_pipeline_variables
|
||||||
|
|
20
app/services/user_preferences/update_service.rb
Normal file
20
app/services/user_preferences/update_service.rb
Normal file
|
@ -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
|
|
@ -88,6 +88,7 @@
|
||||||
endpoint_coverage: @coverage_path,
|
endpoint_coverage: @coverage_path,
|
||||||
help_page_path: suggest_changes_help_path,
|
help_page_path: suggest_changes_help_path,
|
||||||
current_user_data: @current_user_data,
|
current_user_data: @current_user_data,
|
||||||
|
update_current_user_path: @update_current_user_path,
|
||||||
project_path: project_path(@merge_request.project),
|
project_path: project_path(@merge_request.project),
|
||||||
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
|
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
|
||||||
is_fluid_layout: fluid_layout.to_s,
|
is_fluid_layout: fluid_layout.to_s,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Create UserPreferences API
|
||||||
|
merge_request: 55033
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -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 user status
|
||||||
|
|
||||||
Set the status of the current user.
|
Set the status of the current user.
|
||||||
|
|
|
@ -22,40 +22,50 @@ of the configured mechanisms.
|
||||||
|
|
||||||
## Supported Providers
|
## Supported Providers
|
||||||
|
|
||||||
This is a list of the current supported OmniAuth providers. Before proceeding
|
This is a list of the current supported OmniAuth providers. Before proceeding on each provider's documentation,
|
||||||
on each provider's documentation, make sure to first read this document as it
|
make sure to first read this document as it contains some settings that are common for all providers.
|
||||||
contains some settings that are common for all providers.
|
|
||||||
|
|
||||||
- [GitHub](github.md)
|
|Provider documentation |OmniAuth provider name |
|
||||||
- [Bitbucket](bitbucket.md)
|
|-----------------------------------------------------------------|--------------------------|
|
||||||
- [GitLab.com](gitlab.md)
|
|[Atlassian Crowd](../administration/auth/crowd.md) |`crowd` |
|
||||||
- [Google](google.md)
|
|[Atlassian](../administration/auth/atlassian.md) |`atlassian_oauth2` |
|
||||||
- [Facebook](facebook.md)
|
|[Auth0](auth0.md) |`auth0` |
|
||||||
- [Twitter](twitter.md)
|
|[Authentiq](../administration/auth/authentiq.md) |`authentiq` |
|
||||||
- [Shibboleth](shibboleth.md)
|
|[AWS Cognito](../administration/auth/cognito.md) |`cognito` |
|
||||||
- [SAML](saml.md)
|
|[Azure v2](azure.md#microsoft-azure-oauth2-omniauth-provider-v2) |`azure_activedirectory_v2`|
|
||||||
- [Crowd](../administration/auth/crowd.md)
|
|[Azure v1](azure.md) |`azure_oauth2` |
|
||||||
- [Azure](azure.md)
|
|[Bitbucket Cloud](bitbucket.md) |`bitbucket` |
|
||||||
- [Auth0](auth0.md)
|
|[CAS](cas.md) |`cas3` |
|
||||||
- [Authentiq](../administration/auth/authentiq.md)
|
|[Facebook](facebook.md) |`facebook` |
|
||||||
- [OAuth2Generic](oauth2_generic.md)
|
|[Generic OAuth2](oauth2_generic.md) |`oauth2_generic` |
|
||||||
- [JWT](../administration/auth/jwt.md)
|
|[GitHub](github.md) |`github` |
|
||||||
- [OpenID Connect](../administration/auth/oidc.md)
|
|[GitLab.com](gitlab.md) |`gitlab` |
|
||||||
- [Salesforce](salesforce.md)
|
|[Google](google.md) |`google_oauth2` |
|
||||||
- [AWS Cognito](../administration/auth/cognito.md)
|
|[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
|
## Initial OmniAuth Configuration
|
||||||
|
|
||||||
Before configuring individual OmniAuth providers there are a few global settings
|
The OmniAuth provider names from the table above are needed to configure a few global settings that are in common for all providers.
|
||||||
that are in common for all providers that we need to consider.
|
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
Starting from GitLab 11.4, OmniAuth is enabled by default. If you're using an
|
Starting from GitLab 11.4, OmniAuth is enabled by default. If you're using an
|
||||||
earlier version, you must explicitly enable it.
|
earlier version, you must explicitly enable it.
|
||||||
|
|
||||||
- `allow_single_sign_on` allows you to specify the providers you want to allow to
|
- `allow_single_sign_on` allows you to specify the providers that automatically
|
||||||
automatically create an account. It defaults to `false`. If `false` users must
|
create a GitLab account. For example, if you wish to enable Azure (v2) and Google,
|
||||||
be created manually or they can't sign in by using OmniAuth.
|
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)
|
- `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
|
integration enabled. It defaults to `false`. When enabled, users automatically
|
||||||
created through an OmniAuth provider have their LDAP identity created in GitLab as well.
|
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
|
configuration to redirect login requests to your OmniAuth provider for
|
||||||
authentication. This removes the need to click a button before actually signing in.
|
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:
|
sign-in:
|
||||||
|
|
||||||
For Omnibus package:
|
For Omnibus package:
|
||||||
|
|
||||||
```ruby
|
```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:
|
For installations from source:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
omniauth:
|
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
|
Keep in mind that every sign-in attempt is redirected to the OmniAuth
|
||||||
|
|
9
lib/api/entities/user_preferences.rb
Normal file
9
lib/api/entities/user_preferences.rb
Normal file
|
@ -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
|
|
@ -996,6 +996,29 @@ module API
|
||||||
present paginate(current_user.emails), with: Entities::Email
|
present paginate(current_user.emails), with: Entities::Email
|
||||||
end
|
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
|
desc 'Get a single email address owned by the currently authenticated user' do
|
||||||
success Entities::Email
|
success Entities::Email
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
"@gitlab/favicon-overlay": "2.0.0",
|
"@gitlab/favicon-overlay": "2.0.0",
|
||||||
"@gitlab/svgs": "1.185.0",
|
"@gitlab/svgs": "1.185.0",
|
||||||
"@gitlab/tributejs": "1.0.0",
|
"@gitlab/tributejs": "1.0.0",
|
||||||
"@gitlab/ui": "28.15.0",
|
"@gitlab/ui": "28.18.2",
|
||||||
"@gitlab/visual-review-tools": "1.6.1",
|
"@gitlab/visual-review-tools": "1.6.1",
|
||||||
"@rails/actioncable": "^6.0.3-4",
|
"@rails/actioncable": "^6.0.3-4",
|
||||||
"@rails/ujs": "^6.0.3-4",
|
"@rails/ujs": "^6.0.3-4",
|
||||||
|
|
|
@ -74,8 +74,9 @@ RSpec.describe Projects::IssuesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'assigns the candidate experience and tracks the event' do
|
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)
|
.with_context(project: project)
|
||||||
|
.on_next_instance
|
||||||
|
|
||||||
get :index, params: { namespace_id: project.namespace, project_id: project }
|
get :index, params: { namespace_id: project.namespace, project_id: project }
|
||||||
end
|
end
|
||||||
|
@ -218,10 +219,10 @@ RSpec.describe Projects::IssuesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'assigns the candidate experience and tracks the event' do
|
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)
|
expect(experiment(:invite_members_in_comment)).to track(:view, property: project.root_ancestor.id.to_s)
|
||||||
.on_any_instance
|
|
||||||
.for(:invite_member_link)
|
.for(:invite_member_link)
|
||||||
.with_context(namespace: project.root_ancestor)
|
.with_context(namespace: project.root_ancestor)
|
||||||
|
.on_next_instance
|
||||||
|
|
||||||
get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
|
get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
|
||||||
end
|
end
|
||||||
|
@ -229,7 +230,7 @@ RSpec.describe Projects::IssuesController do
|
||||||
|
|
||||||
context 'when user can not invite' do
|
context 'when user can not invite' do
|
||||||
it 'does not track the event' 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 }
|
get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,10 +48,10 @@ RSpec.describe Projects::MergeRequestsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'assigns the candidate experience and tracks the event' do
|
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)
|
expect(experiment(:invite_members_in_comment)).to track(:view, property: project.root_ancestor.id.to_s)
|
||||||
.on_any_instance
|
|
||||||
.for(:invite_member_link)
|
.for(:invite_member_link)
|
||||||
.with_context(namespace: project.root_ancestor)
|
.with_context(namespace: project.root_ancestor)
|
||||||
|
.on_next_instance
|
||||||
|
|
||||||
go
|
go
|
||||||
end
|
end
|
||||||
|
@ -59,7 +59,7 @@ RSpec.describe Projects::MergeRequestsController do
|
||||||
|
|
||||||
context 'when user can not invite' do
|
context 'when user can not invite' do
|
||||||
it 'does not track the event' 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
|
go
|
||||||
end
|
end
|
||||||
|
|
|
@ -221,22 +221,16 @@ RSpec.describe ProjectsController do
|
||||||
allow(controller).to receive(:record_experiment_user)
|
allow(controller).to receive(:record_experiment_user)
|
||||||
end
|
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 }
|
let(:user) { empty_project.owner }
|
||||||
|
|
||||||
it 'creates an "view_project_show" experiment tracking event', :snowplow do
|
it 'creates an "view_project_show" experiment tracking event' do
|
||||||
allow_next_instance_of(ApplicationExperiment) do |e|
|
expect(experiment(:empty_repo_upload)).to track(
|
||||||
allow(e).to receive(:should_track?).and_return(true)
|
:view_project_show,
|
||||||
end
|
property: 'empty'
|
||||||
|
).on_next_instance
|
||||||
|
|
||||||
get :show, params: { namespace_id: empty_project.namespace, id: empty_project }
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -449,7 +443,7 @@ RSpec.describe ProjectsController do
|
||||||
:created,
|
:created,
|
||||||
property: 'blank',
|
property: 'blank',
|
||||||
value: 1
|
value: 1
|
||||||
).on_any_instance.with_context(actor: user)
|
).with_context(actor: user).on_next_instance
|
||||||
|
|
||||||
post :create, params: { project: project_params }
|
post :create, params: { project: project_params }
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,10 +7,6 @@ RSpec.describe NewProjectReadmeExperiment, :experiment do
|
||||||
|
|
||||||
let(:actor) { User.new(id: 42, created_at: Time.current) }
|
let(:actor) { User.new(id: 42, created_at: Time.current) }
|
||||||
|
|
||||||
before do
|
|
||||||
stub_experiments(new_project_readme: :control)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "exclusions" do
|
describe "exclusions" do
|
||||||
let(:threshold) { described_class::MAX_ACCOUNT_AGE }
|
let(:threshold) { described_class::MAX_ACCOUNT_AGE }
|
||||||
|
|
||||||
|
|
|
@ -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 { mount, shallowMount } from '@vue/test-utils';
|
||||||
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
|
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
|
||||||
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.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', () => {
|
describe('links layer component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
const withinComponent = () => within(wrapper.element);
|
||||||
const findAlert = () => wrapper.find(GlAlert);
|
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 findLinksInner = () => wrapper.find(LinksInner);
|
||||||
|
|
||||||
const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo');
|
const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo');
|
||||||
|
@ -103,13 +106,13 @@ describe('links layer component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the disable button', () => {
|
it('renders the disable button', () => {
|
||||||
expect(findShowAnyways().exists()).toBe(true);
|
expect(findShowAnyways()).not.toBe(null);
|
||||||
expect(findShowAnyways().text()).toBe(wrapper.vm.$options.i18n.showLinksAnyways);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows links when override is clicked', async () => {
|
it('shows links when override is clicked', async () => {
|
||||||
expect(findLinksInner().exists()).toBe(false);
|
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);
|
expect(findLinksInner().exists()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,15 +42,17 @@ describe('VueAlerts', () => {
|
||||||
|
|
||||||
const findJsHooks = () => document.querySelectorAll('.js-vue-alert');
|
const findJsHooks = () => document.querySelectorAll('.js-vue-alert');
|
||||||
const findAlerts = () => document.querySelectorAll('.gl-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) => ({
|
const serializeAlert = (alert) => ({
|
||||||
title: alert.querySelector('.gl-alert-title').textContent.trim(),
|
title: alert.querySelector('.gl-alert-title').textContent.trim(),
|
||||||
html: alert.querySelector('.gl-alert-body div').innerHTML,
|
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(),
|
primaryButtonText: alert.querySelector('.gl-alert-action').textContent.trim(),
|
||||||
primaryButtonLink: alert.querySelector('.gl-alert-action').href,
|
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', () => {
|
it('starts with only JsHooks', () => {
|
||||||
|
|
|
@ -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))
|
is_expected.to have_link('Join now', href: invite_url(project_member.invite_token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'contains invite link for the avatar', :experiment do
|
it 'contains invite link for the avatar' do
|
||||||
stub_experiments('members/invite_email': :avatar)
|
stub_experiments('members/invite_email': :avatar)
|
||||||
|
|
||||||
is_expected.not_to have_content('You are invited!')
|
is_expected.not_to have_content('You are invited!')
|
||||||
is_expected.not_to have_body_text 'What is a GitLab'
|
is_expected.not_to have_body_text 'What is a GitLab'
|
||||||
end
|
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)
|
stub_experiments('members/invite_email': :permission_info)
|
||||||
|
|
||||||
is_expected.not_to have_content('You are invited!')
|
is_expected.not_to have_content('You are invited!')
|
||||||
|
|
65
spec/requests/api/users_preferences_spec.rb
Normal file
65
spec/requests/api/users_preferences_spec.rb
Normal file
|
@ -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
|
33
spec/services/user_preferences/update_service_spec.rb
Normal file
33
spec/services/user_preferences/update_service_spec.rb
Normal file
|
@ -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
|
|
@ -907,10 +907,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
||||||
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
||||||
|
|
||||||
"@gitlab/ui@28.15.0":
|
"@gitlab/ui@28.18.2":
|
||||||
version "28.15.0"
|
version "28.18.2"
|
||||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-28.15.0.tgz#e78a1c0724c7cc8880fcff8161e529ca7bcaf6e8"
|
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-28.18.2.tgz#b840a44d4f3094e838d2f7409ca8b578064e2a6f"
|
||||||
integrity sha512-muz1tX3nQmu9dMv7GbTNIkWkEwYhvnLPhtwtnrt8eyRGQ0zIUWLEzdoSiwvMNLAqT2JB8kxahoavR5iSFAYtXA==
|
integrity sha512-KRd/gtZj0885C0cGZiEK4jV5Cdlss62z4d0ii/p45Q6KjwmAC9au946a8pgbtBMCvDmybGxvsMmH4U2MmjNDvQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/standalone" "^7.0.0"
|
"@babel/standalone" "^7.0.0"
|
||||||
"@gitlab/vue-toasted" "^1.3.0"
|
"@gitlab/vue-toasted" "^1.3.0"
|
||||||
|
|
Loading…
Reference in a new issue