diff options
-rw-r--r-- | .gitlab/ci/review.gitlab-ci.yml | 2 | ||||
-rw-r--r-- | app/assets/javascripts/issuables_list/components/issuables_list_app.vue | 21 | ||||
-rw-r--r-- | app/controllers/concerns/spammable_actions.rb | 2 | ||||
-rw-r--r-- | app/helpers/labels_helper.rb | 24 | ||||
-rw-r--r-- | app/models/service.rb | 6 | ||||
-rw-r--r-- | app/services/concerns/akismet_methods.rb | 10 | ||||
-rw-r--r-- | app/services/spam/ham_service.rb | 2 | ||||
-rw-r--r-- | app/services/spam/mark_as_spam_service.rb | 14 | ||||
-rw-r--r-- | app/services/spam/spam_check_service.rb | 22 | ||||
-rw-r--r-- | app/views/ide/_show.html.haml | 2 | ||||
-rw-r--r-- | changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-ide.yml | 5 | ||||
-rw-r--r-- | doc/development/documentation/site_architecture/index.md | 44 | ||||
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | spec/controllers/projects/issues_controller_spec.rb | 16 | ||||
-rw-r--r-- | spec/frontend/issuables_list/components/issuables_list_app_spec.js | 141 | ||||
-rw-r--r-- | spec/models/service_spec.rb | 7 | ||||
-rw-r--r-- | spec/requests/api/error_tracking_spec.rb | 2 | ||||
-rw-r--r-- | spec/services/spam/mark_as_spam_service_spec.rb | 2 | ||||
-rw-r--r-- | yarn.lock | 18 |
19 files changed, 249 insertions, 95 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 3835ad62ba2..678be464dfe 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -253,4 +253,4 @@ danger-review: - git version - node --version - yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline - - danger --fail-on-errors=true + - danger --verbose # fail-on-errors=true diff --git a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue index 1e071f46347..640827fe564 100644 --- a/app/assets/javascripts/issuables_list/components/issuables_list_app.vue +++ b/app/assets/javascripts/issuables_list/components/issuables_list_app.vue @@ -1,9 +1,14 @@ <script> -import { omit } from 'lodash'; +import { toNumber, omit } from 'lodash'; import { GlEmptyState, GlPagination, GlSkeletonLoading } from '@gitlab/ui'; import flash from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import { scrollToElement, urlParamsToObject } from '~/lib/utils/common_utils'; +import { + scrollToElement, + urlParamsToObject, + historyPushState, + getParameterByName, +} from '~/lib/utils/common_utils'; import { __ } from '~/locale'; import initManualOrdering from '~/manual_ordering'; import Issuable from './issuable.vue'; @@ -14,6 +19,7 @@ import { PAGE_SIZE_MANUAL, LOADING_LIST_ITEMS_LENGTH, } from '../constants'; +import { setUrlParams } from '~/lib/utils/url_utility'; import issueableEventHub from '../eventhub'; export default { @@ -56,7 +62,10 @@ export default { isBulkEditing: false, issuables: [], loading: false, - page: 1, + page: + getParameterByName('page', window.location.href) !== null + ? toNumber(getParameterByName('page')) + : 1, selection: {}, totalItems: 0, }; @@ -189,6 +198,12 @@ export default { if (newPage === this.page) return; scrollToElement('#content-body'); + + // NOTE: This allows for the params to be updated on pagination + historyPushState( + setUrlParams({ ...this.filters, page: newPage }, window.location.href, true), + ); + this.fetchIssuables(newPage); }, onSelectAll() { diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index 9ec8f930a78..46ba270f328 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -11,7 +11,7 @@ module SpammableActions end def mark_as_spam - if Spam::MarkAsSpamService.new(spammable: spammable).execute + if Spam::MarkAsSpamService.new(target: spammable).execute redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase } else redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.') diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 97232def91c..3142d7d7782 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -115,13 +115,7 @@ module LabelsHelper end def text_color_class_for_bg(bg_color) - if bg_color.length == 4 - r, g, b = bg_color[1, 4].scan(/./).map { |v| (v * 2).hex } - else - r, g, b = bg_color[1, 7].scan(/.{2}/).map(&:hex) - end - - if (r + g + b) > 500 + if light_color?(bg_color) 'gl-label-text-dark' else 'gl-label-text-light' @@ -129,17 +123,21 @@ module LabelsHelper end def text_color_for_bg(bg_color) - if bg_color.length == 4 - r, g, b = bg_color[1, 4].scan(/./).map { |v| (v * 2).hex } + if light_color?(bg_color) + '#333333' else - r, g, b = bg_color[1, 7].scan(/.{2}/).map(&:hex) + '#FFFFFF' end + end - if (r + g + b) > 500 - '#333333' + def light_color?(color) + if color.length == 4 + r, g, b = color[1, 4].scan(/./).map { |v| (v * 2).hex } else - '#FFFFFF' + r, g, b = color[1, 7].scan(/.{2}/).map(&:hex) end + + (r + g + b) > 500 end def labels_filter_path_with_defaults(only_group_labels: false, include_ancestor_groups: true, include_descendant_groups: false) diff --git a/app/models/service.rb b/app/models/service.rb index e60dda59176..41ae197f432 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -32,7 +32,7 @@ class Service < ApplicationRecord belongs_to :project, inverse_of: :services has_one :service_hook - validates :project_id, presence: true, unless: proc { |service| service.template? } + validates :project_id, presence: true, unless: -> { template? } validates :type, presence: true scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } @@ -70,10 +70,6 @@ class Service < ApplicationRecord true end - def template? - template - end - def category read_attribute(:category).to_sym end diff --git a/app/services/concerns/akismet_methods.rb b/app/services/concerns/akismet_methods.rb index 105b79785bd..4e554ddac4c 100644 --- a/app/services/concerns/akismet_methods.rb +++ b/app/services/concerns/akismet_methods.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true module AkismetMethods - def spammable_owner - @user ||= User.find(spammable.author_id) + def target_owner + @user ||= User.find(target.author_id) end def akismet @akismet ||= Spam::AkismetService.new( - spammable_owner.name, - spammable_owner.email, - spammable.try(:spammable_text) || spammable&.text, + target_owner.name, + target_owner.email, + target.try(:spammable_text) || target&.text, options ) end diff --git a/app/services/spam/ham_service.rb b/app/services/spam/ham_service.rb index d0f53eea90c..87069d5cd54 100644 --- a/app/services/spam/ham_service.rb +++ b/app/services/spam/ham_service.rb @@ -23,6 +23,6 @@ module Spam end end - alias_method :spammable, :spam_log + alias_method :target, :spam_log end end diff --git a/app/services/spam/mark_as_spam_service.rb b/app/services/spam/mark_as_spam_service.rb index 0ebcf17927a..ed5e674d8e9 100644 --- a/app/services/spam/mark_as_spam_service.rb +++ b/app/services/spam/mark_as_spam_service.rb @@ -4,21 +4,21 @@ module Spam class MarkAsSpamService include ::AkismetMethods - attr_accessor :spammable, :options + attr_accessor :target, :options - def initialize(spammable:) - @spammable = spammable + def initialize(target:) + @target = target @options = {} - @options[:ip_address] = @spammable.ip_address - @options[:user_agent] = @spammable.user_agent + @options[:ip_address] = @target.ip_address + @options[:user_agent] = @target.user_agent end def execute - return unless spammable.submittable_as_spam? + return unless target.submittable_as_spam? return unless akismet.submit_spam - spammable.user_agent_detail.update_attribute(:submitted, true) + target.user_agent_detail.update_attribute(:submitted, true) end end end diff --git a/app/services/spam/spam_check_service.rb b/app/services/spam/spam_check_service.rb index d19ef03976f..3269f9d687a 100644 --- a/app/services/spam/spam_check_service.rb +++ b/app/services/spam/spam_check_service.rb @@ -4,11 +4,11 @@ module Spam class SpamCheckService include AkismetMethods - attr_accessor :spammable, :request, :options + attr_accessor :target, :request, :options attr_reader :spam_log def initialize(spammable:, request:) - @spammable = spammable + @target = spammable @request = request @options = {} @@ -17,8 +17,8 @@ module Spam @options[:user_agent] = @request.env['HTTP_USER_AGENT'] @options[:referrer] = @request.env['HTTP_REFERRER'] else - @options[:ip_address] = @spammable.ip_address - @options[:user_agent] = @spammable.user_agent + @options[:ip_address] = @target.ip_address + @options[:user_agent] = @target.user_agent end end @@ -31,8 +31,8 @@ module Spam # Otherwise, it goes to Akismet for spam check. # If so, it assigns spammable object as "spam" and creates a SpamLog record. possible_spam = check(api) - spammable.spam = possible_spam unless spammable.allow_possible_spam? - spammable.spam_log = spam_log + target.spam = possible_spam unless target.allow_possible_spam? + target.spam_log = spam_log end end @@ -48,18 +48,18 @@ module Spam end def check_for_spam? - spammable.check_for_spam? + target.check_for_spam? end def create_spam_log(api) @spam_log = SpamLog.create!( { - user_id: spammable.author_id, - title: spammable.spam_title, - description: spammable.spam_description, + user_id: target.author_id, + title: target.spam_title, + description: target.spam_description, source_ip: options[:ip_address], user_agent: options[:user_agent], - noteable_type: spammable.class.to_s, + noteable_type: target.class.to_s, via_api: api } ) diff --git a/app/views/ide/_show.html.haml b/app/views/ide/_show.html.haml index 057225d021f..b871f0363f3 100644 --- a/app/views/ide/_show.html.haml +++ b/app/views/ide/_show.html.haml @@ -6,5 +6,5 @@ #ide.ide-loading{ data: ide_data } .text-center - = icon('spinner spin 2x') + .spinner.spinner-md %h2.clgray= _('Loading the GitLab IDE...') diff --git a/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-ide.yml b/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-ide.yml new file mode 100644 index 00000000000..2b206e3ddb1 --- /dev/null +++ b/changelogs/unreleased/Resolve-Migrate--fa-spinner-app-views-ide.yml @@ -0,0 +1,5 @@ +--- +title: Migrate .fa-spinner to .spinner for app/views/ide +merge_request: 25022 +author: nuwe1 +type: other diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md index 57a3dde55e6..232bca30e0f 100644 --- a/doc/development/documentation/site_architecture/index.md +++ b/doc/development/documentation/site_architecture/index.md @@ -96,6 +96,50 @@ Read through [the global navigation documentation](global_nav.md) to understand: TBA --> +## Pipelines + +The pipeline in the `gitlab-docs` project: + +- Tests changes to the docs site code. +- Builds the Docker images used in various pipeline jobs. +- Builds and deploys the docs site itself. +- Generates the review apps when the `review-docs-deploy` job is triggered. + +### Rebuild the docs site Docker images + +Once a week, on Mondays, a scheduled pipeline runs and rebuilds the Docker images +used in various pipeline jobs, like `docs-lint`. The Docker image configuration files are +located at <https://gitlab.com/gitlab-org/gitlab-docs/-/tree/master/dockerfiles>. + +If you need to rebuild the Docker images immediately (must have maintainer level permissions): + +CAUTION: **Caution** +If you change the dockerfile configuration and rebuild the images, you can break the master +pipeline in the main `gitlab` repo as well as in `gitlab-docs`. Create an image with +a different name first and test it to ensure you do not break the pipelines. + +1. In [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs), go to **{rocket}** **CI / CD > Pipelines**. +1. Click the **Run Pipeline** button. +1. See that a new pipeline is running. The jobs that build the images are in the first + stage, `build-images`. You can click the pipeline number to see the larger pipeline + graph, or click the first (`build-images`) stage in the mini pipeline graph to + expose the jobs that build the images. +1. Click the **play** (**{play}**) button next to the images you want to rebuild. + - Normally, you do not need to rebuild the `image:gitlab-docs-base` image, as it + rarely changes. If it does need to be rebuilt, be sure to only run `image:docs-lint` + after it is finished rebuilding. + +### Deploy the docs site + +Every four hours a scheduled pipeline builds and deploys the docs site. The pipeline +fetches the current docs from the main project's master branch, builds it with Nanoc +and deploys it to <https://docs.gitlab.com>. + +If you need to build and deploy the site immediately (must have maintainer level permissions): + +1. In [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs), go to **{rocket}** **CI / CD > Schedules**. +1. For the `Build docs.gitlab.com every 4 hours` scheduled pipeline, click the **play** (**{play}**) button. + ## Using YAML data files The easiest way to achieve something similar to diff --git a/package.json b/package.json index f0ff6c50709..b8304fee6e1 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "@babel/plugin-syntax-import-meta": "^7.8.3", "@babel/preset-env": "^7.8.4", "@gitlab/at.js": "^1.5.5", - "@gitlab/svgs": "^1.101.0", - "@gitlab/ui": "^9.16.0", + "@gitlab/svgs": "^1.103.0", + "@gitlab/ui": "^9.17.0", "@gitlab/visual-review-tools": "1.5.1", "@sentry/browser": "^5.10.2", "@sourcegraph/code-host-integration": "0.0.30", diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index fb4d1cf59fe..806a4e2f52c 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -740,16 +740,16 @@ describe Projects::IssuesController do .to log_spam(title: 'Spam title', noteable_type: 'Issue') end - it 'renders recaptcha_html json response' do - update_issue - - expect(json_response).to have_key('recaptcha_html') - end + context 'renders properly' do + render_views - it 'returns 200 status' do - update_issue + it 'renders recaptcha_html json response' do + update_issue - expect(response).to have_gitlab_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to have_key('recaptcha_html') + expect(json_response['recaptcha_html']).not_to be_empty + end end end diff --git a/spec/frontend/issuables_list/components/issuables_list_app_spec.js b/spec/frontend/issuables_list/components/issuables_list_app_spec.js index eafc4d83d87..6b680af354e 100644 --- a/spec/frontend/issuables_list/components/issuables_list_app_spec.js +++ b/spec/frontend/issuables_list/components/issuables_list_app_spec.js @@ -12,12 +12,21 @@ import { PAGE_SIZE, PAGE_SIZE_MANUAL, RELATIVE_POSITION } from '~/issuables_list jest.mock('~/flash', () => jest.fn()); jest.mock('~/issuables_list/eventhub'); +jest.mock('~/lib/utils/common_utils', () => ({ + ...jest.requireActual('~/lib/utils/common_utils'), + scrollToElement: () => {}, +})); const TEST_LOCATION = `${TEST_HOST}/issues`; const TEST_ENDPOINT = '/issues'; const TEST_CREATE_ISSUES_PATH = '/createIssue'; const TEST_EMPTY_SVG_PATH = '/emptySvg'; +const setUrl = query => { + window.location.href = `${TEST_LOCATION}${query}`; + window.location.search = query; +}; + const MOCK_ISSUES = Array(PAGE_SIZE_MANUAL) .fill(0) .map((_, i) => ({ @@ -267,8 +276,6 @@ describe('Issuables list component', () => { }); describe('with query params in window.location', () => { - const query = - '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&utf8=%E2%9C%93&weight=0'; const expectedFilters = { assignee_username: 'root', author_username: 'root', @@ -284,32 +291,73 @@ describe('Issuables list component', () => { sort: 'desc', }; - beforeEach(() => { - window.location.href = `${TEST_LOCATION}${query}`; - window.location.search = query; - setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); - factory({ sortKey: 'milestone_due_desc' }); - return waitForPromises(); - }); + describe('when page is not present in params', () => { + const query = + '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&utf8=%E2%9C%93&weight=0'; - it('applies filters and sorts', () => { - expect(wrapper.vm.hasFilters).toBe(true); - expect(wrapper.vm.filters).toEqual(expectedFilters); + beforeEach(() => { + setUrl(query); - expect(apiSpy).toHaveBeenCalledWith( - expect.objectContaining({ - params: { - ...expectedFilters, - with_labels_details: true, - page: 1, - per_page: PAGE_SIZE, - }, - }), - ); + setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); + factory({ sortKey: 'milestone_due_desc' }); + + return waitForPromises(); + }); + + afterEach(() => { + apiSpy.mockClear(); + }); + + it('applies filters and sorts', () => { + expect(wrapper.vm.hasFilters).toBe(true); + expect(wrapper.vm.filters).toEqual(expectedFilters); + + expect(apiSpy).toHaveBeenCalledWith( + expect.objectContaining({ + params: { + ...expectedFilters, + with_labels_details: true, + page: 1, + per_page: PAGE_SIZE, + }, + }), + ); + }); + + it('passes the base url to issuable', () => { + expect(findFirstIssuable().props('baseUrl')).toBe(TEST_LOCATION); + }); }); - it('passes the base url to issuable', () => { - expect(findFirstIssuable().props('baseUrl')).toEqual(TEST_LOCATION); + describe('when page is present in the param', () => { + const query = + '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&utf8=%E2%9C%93&weight=0&page=3'; + + beforeEach(() => { + setUrl(query); + + setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); + factory({ sortKey: 'milestone_due_desc' }); + + return waitForPromises(); + }); + + afterEach(() => { + apiSpy.mockClear(); + }); + + it('applies filters and sorts', () => { + expect(apiSpy).toHaveBeenCalledWith( + expect.objectContaining({ + params: { + ...expectedFilters, + with_labels_details: true, + page: 3, + per_page: PAGE_SIZE, + }, + }), + ); + }); }); }); @@ -322,7 +370,7 @@ describe('Issuables list component', () => { }); it('passes the base url to issuable', () => { - expect(findFirstIssuable().props('baseUrl')).toEqual(TEST_LOCATION); + expect(findFirstIssuable().props('baseUrl')).toBe(TEST_LOCATION); }); }); @@ -402,4 +450,47 @@ describe('Issuables list component', () => { }); }); }); + + describe('when paginates', () => { + const newPage = 3; + + beforeEach(() => { + window.history.pushState = jest.fn(); + setupApiMock(() => [ + 200, + MOCK_ISSUES.slice(0, PAGE_SIZE), + { + 'x-total': 100, + 'x-page': 2, + }, + ]); + + factory(); + + return waitForPromises(); + }); + + afterEach(() => { + // reset to original value + window.history.pushState.mockRestore(); + }); + + it('calls window.history.pushState one time', () => { + // Trigger pagination + wrapper.find(GlPagination).vm.$emit('input', newPage); + + expect(window.history.pushState).toHaveBeenCalledTimes(1); + }); + + it('sets params in the url', () => { + // Trigger pagination + wrapper.find(GlPagination).vm.$emit('input', newPage); + + expect(window.history.pushState).toHaveBeenCalledWith( + {}, + '', + `${TEST_LOCATION}?state=opened&order_by=priority&sort=asc&page=${newPage}`, + ); + }); + }); }); diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index f58bcbebd67..eaf19bd4fea 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -10,8 +10,13 @@ describe Service do it { is_expected.to have_one :issue_tracker_data } end - describe 'Validations' do + describe 'validations' do it { is_expected.to validate_presence_of(:type) } + + it 'validates presence of project_id if not template', :aggregate_failures do + expect(build(:service, project_id: nil, template: true)).to be_valid + expect(build(:service, project_id: nil, template: false)).to be_invalid + end end describe 'Scopes' do diff --git a/spec/requests/api/error_tracking_spec.rb b/spec/requests/api/error_tracking_spec.rb index 120248bdbc6..deed9777025 100644 --- a/spec/requests/api/error_tracking_spec.rb +++ b/spec/requests/api/error_tracking_spec.rb @@ -22,7 +22,7 @@ describe API::ErrorTracking do end shared_examples 'returns 404' do - it 'returns correct project settings' do + it 'returns no project settings' do make_request expect(response).to have_gitlab_http_status(:not_found) diff --git a/spec/services/spam/mark_as_spam_service_spec.rb b/spec/services/spam/mark_as_spam_service_spec.rb index cba9d6a39cb..9978005279a 100644 --- a/spec/services/spam/mark_as_spam_service_spec.rb +++ b/spec/services/spam/mark_as_spam_service_spec.rb @@ -7,7 +7,7 @@ describe Spam::MarkAsSpamService do let(:spammable) { build(:issue, user_agent_detail: user_agent_detail) } let(:fake_akismet_service) { double(:akismet_service, submit_spam: true) } - subject { described_class.new(spammable: spammable) } + subject { described_class.new(target: spammable) } describe '#execute' do before do diff --git a/yarn.lock b/yarn.lock index 50899ad79e5..07e119260ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -796,15 +796,15 @@ dependencies: vue-eslint-parser "^7.0.0" -"@gitlab/svgs@^1.101.0": - version "1.101.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.101.0.tgz#5440ada1774758e42fd67212d495a29523dd0d5e" - integrity sha512-GE6wRn0UqA5f0pmX5wL/vTgUnAgZEdTIDam+OTMuMxf5a1jfxc1KlSLudgZbS3O/W79jN4uMkTdZ7X8gEzAChw== - -"@gitlab/ui@^9.16.0": - version "9.16.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.16.0.tgz#ac66b55cffdfd9ac2df2abddb11445edc3494732" - integrity sha512-9PbFgqNxIAGn1LyIcnlqQuNGAiBT/fqTx8vPdaDQkdScFZksZOBwiIhpxnRk9UFABC3h+0TNeHgVigD7TKGqJw== +"@gitlab/svgs@^1.103.0": + version "1.103.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.103.0.tgz#fb7136df9f5df3f53685740daf0ebf565b735608" + integrity sha512-+Bt+a8ln9KSz3QxB2P57ub71/eiEnKXJQSheTagYh2KlT6Glzh7XeLjyc3W6uGWBOONqsp96H/tYEa9dmDnLqQ== + +"@gitlab/ui@^9.17.0": + version "9.17.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.17.0.tgz#2c2e0412b8293889ab3c2e8f9d7ae1e4b9b508b7" + integrity sha512-6Ph3eE7ygUc6A72J6+mfce/MQSkyN4Rl2moSIZeMcAnHhIXBn6qoj4+Pq+jTWXYFqy3/ow6Iddle63Flj1dnkA== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |