summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/Feature Flag Roll Out.md49
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/related_issues/components/add_issuable_form.vue2
-rw-r--r--app/assets/javascripts/related_issues/components/issue_token.vue1
-rw-r--r--app/assets/javascripts/related_issues/components/related_issuable_input.vue27
-rw-r--r--app/assets/javascripts/repository/components/preview/index.vue11
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_type_cell.vue25
-rw-r--r--app/assets/javascripts/runner/components/runner_state_locked_badge.vue25
-rw-r--r--app/assets/javascripts/runner/components/runner_state_paused_badge.vue25
-rw-r--r--app/assets/javascripts/runner/components/runner_type_badge.vue19
-rw-r--r--app/assets/javascripts/runner/components/runner_type_help.vue42
-rw-r--r--app/assets/javascripts/runner/constants.js8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss33
-rw-r--r--app/controllers/projects/tags_controller.rb12
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/finders/tags_finder.rb5
-rw-r--r--app/models/ci/resource_group.rb2
-rw-r--r--app/views/authentication/_authenticate.html.haml3
-rw-r--r--app/views/authentication/_register.html.haml4
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml1
-rw-r--r--app/views/projects/branches/new.html.haml1
-rw-r--r--app/views/projects/merge_requests/show.html.haml1
-rw-r--r--app/views/projects/product_analytics/test.html.haml1
-rw-r--r--app/views/projects/tags/index.html.haml3
-rw-r--r--app/views/projects/tags/new.html.haml1
-rw-r--r--app/views/shared/errors/_gitaly_unavailable.html.haml8
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml9
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml1
-rw-r--r--config/feature_flags/development/advanced_search_multi_project_select.yml2
-rw-r--r--config/feature_flags/development/refactor_mr_widgets_extensions.yml (renamed from config/feature_flags/development/ci_resource_group_process_modes.yml)10
-rw-r--r--config/feature_flags/development/refactor_mr_widgets_extensions_user.yml8
-rw-r--r--config/feature_flags/development/reference_cache_memoization.yml2
-rw-r--r--doc/api/dora/metrics.md4
-rw-r--r--doc/api/dora4_project_analytics.md4
-rw-r--r--doc/ci/resource_groups/index.md10
-rw-r--r--doc/development/redis.md1
-rw-r--r--haml_lint/inline_javascript.rb6
-rw-r--r--lib/api/ci/resource_groups.rb1
-rw-r--r--lib/api/tags.rb2
-rw-r--r--locale/gitlab.pot41
-rw-r--r--package.json4
-rw-r--r--qa/qa/resource/group_base.rb16
-rw-r--r--qa/qa/resource/project.rb40
-rw-r--r--qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb76
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb19
-rw-r--r--spec/features/issues/related_issues_spec.rb50
-rw-r--r--spec/finders/ci/commit_statuses_finder_spec.rb6
-rw-r--r--spec/finders/tags_finder_spec.rb79
-rw-r--r--spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js7
-rw-r--r--spec/frontend/runner/components/runner_state_locked_badge_spec.js45
-rw-r--r--spec/frontend/runner/components/runner_state_paused_badge_spec.js45
-rw-r--r--spec/frontend/runner/components/runner_type_badge_spec.js23
-rw-r--r--spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb1
-rw-r--r--spec/models/ci/resource_group_spec.rb16
-rw-r--r--spec/requests/api/ci/resource_groups_spec.rb12
-rw-r--r--spec/views/projects/tags/index.html.haml_spec.rb22
-rw-r--r--yarn.lock16
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"