diff options
58 files changed, 623 insertions, 277 deletions
diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md index 1576f6e8f53..00b396bac4e 100644 --- a/.gitlab/issue_templates/Feature Flag Roll Out.md +++ b/.gitlab/issue_templates/Feature Flag Roll Out.md @@ -24,26 +24,6 @@ Are there any other stages or teams involved that need to be kept in the loop? - The Delivery Team --> -## The Rollout Plan - -- Partial Rollout on GitLab.com with testing groups -- Rollout on GitLab.com for a certain period (How long) -- Percentage Rollout on GitLab.com -- Rollout Feature for everyone as soon as it's ready - -<!-- Which dashboards from https://dashboards.gitlab.net are most relevant? Sentry errors reports can also be useful to review --> - -## Testing Groups/Projects/Users - -<!-- If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example. --> - -- `gitlab-org/gitlab` project -- `gitlab-org/gitlab-foss` project -- `gitlab-com/www-gitlab-com` project -- `gitlab-org`/`gitlab-com` groups -- ... - - ## Expectations ### What are we expecting to happen? @@ -62,17 +42,30 @@ Are there any other stages or teams involved that need to be kept in the loop? ### Rollout on non-production environments -- [ ] Ensure that the feature MRs have been deployed to non-production environments. +- Ensure that the feature MRs have been deployed to non-production environments. - [ ] `/chatops run auto_deploy status <merge-commit-of-your-feature>` - [ ] Enable the feature globally on non-production environments. - [ ] `/chatops run feature set <feature-flag-name> true --dev` - [ ] `/chatops run feature set <feature-flag-name> true --staging` - [ ] Verify that the feature works as expected. Posting the QA result in this issue is preferable. -### Preparation before production rollout +### Specific rollout on production -- [ ] Ensure that the feature MRs have been deployed to both production and canary. +- Ensure that the feature MRs have been deployed to both production and canary. - [ ] `/chatops run auto_deploy status <merge-commit-of-your-feature>` +- If you're using [project-actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), you must enable the feature on these entries: + - [ ] `/chatops run feature set --project=gitlab-org/gitlab <feature-flag-name> true` + - [ ] `/chatops run feature set --project=gitlab-org/gitlab-foss <feature-flag-name> true` + - [ ] `/chatops run feature set --project=gitlab-com/www-gitlab-com <feature-flag-name> true` +- If you're using [group-actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), you must enable the feature on these entries: + - [ ] `/chatops run feature set --group=gitlab-org <feature-flag-name> true` + - [ ] `/chatops run feature set --group=gitlab-com <feature-flag-name> true` +- If you're using [user-actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), you must enable the feature on these entries: + - [ ] `/chatops run feature set --user=<your-username> <feature-flag-name> true` +- [ ] Verify that the feature works on the specific entries. Posting the QA result in this issue is preferable. + +### Preparation before global rollout + - [ ] Check if the feature flag change needs to be accompanied with a [change management issue](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#feature-flags-and-the-change-management-process). Cross link the issue here if it does. @@ -86,19 +79,13 @@ Are there any other stages or teams involved that need to be kept in the loop? All `/chatops` commands that target production should be done in the `#production` slack channel for visibility. -- [ ] Confirm the feature flag is enabled on `staging` without incident -- [ ] Roll out the feature to targeted testing projects/groups first - - [ ] `/chatops run feature set --project=gitlab-org/gitlab <feature-flag-name> true` - - [ ] `/chatops run feature set --project=gitlab-org/gitlab-foss <feature-flag-name> true` - - [ ] `/chatops run feature set --project=gitlab-com/www-gitlab-com <feature-flag-name> true` - - [ ] [Incrementally roll out](https://docs.gitlab.com/ee/development/feature_flags/controls.html#process) the feature. - If the feature flag in code has [an actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), perform **actor-based** rollout. - [ ] `/chatops run feature set <feature-flag-name> <rollout-percentage> --actors` - If the feature flag in code does **NOT** have [an actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), perform time-based rollout (**random** rollout). - [ ] `/chatops run feature set <feature-flag-name> <rollout-percentage>` -- [ ] Verify the change has the desired outcome with the limited rollout before enabling the feature globally on production. -- [ ] Enable the feature globally on production environment. `/chatops run feature set <feature-flag-name> true` + - Enable the feature globally on production environment. + - [ ] `/chatops run feature set <feature-flag-name> true` - [ ] Announce on [the feature issue](ISSUE LINK) that the feature has been globally enabled. - [ ] Wait for [at least one day for the verification term](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#including-a-feature-behind-feature-flag-in-the-final-release). diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 156ef774529..e5d6d9a3d03 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -a47a975ef7d4ef51e0d68c5662d5cb3bb5b83b76 +2b424740fc73419f40bd74c2f05db3d7ef774198 diff --git a/app/assets/javascripts/related_issues/components/add_issuable_form.vue b/app/assets/javascripts/related_issues/components/add_issuable_form.vue index 4deb93e4b30..545263a9e37 100644 --- a/app/assets/javascripts/related_issues/components/add_issuable_form.vue +++ b/app/assets/javascripts/related_issues/components/add_issuable_form.vue @@ -205,7 +205,7 @@ export default { :disabled="isSubmitButtonDisabled" :loading="isSubmitting" type="submit" - class="js-add-issuable-form-add-button float-left" + class="float-left" data-qa-selector="add_issue_button" > {{ __('Add') }} diff --git a/app/assets/javascripts/related_issues/components/issue_token.vue b/app/assets/javascripts/related_issues/components/issue_token.vue index 840a6750cd2..abbd612d3ec 100644 --- a/app/assets/javascripts/related_issues/components/issue_token.vue +++ b/app/assets/javascripts/related_issues/components/issue_token.vue @@ -111,7 +111,6 @@ export default { :disabled="removeDisabled" data-testid="removeBtn" type="button" - class="js-issue-token-remove-button" @click="onRemoveRequest" > <gl-icon name="close" /> diff --git a/app/assets/javascripts/related_issues/components/related_issuable_input.vue b/app/assets/javascripts/related_issues/components/related_issuable_input.vue index 46b97370d66..270d4632a54 100644 --- a/app/assets/javascripts/related_issues/components/related_issuable_input.vue +++ b/app/assets/javascripts/related_issues/components/related_issuable_input.vue @@ -107,9 +107,6 @@ export default { onAutoCompleteToggled(isOpen) { this.isAutoCompleteOpen = isOpen; }, - onInputWrapperClick() { - this.$refs.input.focus(); - }, onInput() { const { value } = this.$refs.input; const caretPos = this.$refs.input.selectionStart; @@ -185,26 +182,23 @@ export default { <div ref="issuableFormWrapper" :class="{ focus: isInputFocused }" - class="add-issuable-form-input-wrapper form-control gl-field-error-outline" + class="add-issuable-form-input-wrapper form-control gl-field-error-outline gl-h-auto gl-p-3 gl-pb-2" role="button" @click="onIssuableFormWrapperClick" > - <ul class="add-issuable-form-input-token-list"> - <!-- - We need to ensure this key changes any time the pendingReferences array is updated - else two consecutive pending ref strings in an array with the same name will collide - and cause odd behavior when one is removed. - --> + <ul + class="gl-display-flex gl-flex-wrap gl-align-items-baseline gl-list-style-none gl-m-0 gl-p-0" + > <li v-for="(reference, index) in references" - :key="`related-issues-token-${reference}`" - class="js-add-issuable-form-token-list-item add-issuable-form-token-list-item" + :key="reference" + class="gl-max-w-full gl-mb-2 gl-mr-2" > <issue-token :id-key="index" :display-reference="reference.text || reference" - :can-remove="true" - :is-condensed="true" + can-remove + is-condensed :path-id-separator="pathIdSeparator" event-namespace="pendingIssuable" @pendingIssuableRemoveRequest=" @@ -214,14 +208,15 @@ export default { " /> </li> - <li class="add-issuable-form-input-list-item"> + <li class="gl-mb-2 gl-flex-grow-1"> <input :id="inputId" ref="input" :value="inputValue" :placeholder="inputPlaceholder" + :aria-label="inputPlaceholder" type="text" - class="js-add-issuable-form-input add-issuable-form-input" + class="gl-w-full gl-border-none gl-outline-0" data-qa-selector="add_issue_field" autocomplete="off" @input="onInput" diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue index 54e67c5ab5c..c6e461b10e0 100644 --- a/app/assets/javascripts/repository/components/preview/index.vue +++ b/app/assets/javascripts/repository/components/preview/index.vue @@ -1,5 +1,5 @@ <script> -import { GlIcon, GlLink, GlLoadingIcon } from '@gitlab/ui'; +import { GlIcon, GlLink, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import $ from 'jquery'; import '~/behaviors/markdown/render_gfm'; import { handleLocationHash } from '~/lib/utils/common_utils'; @@ -22,6 +22,9 @@ export default { GlLink, GlLoadingIcon, }, + directives: { + SafeHtml, + }, props: { blob: { type: Object, @@ -59,11 +62,7 @@ export default { </div> <div class="blob-viewer" data-qa-selector="blob_viewer_content" itemprop="about"> <gl-loading-icon v-if="loading > 0" size="md" color="dark" class="my-4 mx-auto" /> - <div - v-else-if="readme" - ref="readme" - v-html="readme.html /* eslint-disable-line vue/no-v-html */" - ></div> + <div v-else-if="readme" ref="readme" v-safe-html="readme.html"></div> </div> </article> </template> diff --git a/app/assets/javascripts/runner/components/cells/runner_type_cell.vue b/app/assets/javascripts/runner/components/cells/runner_type_cell.vue index f186a8daf72..c8cb0bf6088 100644 --- a/app/assets/javascripts/runner/components/cells/runner_type_cell.vue +++ b/app/assets/javascripts/runner/components/cells/runner_type_cell.vue @@ -1,11 +1,18 @@ <script> -import { GlBadge } from '@gitlab/ui'; +import { GlTooltipDirective } from '@gitlab/ui'; import RunnerTypeBadge from '../runner_type_badge.vue'; +import RunnerStateLockedBadge from '../runner_state_locked_badge.vue'; +import RunnerStatePausedBadge from '../runner_state_paused_badge.vue'; +import { I18N_LOCKED_RUNNER_DESCRIPTION, I18N_PAUSED_RUNNER_DESCRIPTION } from '../../constants'; export default { components: { - GlBadge, RunnerTypeBadge, + RunnerStateLockedBadge, + RunnerStatePausedBadge, + }, + directives: { + GlTooltip: GlTooltipDirective, }, props: { runner: { @@ -24,19 +31,17 @@ export default { return !this.runner.active; }, }, + i18n: { + I18N_LOCKED_RUNNER_DESCRIPTION, + I18N_PAUSED_RUNNER_DESCRIPTION, + }, }; </script> <template> <div> <runner-type-badge :type="runnerType" size="sm" /> - - <gl-badge v-if="locked" variant="warning" size="sm"> - {{ s__('Runners|locked') }} - </gl-badge> - - <gl-badge v-if="paused" variant="danger" size="sm"> - {{ s__('Runners|paused') }} - </gl-badge> + <runner-state-locked-badge v-if="locked" size="sm" /> + <runner-state-paused-badge v-if="paused" size="sm" /> </div> </template> diff --git a/app/assets/javascripts/runner/components/runner_state_locked_badge.vue b/app/assets/javascripts/runner/components/runner_state_locked_badge.vue new file mode 100644 index 00000000000..458526010bc --- /dev/null +++ b/app/assets/javascripts/runner/components/runner_state_locked_badge.vue @@ -0,0 +1,25 @@ +<script> +import { GlBadge, GlTooltipDirective } from '@gitlab/ui'; +import { I18N_LOCKED_RUNNER_DESCRIPTION } from '../constants'; + +export default { + components: { + GlBadge, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + i18n: { + I18N_LOCKED_RUNNER_DESCRIPTION, + }, +}; +</script> +<template> + <gl-badge + v-gl-tooltip="$options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION" + variant="warning" + v-bind="$attrs" + > + {{ s__('Runners|locked') }} + </gl-badge> +</template> diff --git a/app/assets/javascripts/runner/components/runner_state_paused_badge.vue b/app/assets/javascripts/runner/components/runner_state_paused_badge.vue new file mode 100644 index 00000000000..d1e6fa05e4d --- /dev/null +++ b/app/assets/javascripts/runner/components/runner_state_paused_badge.vue @@ -0,0 +1,25 @@ +<script> +import { GlBadge, GlTooltipDirective } from '@gitlab/ui'; +import { I18N_PAUSED_RUNNER_DESCRIPTION } from '../constants'; + +export default { + components: { + GlBadge, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + i18n: { + I18N_PAUSED_RUNNER_DESCRIPTION, + }, +}; +</script> +<template> + <gl-badge + v-gl-tooltip="$options.i18n.I18N_PAUSED_RUNNER_DESCRIPTION" + variant="danger" + v-bind="$attrs" + > + {{ s__('Runners|paused') }} + </gl-badge> +</template> diff --git a/app/assets/javascripts/runner/components/runner_type_badge.vue b/app/assets/javascripts/runner/components/runner_type_badge.vue index c2f43daa899..1a61b80184b 100644 --- a/app/assets/javascripts/runner/components/runner_type_badge.vue +++ b/app/assets/javascripts/runner/components/runner_type_badge.vue @@ -1,20 +1,30 @@ <script> -import { GlBadge } from '@gitlab/ui'; +import { GlBadge, GlTooltipDirective } from '@gitlab/ui'; import { s__ } from '~/locale'; -import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants'; +import { + INSTANCE_TYPE, + GROUP_TYPE, + PROJECT_TYPE, + I18N_INSTANCE_RUNNER_DESCRIPTION, + I18N_GROUP_RUNNER_DESCRIPTION, + I18N_PROJECT_RUNNER_DESCRIPTION, +} from '../constants'; const BADGE_DATA = { [INSTANCE_TYPE]: { variant: 'success', text: s__('Runners|shared'), + tooltip: I18N_INSTANCE_RUNNER_DESCRIPTION, }, [GROUP_TYPE]: { variant: 'success', text: s__('Runners|group'), + tooltip: I18N_GROUP_RUNNER_DESCRIPTION, }, [PROJECT_TYPE]: { variant: 'info', text: s__('Runners|specific'), + tooltip: I18N_PROJECT_RUNNER_DESCRIPTION, }, }; @@ -22,6 +32,9 @@ export default { components: { GlBadge, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { type: { type: String, @@ -40,7 +53,7 @@ export default { }; </script> <template> - <gl-badge v-if="badge" :variant="badge.variant" v-bind="$attrs"> + <gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" :variant="badge.variant" v-bind="$attrs"> {{ badge.text }} </gl-badge> </template> diff --git a/app/assets/javascripts/runner/components/runner_type_help.vue b/app/assets/javascripts/runner/components/runner_type_help.vue index 70456b3ab65..2326f66ebb8 100644 --- a/app/assets/javascripts/runner/components/runner_type_help.vue +++ b/app/assets/javascripts/runner/components/runner_type_help.vue @@ -1,18 +1,36 @@ <script> -import { GlBadge } from '@gitlab/ui'; -import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants'; +import { + INSTANCE_TYPE, + GROUP_TYPE, + PROJECT_TYPE, + I18N_INSTANCE_RUNNER_DESCRIPTION, + I18N_GROUP_RUNNER_DESCRIPTION, + I18N_PROJECT_RUNNER_DESCRIPTION, + I18N_LOCKED_RUNNER_DESCRIPTION, + I18N_PAUSED_RUNNER_DESCRIPTION, +} from '../constants'; import RunnerTypeBadge from './runner_type_badge.vue'; +import RunnerStateLockedBadge from './runner_state_locked_badge.vue'; +import RunnerStatePausedBadge from './runner_state_paused_badge.vue'; export default { components: { - GlBadge, RunnerTypeBadge, + RunnerStateLockedBadge, + RunnerStatePausedBadge, }, runnerTypes: { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE, }, + i18n: { + I18N_INSTANCE_RUNNER_DESCRIPTION, + I18N_GROUP_RUNNER_DESCRIPTION, + I18N_PROJECT_RUNNER_DESCRIPTION, + I18N_LOCKED_RUNNER_DESCRIPTION, + I18N_PAUSED_RUNNER_DESCRIPTION, + }, }; </script> @@ -32,27 +50,23 @@ export default { <ul> <li> <runner-type-badge :type="$options.runnerTypes.INSTANCE_TYPE" size="sm" /> - - {{ __('Runs jobs from all unassigned projects.') }} + - {{ $options.i18n.I18N_INSTANCE_RUNNER_DESCRIPTION }} </li> <li> <runner-type-badge :type="$options.runnerTypes.GROUP_TYPE" size="sm" /> - - {{ __('Runs jobs from all unassigned projects in its group.') }} + - {{ $options.i18n.I18N_GROUP_RUNNER_DESCRIPTION }} </li> <li> <runner-type-badge :type="$options.runnerTypes.PROJECT_TYPE" size="sm" /> - - {{ __('Runs jobs from assigned projects.') }} + - {{ $options.i18n.I18N_PROJECT_RUNNER_DESCRIPTION }} </li> <li> - <gl-badge variant="warning" size="sm"> - {{ s__('Runners|locked') }} - </gl-badge> - - {{ __('Cannot be assigned to other projects.') }} + <runner-state-locked-badge size="sm" /> + - {{ $options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION }} </li> <li> - <gl-badge variant="danger" size="sm"> - {{ s__('Runners|paused') }} - </gl-badge> - - {{ __('Not available to run jobs.') }} + <runner-state-paused-badge size="sm" /> + - {{ $options.i18n.I18N_PAUSED_RUNNER_DESCRIPTION }} </li> </ul> </div> diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js index 46e55b322c7..a2fb9d9efd8 100644 --- a/app/assets/javascripts/runner/constants.js +++ b/app/assets/javascripts/runner/constants.js @@ -7,6 +7,14 @@ export const GROUP_RUNNER_COUNT_LIMIT = 1000; export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.'); export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}'); +export const I18N_INSTANCE_RUNNER_DESCRIPTION = s__('Runners|Available to all projects'); +export const I18N_GROUP_RUNNER_DESCRIPTION = s__( + 'Runners|Available to all projects and subgroups in the group', +); +export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects'); +export const I18N_LOCKED_RUNNER_DESCRIPTION = s__('Runners|You cannot assign to other projects'); +export const I18N_PAUSED_RUNNER_DESCRIPTION = s__('Runners|Not available to run jobs'); + export const RUNNER_TAG_BADGE_VARIANT = 'info'; export const RUNNER_TAG_BG_CLASS = 'gl-bg-blue-100'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue index 5d8541cf5e8..1347586c738 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue @@ -160,7 +160,12 @@ export default { wclass="report-block-list" class="report-block-container" > - <li v-for="data in fullData" :key="data.id" class="gl-display-flex gl-align-items-center"> + <li + v-for="data in fullData" + :key="data.id" + class="gl-display-flex gl-align-items-center" + data-testid="extension-list-item" + > <status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" /> <div class="gl-mt-2 gl-mb-2 gl-flex-wrap gl-align-self-center gl-display-flex"> <div v-safe-html="data.text" class="gl-mr-4"></div> diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index c4df2e102bd..6296b023e90 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -746,33 +746,14 @@ } } -.add-issuable-form-input-token-list { - display: flex; - flex-wrap: wrap; - align-items: baseline; - list-style: none; - margin-bottom: 0; - padding-left: 0; -} - -.add-issuable-form-token-list-item { - max-width: 100%; - margin-bottom: $gl-vert-padding; - margin-right: 5px; -} - -.add-issuable-form-input-list-item { - flex: 1; - min-width: 200px; - margin-bottom: $gl-vert-padding; -} - -.add-issuable-form-input { - width: 100%; - border: 0; +.add-issuable-form-input-wrapper { + &.focus { + border-color: $blue-300; + box-shadow: 0 0 4px $dropdown-input-focus-shadow; + } - &:focus { - outline: none; + .gl-show-field-errors &.form-control:not(textarea) { + height: auto; } } diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 94b0473e1f3..02d36c3353d 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -18,17 +18,21 @@ class Projects::TagsController < Projects::ApplicationController params[:sort] = params[:sort].presence || sort_value_recently_updated @sort = params[:sort] - @tags = TagsFinder.new(@repository, params).execute - @tags = Kaminari.paginate_array(@tags).page(params[:page]) + @tags, @tags_loading_error = TagsFinder.new(@repository, params).execute + + @tags = Kaminari.paginate_array(@tags).page(params[:page]) tag_names = @tags.map(&:name) @tags_pipelines = @project.ci_pipelines.latest_successful_for_refs(tag_names) + @releases = project.releases.where(tag: tag_names) @tag_pipeline_statuses = Ci::CommitStatusesFinder.new(@project, @repository, current_user, @tags).execute respond_to do |format| - format.html - format.atom { render layout: 'xml.atom' } + status = @tags_loading_error ? :service_unavailable : :ok + + format.html { render status: status } + format.atom { render layout: 'xml.atom', status: status } end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 7ebd93d4a5d..89767915d7f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -284,9 +284,9 @@ class ProjectsController < Projects::ApplicationController end if find_tags && @repository.tag_count.nonzero? - tags = TagsFinder.new(@repository, params).execute.take(100).map(&:name) + tags, _ = TagsFinder.new(@repository, params).execute - options['Tags'] = tags + options['Tags'] = tags.take(100).map(&:name) end # If reference is commit id - we should add it to branch/tag selectbox diff --git a/app/finders/tags_finder.rb b/app/finders/tags_finder.rb index d9848d027cf..0ccbbdc1b87 100644 --- a/app/finders/tags_finder.rb +++ b/app/finders/tags_finder.rb @@ -7,6 +7,9 @@ class TagsFinder < GitRefsFinder def execute tags = repository.tags_sorted_by(sort) - by_search(tags) + + [by_search(tags), nil] + rescue Gitlab::Git::CommandError => e + [[], e] end end diff --git a/app/models/ci/resource_group.rb b/app/models/ci/resource_group.rb index 946fce3473f..6d25f747a9d 100644 --- a/app/models/ci/resource_group.rb +++ b/app/models/ci/resource_group.rb @@ -32,7 +32,7 @@ module Ci end def upcoming_processables - if unordered? || Feature.disabled?(:ci_resource_group_process_modes, project, default_enabled: :yaml) + if unordered? processables.waiting_for_resource elsif oldest_first? processables.waiting_for_resource_or_upcoming diff --git a/app/views/authentication/_authenticate.html.haml b/app/views/authentication/_authenticate.html.haml index 5a2ae3f44c2..7dcec50573f 100644 --- a/app/views/authentication/_authenticate.html.haml +++ b/app/views/authentication/_authenticate.html.haml @@ -1,14 +1,17 @@ #js-authenticate-token-2fa %a.gl-button.btn.btn-block.btn-confirm#js-login-2fa-device{ href: '#' }= _("Sign in via 2FA code") +-# haml-lint:disable InlineJavaScript %script#js-authenticate-token-2fa-in-progress{ type: "text/template" } %p= _("Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now.") +-# haml-lint:disable InlineJavaScript %script#js-authenticate-token-2fa-error{ type: "text/template" } %div %p <%= error_message %> (<%= error_name %>) %a.btn.btn-default.gl-button.btn-block#js-token-2fa-try-again= _("Try again?") +-# haml-lint:disable InlineJavaScript %script#js-authenticate-token-2fa-authenticated{ type: "text/template" } %div %p= _("We heard back from your device. You have been authenticated.") diff --git a/app/views/authentication/_register.html.haml b/app/views/authentication/_register.html.haml index 678fd3c8e8c..5eed969ed35 100644 --- a/app/views/authentication/_register.html.haml +++ b/app/views/authentication/_register.html.haml @@ -1,8 +1,10 @@ #js-register-token-2fa +-# haml-lint:disable InlineJavaScript %script#js-register-2fa-message{ type: "text/template" } %p <%= message %> +-# haml-lint:disable InlineJavaScript %script#js-register-token-2fa-setup{ type: "text/template" } - if current_user.two_factor_otp_enabled? .row.gl-mb-3 @@ -17,12 +19,14 @@ .col-md-8 %p= _("You need to register a two-factor authentication app before you can set up a device.") +-# haml-lint:disable InlineJavaScript %script#js-register-token-2fa-error{ type: "text/template" } %div %p %span <%= error_message %> (<%= error_name %>) %a.btn.btn-default.gl-button#js-token-2fa-try-again= _("Try again?") +-# haml-lint:disable InlineJavaScript %script#js-register-token-2fa-registered{ type: "text/template" } .row.gl-mb-3 .col-md-12 diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index 3678ec748f7..02a37dac158 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -21,6 +21,7 @@ %li %h2.breadcrumbs-sub-title{ data: { qa_selector: 'breadcrumb_sub_title_content' } } = link_to @breadcrumb_title, breadcrumb_title_link + -# haml-lint:disable InlineJavaScript %script{ type: 'application/ld+json' } :plain #{schema_breadcrumb_json} diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 27858932e5e..8ee7910de4b 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -31,4 +31,5 @@ .form-actions = button_tag 'Create branch', class: 'gl-button btn btn-confirm' = link_to _('Cancel'), project_branches_path(@project), class: 'gl-button btn btn-default btn-cancel' +-# haml-lint:disable InlineJavaScript %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 7e260a03c5d..ff5582f2627 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -49,6 +49,7 @@ = render "projects/merge_requests/tabs/pane", id: "notes", class: "notes voting_notes" do .row %section.col-md-12 + -# haml-lint:disable InlineJavaScript %script.js-notes-data{ type: "application/json" }= initial_notes_data(true).to_json.html_safe .issuable-discussion.js-vue-notes-event - if @merge_request.description.present? diff --git a/app/views/projects/product_analytics/test.html.haml b/app/views/projects/product_analytics/test.html.haml index 60d897ee138..3204cd5fbbe 100644 --- a/app/views/projects/product_analytics/test.html.haml +++ b/app/views/projects/product_analytics/test.html.haml @@ -12,5 +12,6 @@ %code = @event.as_json_wo_empty +-# haml-lint:disable InlineJavaScript :javascript #{render 'tracker'} diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 79205a51d71..d3cc409df1d 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -18,6 +18,9 @@ = render_if_exists 'projects/commits/mirror_status' + - if @tags_loading_error + = render 'shared/errors/gitaly_unavailable', reason: s_('TagsPage|Unable to load tags') + .tags - if @tags.any? %ul.flex-list.content-list diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index fe00772d1d6..4281152225a 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -54,4 +54,5 @@ .form-actions.gl-display-flex = button_tag s_('TagsPage|Create tag'), class: 'gl-button btn btn-confirm gl-mr-3', data: { qa_selector: "create_tag_button" } = link_to s_('TagsPage|Cancel'), project_tags_path(@project), class: 'gl-button btn btn-default btn-cancel' +-# haml-lint:disable InlineJavaScript %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe diff --git a/app/views/shared/errors/_gitaly_unavailable.html.haml b/app/views/shared/errors/_gitaly_unavailable.html.haml new file mode 100644 index 00000000000..96a68cbcdc6 --- /dev/null +++ b/app/views/shared/errors/_gitaly_unavailable.html.haml @@ -0,0 +1,8 @@ +.gl-alert.gl-alert-danger.gl-mb-5.gl-mt-5 + .gl-alert-container + = sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') + .gl-alert-content + .gl-alert-title + = reason + .gl-alert-body + = s_('The git server, Gitaly, is not available at this time. Please contact your administrator.') diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 1e8724c3448..f5f5674190c 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -7,6 +7,7 @@ - can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit) - add_page_startup_api_call "#{issuable_sidebar[:issuable_json_path]}?serializer=sidebar_extras" - reviewers = local_assigns.fetch(:reviewers, nil) +- in_group_context_with_iterations = @project.group.present? && issuable_sidebar[:supports_iterations] %aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in }, issuable_type: issuable_type }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite', 'aria-label': issuable_type } .issuable-sidebar @@ -28,11 +29,11 @@ = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type - if issuable_sidebar[:supports_milestone] - .block.milestone{ :class => ("gl-border-b-0!" if issuable_sidebar[:supports_iterations]), data: { qa_selector: 'milestone_block' } } + .block.milestone{ :class => ("gl-border-b-0!" if in_group_context_with_iterations), data: { qa_selector: 'milestone_block' } } .js-milestone-select{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } } - - if @project.group.present? && issuable_sidebar[:supports_iterations] - .block{ class: 'gl-pt-0!', data: { qa_selector: 'iteration_container' } } + - if in_group_context_with_iterations + .block{ class: 'gl-pt-0! gl-collapse-empty', data: { qa_selector: 'iteration_container', testid: 'iteration_container' } }< = render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type - if issuable_sidebar[:supports_time_tracking] @@ -55,11 +56,13 @@ .js-sidebar-status-entry-point{ data: sidebar_status_data(issuable_sidebar, @project) } - if issuable_sidebar.has_key?(:confidential) + -# haml-lint:disable InlineJavaScript %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe #js-confidential-entry-point = render_if_exists 'shared/issuable/sidebar_cve_id_request', issuable_sidebar: issuable_sidebar + -# haml-lint:disable InlineJavaScript %script#js-lock-issue-data{ type: "application/json" }= { is_locked: !!issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe #js-lock-entry-point diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml index f7f5c02370d..e34f412baa4 100644 --- a/app/views/shared/notes/_notes_with_form.html.haml +++ b/app/views/shared/notes/_notes_with_form.html.haml @@ -25,4 +25,5 @@ = sprite_icon('lock', css_class: 'icon') %span = html_escape(_("This %{issuable} is locked. Only %{strong_open}project members%{strong_close} can comment.")) % { issuable: issuable.class.to_s.titleize.downcase, strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe } +-# haml-lint:disable InlineJavaScript %script.js-notes-data{ type: "application/json" }= initial_notes_data(autocomplete).to_json.html_safe diff --git a/config/feature_flags/development/advanced_search_multi_project_select.yml b/config/feature_flags/development/advanced_search_multi_project_select.yml index 4f38955fa71..8f74c8990fa 100644 --- a/config/feature_flags/development/advanced_search_multi_project_select.yml +++ b/config/feature_flags/development/advanced_search_multi_project_select.yml @@ -2,7 +2,7 @@ name: advanced_search_multi_project_select introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62606 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/333011 -milestone: '14.0' +milestone: '14.4' type: development group: group::global search default_enabled: false diff --git a/config/feature_flags/development/ci_resource_group_process_modes.yml b/config/feature_flags/development/refactor_mr_widgets_extensions.yml index 12d2945c5cc..5b6ea22aafe 100644 --- a/config/feature_flags/development/ci_resource_group_process_modes.yml +++ b/config/feature_flags/development/refactor_mr_widgets_extensions.yml @@ -1,8 +1,8 @@ --- -name: ci_resource_group_process_modes -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67015 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340380 -milestone: '14.3' +name: refactor_mr_widgets_extensions +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70993 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/341759 +milestone: '14.4' type: development -group: group::release +group: group::code review default_enabled: false diff --git a/config/feature_flags/development/refactor_mr_widgets_extensions_user.yml b/config/feature_flags/development/refactor_mr_widgets_extensions_user.yml new file mode 100644 index 00000000000..aa3c2799100 --- /dev/null +++ b/config/feature_flags/development/refactor_mr_widgets_extensions_user.yml @@ -0,0 +1,8 @@ +--- +name: refactor_mr_widgets_extensions_user +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70993 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/341759 +milestone: '14.4' +type: development +group: group::code review +default_enabled: false diff --git a/config/feature_flags/development/reference_cache_memoization.yml b/config/feature_flags/development/reference_cache_memoization.yml index 795d9497f9d..74012208174 100644 --- a/config/feature_flags/development/reference_cache_memoization.yml +++ b/config/feature_flags/development/reference_cache_memoization.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/341849 milestone: '14.4' type: development group: group::source code -default_enabled: false +default_enabled: true diff --git a/doc/api/dora/metrics.md b/doc/api/dora/metrics.md index 8c82446db2e..4fbd2b0fa80 100644 --- a/doc/api/dora/metrics.md +++ b/doc/api/dora/metrics.md @@ -1,6 +1,6 @@ --- -stage: Release -group: Release +stage: Manage +group: Optimize info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments type: reference, api --- diff --git a/doc/api/dora4_project_analytics.md b/doc/api/dora4_project_analytics.md index 5a6e1576a3d..f69c918c6e2 100644 --- a/doc/api/dora4_project_analytics.md +++ b/doc/api/dora4_project_analytics.md @@ -1,6 +1,6 @@ --- -stage: Release -group: Release +stage: Manage +group: Optimize info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments type: reference, api --- diff --git a/doc/ci/resource_groups/index.md b/doc/ci/resource_groups/index.md index 06b715efc9c..7de3643c0d4 100644 --- a/doc/ci/resource_groups/index.md +++ b/doc/ci/resource_groups/index.md @@ -66,13 +66,9 @@ Only one resource can be attached to a resource group. ## Process modes -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202186) in GitLab 14.3. - -FLAG: -On self-managed GitLab, by default this feature is not available. -To make it available, ask an administrator to [enable the `ci_resource_group_process_modes` flag](../../administration/feature_flags.md). -On GitLab.com, this feature is not available. -The feature is not ready for production use. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202186) in GitLab 14.3. +> - [Feature flag `ci_resource_group_process_modes`](https://gitlab.com/gitlab-org/gitlab/-/issues/340380) removed in GitLab 14.4. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/202186) in GitLab 14.4. You can choose a process mode to strategically control the job concurrency for your deployment preferences. The following modes are supported: diff --git a/doc/development/redis.md b/doc/development/redis.md index a04ca94ad91..063e1b8d53d 100644 --- a/doc/development/redis.md +++ b/doc/development/redis.md @@ -15,7 +15,6 @@ GitLab uses [Redis](https://redis.io) for the following distinct purposes: - To manage the shared application state. - To store CI trace chunks. - As a Pub/Sub queue backend for ActionCable. -- CI trace chunk storage. - Rate limiting state storage. In most environments (including the GDK), all of these point to the same diff --git a/haml_lint/inline_javascript.rb b/haml_lint/inline_javascript.rb index da6af92e82b..c87d77d7a4b 100644 --- a/haml_lint/inline_javascript.rb +++ b/haml_lint/inline_javascript.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true unless Rails.env.production? - require_dependency 'haml_lint/haml_visitor' - require_dependency 'haml_lint/linter' - require_dependency 'haml_lint/linter_registry' + require 'haml_lint/haml_visitor' + require 'haml_lint/linter' + require 'haml_lint/linter_registry' module HamlLint class Linter::InlineJavaScript < Linter diff --git a/lib/api/ci/resource_groups.rb b/lib/api/ci/resource_groups.rb index 02a5e92bd83..616bec499d4 100644 --- a/lib/api/ci/resource_groups.rb +++ b/lib/api/ci/resource_groups.rb @@ -32,7 +32,6 @@ module API values: ::Ci::ResourceGroup.process_modes.keys end put ':id/resource_groups/:key' do - not_found! unless ::Feature.enabled?(:ci_resource_group_process_modes, user_project, default_enabled: :yaml) authorize! :update_resource_group, resource_group if resource_group.update(declared_params(include_missing: false)) diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 395aacced78..f018b421edd 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -24,7 +24,7 @@ module API use :pagination end get ':id/repository/tags', feature_category: :source_code_management do - tags = ::TagsFinder.new(user_project.repository, + tags, _ = ::TagsFinder.new(user_project.repository, sort: "#{params[:order_by]}_#{params[:sort]}", search: params[:search]).execute diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5184145ef0e..6b62fe35b3d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6252,9 +6252,6 @@ msgstr "" msgid "Cannot assign a confidential epic to a non-confidential issue. Make the issue confidential and try again" msgstr "" -msgid "Cannot be assigned to other projects." -msgstr "" - msgid "Cannot be merged automatically" msgstr "" @@ -23169,9 +23166,6 @@ msgstr "" msgid "Not available for protected branches" msgstr "" -msgid "Not available to run jobs." -msgstr "" - msgid "Not confidential" msgstr "" @@ -29214,6 +29208,15 @@ msgstr "" msgid "Runners|Are you sure you want to delete this runner?" msgstr "" +msgid "Runners|Associated with one or more projects" +msgstr "" + +msgid "Runners|Available to all projects" +msgstr "" + +msgid "Runners|Available to all projects and subgroups in the group" +msgstr "" + msgid "Runners|Can run untagged jobs" msgstr "" @@ -29274,6 +29277,9 @@ msgstr "" msgid "Runners|New runner, has not connected yet" msgstr "" +msgid "Runners|Not available to run jobs" +msgstr "" + msgid "Runners|Not connected" msgstr "" @@ -29397,6 +29403,9 @@ msgstr "" msgid "Runners|You can set up a specific runner to be used by multiple projects but you cannot make this a shared runner." msgstr "" +msgid "Runners|You cannot assign to other projects" +msgstr "" + msgid "Runners|You have used %{quotaUsed} out of %{quotaLimit} of your shared Runners pipeline minutes." msgstr "" @@ -29427,15 +29436,6 @@ msgstr "" msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects." msgstr "" -msgid "Runs jobs from all unassigned projects in its group." -msgstr "" - -msgid "Runs jobs from all unassigned projects." -msgstr "" - -msgid "Runs jobs from assigned projects." -msgstr "" - msgid "SAML" msgstr "" @@ -33197,6 +33197,9 @@ msgstr "" msgid "TagsPage|This tag has no release notes." msgstr "" +msgid "TagsPage|Unable to load tags" +msgstr "" + msgid "TagsPage|Use git tag command to add a new one:" msgstr "" @@ -33779,6 +33782,9 @@ msgstr "" msgid "The form contains the following warning:" msgstr "" +msgid "The git server, Gitaly, is not available at this time. Please contact your administrator." +msgstr "" + msgid "The global settings require you to enable Two-Factor Authentication for your account." msgstr "" @@ -39775,6 +39781,11 @@ msgstr "" msgid "ciReport|Investigate this vulnerability by creating an issue" msgstr "" +msgid "ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} change" +msgid_plural "ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} changes" +msgstr[0] "" +msgstr[1] "" + msgid "ciReport|Load performance test metrics: " msgstr "" diff --git a/package.json b/package.json index 0ebe0245078..2c095f06e96 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", - "@gitlab/svgs": "1.213.0", + "@gitlab/svgs": "1.215.0", "@gitlab/tributejs": "1.0.0", - "@gitlab/ui": "32.15.0", + "@gitlab/ui": "32.18.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "6.1.4-1", "@rails/ujs": "6.1.4-1", diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb index a483a8e0462..953c26ed037 100644 --- a/qa/qa/resource/group_base.rb +++ b/qa/qa/resource/group_base.rb @@ -14,6 +14,22 @@ module QA :name, :full_path + # Get group projects + # + # @return [Array<QA::Resource::Project>] + def projects + parse_body(api_get_from("#{api_get_path}/projects")).map do |project| + Project.init do |resource| + resource.api_client = api_client + resource.group = self + resource.id = project[:id] + resource.name = project[:name] + resource.description = project[:description] + resource.path_with_namespace = project[:path_with_namespace] + end + end + end + # Get group labels # # @return [Array<QA::Resource::GroupLabel>] diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 01cb68d64bf..3f6a4eee5ac 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -358,6 +358,46 @@ module QA parse_body(response) end + # Object comparison + # + # @param [QA::Resource::Project] other + # @return [Boolean] + def ==(other) + other.is_a?(Project) && comparable_project == other.comparable_project + end + + # Override inspect for a better rspec failure diff output + # + # @return [String] + def inspect + JSON.pretty_generate(comparable_project) + end + + protected + + # Return subset of fields for comparing projects + # + # @return [Hash] + def comparable_project + reload! if api_response.nil? + + api_resource.slice( + :name, + :path, + :description, + :tag_list, + :archived, + :issues_enabled, + :merge_request_enabled, + :wiki_enabled, + :jobs_enabled, + :snippets_enabled, + :shared_runners_enabled, + :request_access_enabled, + :avatar_url + ) + end + private def transform_api_resource(api_resource) diff --git a/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb b/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb new file mode 100644 index 00000000000..b88fdfdc757 --- /dev/null +++ b/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage', :requires_admin do + describe 'Bulk project import' do + let!(:staging?) { Runtime::Scenario.gitlab_address.include?('staging.gitlab.com') } + + let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } } + let(:admin_api_client) { Runtime::API::Client.as_admin } + let(:user) do + Resource::User.fabricate_via_api! do |usr| + usr.api_client = admin_api_client + usr.hard_delete_on_api_removal = true + end + end + + let(:api_client) { Runtime::API::Client.new(user: user) } + + let(:sandbox) do + Resource::Sandbox.fabricate_via_api! do |group| + group.api_client = admin_api_client + end + end + + let(:source_group) do + Resource::Sandbox.fabricate_via_api! do |group| + group.api_client = api_client + group.path = "source-group-for-import-#{SecureRandom.hex(4)}" + end + end + + let(:source_project) do + Resource::Project.fabricate_via_api! do |project| + project.api_client = api_client + project.group = source_group + end + end + + let(:imported_group) do + Resource::BulkImportGroup.fabricate_via_api! do |group| + group.api_client = api_client + group.sandbox = sandbox + group.source_group_path = source_group.path + end + end + + before do + Runtime::Feature.enable(:bulk_import_projects) + Runtime::Feature.enable(:top_level_group_creation_enabled) if staging? + + sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER) + + source_project # fabricate source group and project + end + + after do + user.remove_via_api! + ensure + Runtime::Feature.disable(:bulk_import_projects) + Runtime::Feature.disable(:top_level_group_creation_enabled) if staging? + end + + context 'with project' do + it 'successfully imports project' do + expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration) + + imported_projects = imported_group.reload!.projects + aggregate_failures do + expect(imported_projects.count).to eq(1) + expect(imported_projects.first).to eq(source_project) + end + end + end + end + end +end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index efb57494f82..d0719643b7f 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -17,6 +17,25 @@ RSpec.describe Projects::TagsController do expect(assigns(:tags).map(&:name)).to include('v1.1.0', 'v1.0.0') end + context 'when Gitaly is unavailable' do + where(:format) do + [:html, :atom] + end + + with_them do + it 'returns 503 status code' do + expect_next_instance_of(TagsFinder) do |finder| + expect(finder).to receive(:execute).and_return([[], Gitlab::Git::CommandError.new]) + end + + get :index, params: { namespace_id: project.namespace.to_param, project_id: project }, format: format + + expect(assigns(:tags)).to eq([]) + expect(response).to have_gitlab_http_status(:service_unavailable) + end + end + end + it 'returns releases matching those tags' do subject diff --git a/spec/features/issues/related_issues_spec.rb b/spec/features/issues/related_issues_spec.rb index a8933ed9c30..a95229d4f1b 100644 --- a/spec/features/issues/related_issues_spec.rb +++ b/spec/features/issues/related_issues_spec.rb @@ -231,8 +231,8 @@ RSpec.describe 'Related issues', :js do it 'add related issue' do click_button 'Add a related issue' - find('.js-add-issuable-form-input').set "#{issue_b.to_reference(project)} " - find('.js-add-issuable-form-add-button').click + fill_in 'Paste issue link', with: "#{issue_b.to_reference(project)} " + click_button 'Add' wait_for_requests @@ -248,8 +248,8 @@ RSpec.describe 'Related issues', :js do it 'add cross-project related issue' do click_button 'Add a related issue' - find('.js-add-issuable-form-input').set "#{issue_project_b_a.to_reference(project)} " - find('.js-add-issuable-form-add-button').click + fill_in 'Paste issue link', with: "#{issue_project_b_a.to_reference(project)} " + click_button 'Add' wait_for_requests @@ -262,8 +262,8 @@ RSpec.describe 'Related issues', :js do it 'pressing enter should submit the form' do click_button 'Add a related issue' - find('.js-add-issuable-form-input').set "#{issue_project_b_a.to_reference(project)} " - find('.js-add-issuable-form-input').native.send_key(:enter) + fill_in 'Paste issue link', with: "#{issue_project_b_a.to_reference(project)} " + find_field('Paste issue link').native.send_key(:enter) wait_for_requests @@ -276,9 +276,9 @@ RSpec.describe 'Related issues', :js do it 'disallows duplicate entries' do click_button 'Add a related issue' - find('.js-add-issuable-form-input').set 'duplicate duplicate duplicate' + fill_in 'Paste issue link', with: 'duplicate duplicate duplicate' - items = all('.js-add-issuable-form-token-list-item') + items = all('.issue-token') expect(items.count).to eq(1) expect(items[0].text).to eq('duplicate') @@ -289,28 +289,34 @@ RSpec.describe 'Related issues', :js do it 'allows us to remove pending issues' do # Tests against https://gitlab.com/gitlab-org/gitlab/issues/11625 click_button 'Add a related issue' - find('.js-add-issuable-form-input').set 'issue1 issue2 issue3 ' + fill_in 'Paste issue link', with: 'issue1 issue2 issue3 ' - items = all('.js-add-issuable-form-token-list-item') + items = all('.issue-token') expect(items.count).to eq(3) expect(items[0].text).to eq('issue1') expect(items[1].text).to eq('issue2') expect(items[2].text).to eq('issue3') # Remove pending issues left to right to make sure none get stuck - items[0].find('.js-issue-token-remove-button').click - items = all('.js-add-issuable-form-token-list-item') + within items[0] do + click_button 'Remove' + end + items = all('.issue-token') expect(items.count).to eq(2) expect(items[0].text).to eq('issue2') expect(items[1].text).to eq('issue3') - items[0].find('.js-issue-token-remove-button').click - items = all('.js-add-issuable-form-token-list-item') + within items[0] do + click_button 'Remove' + end + items = all('.issue-token') expect(items.count).to eq(1) expect(items[0].text).to eq('issue3') - items[0].find('.js-issue-token-remove-button').click - items = all('.js-add-issuable-form-token-list-item') + within items[0] do + click_button 'Remove' + end + items = all('.issue-token') expect(items.count).to eq(0) end end @@ -352,8 +358,8 @@ RSpec.describe 'Related issues', :js do it 'add related issue' do click_button 'Add a related issue' - find('.js-add-issuable-form-input').set "##{issue_d.iid} " - find('.js-add-issuable-form-add-button').click + fill_in 'Paste issue link', with: "##{issue_d.iid} " + click_button 'Add' wait_for_requests @@ -368,8 +374,8 @@ RSpec.describe 'Related issues', :js do it 'add invalid related issue' do click_button 'Add a related issue' - find('.js-add-issuable-form-input').set "#9999999 " - find('.js-add-issuable-form-add-button').click + fill_in 'Paste issue link', with: '#9999999 ' + click_button 'Add' wait_for_requests @@ -383,8 +389,8 @@ RSpec.describe 'Related issues', :js do it 'add unauthorized related issue' do click_button 'Add a related issue' - find('.js-add-issuable-form-input').set "#{issue_project_unauthorized_a.to_reference(project)} " - find('.js-add-issuable-form-add-button').click + fill_in 'Paste issue link', with: "#{issue_project_unauthorized_a.to_reference(project)} " + click_button 'Add' wait_for_requests diff --git a/spec/finders/ci/commit_statuses_finder_spec.rb b/spec/finders/ci/commit_statuses_finder_spec.rb index 2e26e38f4b4..9f66b53dd1f 100644 --- a/spec/finders/ci/commit_statuses_finder_spec.rb +++ b/spec/finders/ci/commit_statuses_finder_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Ci::CommitStatusesFinder, '#execute' do let_it_be(:user) { create(:user) } context 'tag refs' do - let_it_be(:tags) { TagsFinder.new(project.repository, {}).execute } + let_it_be(:tags) { project.repository.tags } let(:subject) { described_class.new(project, project.repository, user, tags).execute } @@ -131,7 +131,7 @@ RSpec.describe Ci::CommitStatusesFinder, '#execute' do end context 'CI pipelines visible to' do - let_it_be(:tags) { TagsFinder.new(project.repository, {}).execute } + let_it_be(:tags) { project.repository.tags } let(:subject) { described_class.new(project, project.repository, user, tags).execute } @@ -161,7 +161,7 @@ RSpec.describe Ci::CommitStatusesFinder, '#execute' do context 'when not a member of a private project' do let(:private_project) { create(:project, :private, :repository) } - let(:private_tags) { TagsFinder.new(private_tags.repository, {}).execute } + let(:private_tags) { private_tags.repository.tags } let(:private_subject) { described_class.new(private_project, private_project.repository, user, tags).execute } before do diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb index 08978a32e50..fe015d53ac9 100644 --- a/spec/finders/tags_finder_spec.rb +++ b/spec/finders/tags_finder_spec.rb @@ -3,93 +3,76 @@ require 'spec_helper' RSpec.describe TagsFinder do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:repository) { project.repository } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:repository) { project.repository } + + def load_tags(params) + tags_finder = described_class.new(repository, params) + tags, error = tags_finder.execute + + expect(error).to eq(nil) + + tags + end describe '#execute' do context 'sort only' do it 'sorts by name' do - tags_finder = described_class.new(repository, {}) - - result = tags_finder.execute - - expect(result.first.name).to eq("v1.0.0") + expect(load_tags({}).first.name).to eq("v1.0.0") end it 'sorts by recently_updated' do - tags_finder = described_class.new(repository, { sort: 'updated_desc' }) - - result = tags_finder.execute recently_updated_tag = repository.tags.max do |a, b| repository.commit(a.dereferenced_target).committed_date <=> repository.commit(b.dereferenced_target).committed_date end - expect(result.first.name).to eq(recently_updated_tag.name) + params = { sort: 'updated_desc' } + + expect(load_tags(params).first.name).to eq(recently_updated_tag.name) end it 'sorts by last_updated' do - tags_finder = described_class.new(repository, { sort: 'updated_asc' }) - - result = tags_finder.execute + params = { sort: 'updated_asc' } - expect(result.first.name).to eq('v1.0.0') + expect(load_tags(params).first.name).to eq('v1.0.0') end end context 'filter only' do it 'filters tags by name' do - tags_finder = described_class.new(repository, { search: '1.0.0' }) - - result = tags_finder.execute + result = load_tags({ search: '1.0.0' }) expect(result.first.name).to eq('v1.0.0') expect(result.count).to eq(1) end it 'does not find any tags with that name' do - tags_finder = described_class.new(repository, { search: 'hey' }) - - result = tags_finder.execute - - expect(result.count).to eq(0) + expect(load_tags({ search: 'hey' }).count).to eq(0) end it 'filters tags by name that begins with' do - params = { search: '^v1.0' } - tags_finder = described_class.new(repository, params) - - result = tags_finder.execute + result = load_tags({ search: '^v1.0' }) expect(result.first.name).to eq('v1.0.0') expect(result.count).to eq(1) end it 'filters tags by name that ends with' do - params = { search: '0.0$' } - tags_finder = described_class.new(repository, params) - - result = tags_finder.execute + result = load_tags({ search: '0.0$' }) expect(result.first.name).to eq('v1.0.0') expect(result.count).to eq(1) end it 'filters tags by nonexistent name that begins with' do - params = { search: '^nope' } - tags_finder = described_class.new(repository, params) - - result = tags_finder.execute + result = load_tags({ search: '^nope' }) expect(result.count).to eq(0) end it 'filters tags by nonexistent name that ends with' do - params = { search: 'nope$' } - tags_finder = described_class.new(repository, params) - - result = tags_finder.execute - + result = load_tags({ search: 'nope$' }) expect(result.count).to eq(0) end end @@ -97,7 +80,7 @@ RSpec.describe TagsFinder do context 'filter and sort' do let(:tags_to_compare) { %w[v1.0.0 v1.1.0] } - subject { described_class.new(repository, params).execute.select { |tag| tags_to_compare.include?(tag.name) } } + subject { load_tags(params).select { |tag| tags_to_compare.include?(tag.name) } } context 'when sort by updated_desc' do let(:params) { { sort: 'updated_desc', search: 'v1' } } @@ -117,5 +100,17 @@ RSpec.describe TagsFinder do end end end + + context 'when Gitaly is unavailable' do + it 'returns empty list of tags' do + expect(Gitlab::GitalyClient).to receive(:call).and_raise(GRPC::Unavailable) + + tags_finder = described_class.new(repository, {}) + tags, error = tags_finder.execute + + expect(error).to be_a(Gitlab::Git::CommandError) + expect(tags).to eq([]) + end + end end end diff --git a/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js b/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js index 173d12757e3..ff6922989cb 100644 --- a/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js +++ b/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js @@ -1,5 +1,6 @@ import { mount, shallowMount } from '@vue/test-utils'; import AddIssuableForm from '~/related_issues/components/add_issuable_form.vue'; +import IssueToken from '~/related_issues/components/issue_token.vue'; import { issuableTypesMap, linkedIssueTypesMap, PathIdSeparator } from '~/related_issues/constants'; const issuable1 = { @@ -22,7 +23,7 @@ const issuable2 = { const pathIdSeparator = PathIdSeparator.Issue; -const findFormInput = (wrapper) => wrapper.find('.js-add-issuable-form-input').element; +const findFormInput = (wrapper) => wrapper.find('input').element; const findRadioInput = (inputs, value) => inputs.filter((input) => input.element.value === value)[0]; @@ -105,11 +106,11 @@ describe('AddIssuableForm', () => { }); it('should put input value in place', () => { - expect(findFormInput(wrapper).value).toEqual(inputValue); + expect(findFormInput(wrapper).value).toBe(inputValue); }); it('should render pending issuables items', () => { - expect(wrapper.findAll('.js-add-issuable-form-token-list-item').length).toEqual(2); + expect(wrapper.findAllComponents(IssueToken)).toHaveLength(2); }); it('should not have disabled submit button', () => { diff --git a/spec/frontend/runner/components/runner_state_locked_badge_spec.js b/spec/frontend/runner/components/runner_state_locked_badge_spec.js new file mode 100644 index 00000000000..e92b671f5a1 --- /dev/null +++ b/spec/frontend/runner/components/runner_state_locked_badge_spec.js @@ -0,0 +1,45 @@ +import { GlBadge } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import RunnerStateLockedBadge from '~/runner/components/runner_state_locked_badge.vue'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; + +describe('RunnerTypeBadge', () => { + let wrapper; + + const findBadge = () => wrapper.findComponent(GlBadge); + const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip'); + + const createComponent = ({ props = {} } = {}) => { + wrapper = shallowMount(RunnerStateLockedBadge, { + propsData: { + ...props, + }, + directives: { + GlTooltip: createMockDirective(), + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders locked state', () => { + expect(wrapper.text()).toBe('locked'); + expect(findBadge().props('variant')).toBe('warning'); + }); + + it('renders tooltip', () => { + expect(getTooltip().value).toBeDefined(); + }); + + it('passes arbitrary attributes to the badge', () => { + createComponent({ props: { size: 'sm' } }); + + expect(findBadge().props('size')).toBe('sm'); + }); +}); diff --git a/spec/frontend/runner/components/runner_state_paused_badge_spec.js b/spec/frontend/runner/components/runner_state_paused_badge_spec.js new file mode 100644 index 00000000000..8df56d6e3f3 --- /dev/null +++ b/spec/frontend/runner/components/runner_state_paused_badge_spec.js @@ -0,0 +1,45 @@ +import { GlBadge } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import RunnerStatePausedBadge from '~/runner/components/runner_state_paused_badge.vue'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; + +describe('RunnerTypeBadge', () => { + let wrapper; + + const findBadge = () => wrapper.findComponent(GlBadge); + const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip'); + + const createComponent = ({ props = {} } = {}) => { + wrapper = shallowMount(RunnerStatePausedBadge, { + propsData: { + ...props, + }, + directives: { + GlTooltip: createMockDirective(), + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders paused state', () => { + expect(wrapper.text()).toBe('paused'); + expect(findBadge().props('variant')).toBe('danger'); + }); + + it('renders tooltip', () => { + expect(getTooltip().value).toBeDefined(); + }); + + it('passes arbitrary attributes to the badge', () => { + createComponent({ props: { size: 'sm' } }); + + expect(findBadge().props('size')).toBe('sm'); + }); +}); diff --git a/spec/frontend/runner/components/runner_type_badge_spec.js b/spec/frontend/runner/components/runner_type_badge_spec.js index ab5ccf6390f..fb344e65389 100644 --- a/spec/frontend/runner/components/runner_type_badge_spec.js +++ b/spec/frontend/runner/components/runner_type_badge_spec.js @@ -1,18 +1,23 @@ import { GlBadge } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import RunnerTypeBadge from '~/runner/components/runner_type_badge.vue'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants'; describe('RunnerTypeBadge', () => { let wrapper; const findBadge = () => wrapper.findComponent(GlBadge); + const getTooltip = () => getBinding(findBadge().element, 'gl-tooltip'); const createComponent = ({ props = {} } = {}) => { wrapper = shallowMount(RunnerTypeBadge, { propsData: { ...props, }, + directives: { + GlTooltip: createMockDirective(), + }, }); }; @@ -20,16 +25,24 @@ describe('RunnerTypeBadge', () => { wrapper.destroy(); }); - it.each` + describe.each` type | text | variant ${INSTANCE_TYPE} | ${'shared'} | ${'success'} ${GROUP_TYPE} | ${'group'} | ${'success'} ${PROJECT_TYPE} | ${'specific'} | ${'info'} - `('displays $type runner with as "$text" with a $variant variant ', ({ type, text, variant }) => { - createComponent({ props: { type } }); + `('displays $type runner', ({ type, text, variant }) => { + beforeEach(() => { + createComponent({ props: { type } }); + }); - expect(findBadge().text()).toBe(text); - expect(findBadge().props('variant')).toBe(variant); + it(`as "${text}" with a ${variant} variant`, () => { + expect(findBadge().text()).toBe(text); + expect(findBadge().props('variant')).toBe(variant); + }); + + it('with a tooltip', () => { + expect(getTooltip().value).toBeDefined(); + }); }); it('validation fails for an incorrect type', () => { diff --git a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb index 2c9da0f6606..e551dfaa1c5 100644 --- a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb +++ b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb @@ -12,6 +12,7 @@ RSpec.describe Gitlab::Git::WrapsGitalyErrors do mapping = { GRPC::NotFound => Gitlab::Git::Repository::NoRepository, GRPC::InvalidArgument => ArgumentError, + GRPC::DeadlineExceeded => Gitlab::Git::CommandTimedOut, GRPC::BadStatus => Gitlab::Git::CommandError } diff --git a/spec/models/ci/resource_group_spec.rb b/spec/models/ci/resource_group_spec.rb index 18ee2d3d7fa..aae16157fbf 100644 --- a/spec/models/ci/resource_group_spec.rb +++ b/spec/models/ci/resource_group_spec.rb @@ -117,14 +117,6 @@ RSpec.describe Ci::ResourceGroup do expect(subject[3]).to eq(build_2_waiting_for_resource) expect(subject[4..5]).to contain_exactly(build_2_created, build_2_scheduled) end - - context 'when ci_resource_group_process_modes feature flag is disabled' do - it 'returns correct jobs in an indeterministic order' do - stub_feature_flags(ci_resource_group_process_modes: false) - - expect(subject).to contain_exactly(build_1_waiting_for_resource, build_2_waiting_for_resource) - end - end end context 'when process mode is newest_first' do @@ -136,14 +128,6 @@ RSpec.describe Ci::ResourceGroup do expect(subject[3]).to eq(build_1_waiting_for_resource) expect(subject[4..5]).to contain_exactly(build_1_created, build_1_scheduled) end - - context 'when ci_resource_group_process_modes feature flag is disabled' do - it 'returns correct jobs in an indeterministic order' do - stub_feature_flags(ci_resource_group_process_modes: false) - - expect(subject).to contain_exactly(build_1_waiting_for_resource, build_2_waiting_for_resource) - end - end end context 'when process mode is unknown' do diff --git a/spec/requests/api/ci/resource_groups_spec.rb b/spec/requests/api/ci/resource_groups_spec.rb index c48b7cd769c..f5b68557a0d 100644 --- a/spec/requests/api/ci/resource_groups_spec.rb +++ b/spec/requests/api/ci/resource_groups_spec.rb @@ -62,18 +62,6 @@ RSpec.describe API::Ci::ResourceGroups do expect(json_response['process_mode']).to eq('oldest_first') end - context 'when ci_resource_group_process_modes feature flag is disabled' do - before do - stub_feature_flags(ci_resource_group_process_modes: false) - end - - it 'returns not found' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - context 'with invalid parameter' do let(:params) { { process_mode: :unknown } } diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb index 2702ab9e2a9..ebd526284d1 100644 --- a/spec/views/projects/tags/index.html.haml_spec.rb +++ b/spec/views/projects/tags/index.html.haml_spec.rb @@ -3,10 +3,11 @@ require 'spec_helper' RSpec.describe 'projects/tags/index.html.haml' do - let(:project) { create(:project, :repository) } - let(:tags) { TagsFinder.new(project.repository, {}).execute } - let(:git_tag) { project.repository.tags.last } - let(:release) { create(:release, project: project, sha: git_tag.target_commit.sha) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:tags) { project.repository.tags } + let_it_be(:git_tag) { project.repository.tags.last } + let_it_be(:release) { create(:release, project: project, sha: git_tag.target_commit.sha) } + let(:pipeline) { create(:ci_pipeline, :success, project: project, ref: git_tag.name, sha: release.sha) } before do @@ -86,4 +87,17 @@ RSpec.describe 'projects/tags/index.html.haml' do expect(page.all('.tags .content-list li')).not_to have_css 'svg.s24' end end + + context 'when Gitaly is unavailable' do + it 'renders an error' do + assign(:tags_loading_error, GRPC::Unavailable.new) + + content = render + + expect(content).to include("Unable to load tags") + expect(content).to include( + "The git server, Gitaly, is not available at this time. Please contact your administrator." + ) + end + end end diff --git a/yarn.lock b/yarn.lock index 0214d4be47b..d7d01640be6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -904,20 +904,20 @@ stylelint-declaration-strict-value "1.7.7" stylelint-scss "3.18.0" -"@gitlab/svgs@1.213.0": - version "1.213.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.213.0.tgz#fcd9794049d2b15f5796dbab2a3d501679153582" - integrity sha512-3d9EGpEkPDeW92Xx3FueFCJFZ/yL+uv5MWCUHmSt1tP9YmUhMXw/51c43c5+V17FuCyvhJS5tm3aEg3VYoWIRA== +"@gitlab/svgs@1.215.0": + version "1.215.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.215.0.tgz#f2760bbb0a38b26346e1b755e63fb63eba005edd" + integrity sha512-/bc0+EOYPQlPCMbfyOkMLxDKBn+ewEBlmTRmFwf7mXvfIRszdJPY8XCx/fJIEQwDr8+k4E28ktFnLZGnaFhCnw== "@gitlab/tributejs@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== -"@gitlab/ui@32.15.0": - version "32.15.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.15.0.tgz#292518f1c52ef22d73cfded9d6f9f6d48a47efee" - integrity sha512-n7SwTA5Je+s/66cTXBhRdlxiJILGA+mXX6aevUFXzFvTn4a4yVb1wx4mmaMA/EB3vUxb8+u/z61CYZ4pd5JIbw== +"@gitlab/ui@32.18.0": + version "32.18.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.18.0.tgz#cd340f050fe0183218f6233328aca2369bd6e449" + integrity sha512-bDMmsNB9VMBX2JbezyJWfk02t0aFfAT9Ez4ALTDUJLb5/Q9GKByfE5sLycms6L1aZxzP6r1jypnu5DD0eT92eg== dependencies: "@babel/standalone" "^7.0.0" bootstrap-vue "2.19.0" |