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. +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) +|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"