Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-02 21:09:22 +00:00
parent fee1014807
commit 65be6f9dd4
21 changed files with 259 additions and 74 deletions

View file

@ -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'

View file

@ -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)

View file

@ -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

View 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

View file

@ -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,

View file

@ -0,0 +1,5 @@
---
title: Create UserPreferences API
merge_request: 55033
author:
type: added

View file

@ -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.

View file

@ -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

View 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

View file

@ -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

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 }

View file

@ -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);
}); });
}); });

View file

@ -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', () => {

View file

@ -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!')

View 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

View 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

View file

@ -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"