Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue13
-rw-r--r--app/assets/javascripts/api.js15
-rw-r--r--app/assets/javascripts/badges/components/badge.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue18
-rw-r--r--app/assets/javascripts/boards/components/toggle_focus.vue1
-rw-r--r--app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue10
-rw-r--r--app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js23
-rw-r--r--app/assets/javascripts/design_management/components/toolbar/design_navigation.vue10
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue1
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue1
-rw-r--r--app/assets/javascripts/issuable/components/issuable_by_email.vue6
-rw-r--r--app/assets/javascripts/issuable_show/components/issuable_title.vue7
-rw-r--r--app/assets/javascripts/issue_show/components/title.vue7
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_header.vue7
-rw-r--r--app/assets/javascripts/monitoring/components/refresh_button.vue8
-rw-r--r--app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue7
-rw-r--r--app/assets/javascripts/notes/components/sort_discussion.vue11
-rw-r--r--app/assets/javascripts/registry/settings/components/settings_form.vue14
-rw-r--r--app/assets/javascripts/releases/components/release_block_header.vue7
-rw-r--r--app/assets/javascripts/search/sort/components/app.vue1
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue8
-rw-r--r--app/assets/javascripts/snippets/components/embed_dropdown.vue1
-rw-r--r--app/assets/javascripts/static_site_editor/graphql/index.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/clone_dropdown.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/modal_copy_button.vue1
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/serializers/pipeline_serializer.rb1
-rw-r--r--app/views/groups/settings/_permanent_deletion.html.haml3
-rw-r--r--app/views/groups/settings/_remove_button.html.haml7
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml2
-rw-r--r--changelogs/unreleased/321788-drop-unused-preload.yml5
-rw-r--r--changelogs/unreleased/325429-container-registry-cleanup-policy-wiped-all-images.yml5
-rw-r--r--changelogs/unreleased/325812-update-the-package-settings-to-use-the-blue-primary-button.yml5
-rw-r--r--changelogs/unreleased/cngo-add-aria-labels-to-icon-buttons.yml5
-rw-r--r--changelogs/unreleased/id-n-1-for-jira-pulls.yml5
-rw-r--r--config/feature_flags/ops/usage_data_api.yml (renamed from config/feature_flags/development/usage_data_api.yml)4
-rw-r--r--doc/administration/geo/glossary.md2
-rw-r--r--doc/administration/geo/replication/version_specific_updates.md2
-rw-r--r--doc/administration/geo/setup/index.md2
-rw-r--r--doc/ci/yaml/README.md64
-rw-r--r--doc/development/usage_ping/dictionary.md48
-rw-r--r--doc/user/project/issues/managing_issues.md11
-rw-r--r--lib/api/usage_data.rb2
-rw-r--r--lib/api/v3/github.rb7
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--lib/gitlab/usage_data_counters/known_events/epic_events.yml12
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/features/projects/settings/registry_settings_spec.rb4
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb10
-rw-r--r--spec/frontend/api_spec.js21
-rw-r--r--spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap2
-rw-r--r--spec/frontend/registry/settings/components/settings_form_spec.js82
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap2
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js16
-rw-r--r--spec/models/namespace_spec.rb6
-rw-r--r--spec/requests/api/v3/github_spec.rb50
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/support/shared_examples/features/error_tracking_shared_example.rb2
-rw-r--r--spec/views/groups/settings/_remove.html.haml_spec.rb17
62 files changed, 515 insertions, 129 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
index a071e011ec3..417ecd7f3b4 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
@@ -21,6 +21,8 @@ import {
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
export const i18n = {
+ deleteIntegration: s__('AlertSettings|Delete integration'),
+ editIntegration: s__('AlertSettings|Edit integration'),
title: s__('AlertsIntegrations|Current integrations'),
emptyState: s__('AlertsIntegrations|No integrations have been added yet'),
status: {
@@ -174,11 +176,16 @@ export default {
<template #cell(actions)="{ item }">
<gl-button-group class="gl-ml-3">
- <gl-button icon="settings" @click="editIntegration(item)" />
+ <gl-button
+ icon="settings"
+ :aria-label="$options.i18n.editIntegration"
+ @click="editIntegration(item)"
+ />
<gl-button
v-gl-modal.deleteIntegration
:disabled="item.type === $options.typeSet.prometheus"
icon="remove"
+ :aria-label="$options.i18n.deleteIntegration"
@click="setIntegrationToDelete(item)"
/>
</gl-button-group>
@@ -198,8 +205,8 @@ export default {
</gl-table>
<gl-modal
modal-id="deleteIntegration"
- :title="s__('AlertSettings|Delete integration')"
- :ok-title="s__('AlertSettings|Delete integration')"
+ :title="$options.i18n.deleteIntegration"
+ :ok-title="$options.i18n.deleteIntegration"
ok-variant="danger"
@ok="deleteIntegration"
>
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 8dc44231e26..0015044bb66 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -44,7 +44,7 @@ const Api = {
projectMilestonesPath: '/api/:version/projects/:id/milestones',
projectIssuePath: '/api/:version/projects/:id/issues/:issue_iid',
mergeRequestsPath: '/api/:version/merge_requests',
- groupLabelsPath: '/groups/:namespace_path/-/labels',
+ groupLabelsPath: '/api/:version/groups/:namespace_path/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
issuableTemplatesPath: '/:namespace_path/:project_path/templates/:type',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
@@ -402,18 +402,29 @@ const Api = {
newLabel(namespacePath, projectPath, data, callback) {
let url;
+ let payload;
if (projectPath) {
url = Api.buildUrl(Api.projectLabelsPath)
.replace(':namespace_path', namespacePath)
.replace(':project_path', projectPath);
+ payload = {
+ label: data,
+ };
} else {
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
+
+ // groupLabelsPath uses public API which accepts
+ // `name` and `color` props.
+ payload = {
+ name: data.title,
+ color: data.color,
+ };
}
return axios
.post(url, {
- label: data,
+ ...payload,
})
.then((res) => callback(res.data))
.catch((e) => callback(e.response.data));
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue
index 9e5d70075f3..309af368df9 100644
--- a/app/assets/javascripts/badges/components/badge.vue
+++ b/app/assets/javascripts/badges/components/badge.vue
@@ -1,7 +1,11 @@
<script>
import { GlLoadingIcon, GlTooltipDirective, GlIcon, GlButton } from '@gitlab/ui';
+import { s__ } from '~/locale';
export default {
+ i18n: {
+ buttonLabel: s__('Badges|Reload badge image'),
+ },
// name: 'Badge' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
// eslint-disable-next-line @gitlab/require-i18n-strings
name: 'Badge',
@@ -94,7 +98,8 @@ export default {
<gl-button
v-show="hasError"
v-gl-tooltip.hover
- :title="s__('Badges|Reload badge image')"
+ :title="$options.i18n.buttonLabel"
+ :aria-label="$options.i18n.buttonLabel"
category="tertiary"
variant="confirm"
type="button"
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 8c354c61fa2..e5eec70c544 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -163,6 +163,9 @@ export default {
currentMutation() {
return this.board.id ? updateBoardMutation : createBoardMutation;
},
+ deleteMutation() {
+ return destroyBoardMutation;
+ },
baseMutationVariables() {
const { board } = this;
const variables = {
@@ -244,17 +247,20 @@ export default {
return this.boardUpdateResponse(response.data);
},
+ async deleteBoard() {
+ await this.$apollo.mutate({
+ mutation: this.deleteMutation,
+ variables: {
+ id: fullBoardId(this.board.id),
+ },
+ });
+ },
async submit() {
if (this.board.name.length === 0) return;
this.isLoading = true;
if (this.isDeleteForm) {
try {
- await this.$apollo.mutate({
- mutation: destroyBoardMutation,
- variables: {
- id: fullBoardId(this.board.id),
- },
- });
+ await this.deleteBoard();
visitUrl(this.rootPath);
} catch {
Flash(this.$options.i18n.deleteErrorMessage);
diff --git a/app/assets/javascripts/boards/components/toggle_focus.vue b/app/assets/javascripts/boards/components/toggle_focus.vue
index dc0f8ffd0fd..87a1d517111 100644
--- a/app/assets/javascripts/boards/components/toggle_focus.vue
+++ b/app/assets/javascripts/boards/components/toggle_focus.vue
@@ -47,6 +47,7 @@ export default {
class="js-focus-mode-btn"
data-qa-selector="focus_mode_button"
:title="$options.i18n.toggleFocusMode"
+ :aria-label="$options.i18n.toggleFocusMode"
@click="toggleFocusMode"
/>
</div>
diff --git a/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue b/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue
index 0233ffaccdc..bc1e401d373 100644
--- a/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue
+++ b/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue
@@ -7,6 +7,10 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
export default {
+ i18n: {
+ editButton: s__('Pipelines|Edit'),
+ revokeButton: s__('Pipelines|Revoke'),
+ },
components: {
GlTable,
GlButton,
@@ -108,13 +112,15 @@ export default {
</template>
<template #cell(actions)="{ item }">
<gl-button
- :title="s__('Pipelines|Edit')"
+ :title="$options.i18n.editButton"
+ :aria-label="$options.i18n.editButton"
icon="pencil"
data-testid="edit-btn"
:href="item.editProjectTriggerPath"
/>
<gl-button
- :title="s__('Pipelines|Revoke')"
+ :title="$options.i18n.revokeButton"
+ :aria-label="$options.i18n.revokeButton"
icon="remove"
variant="warning"
:data-confirm="
diff --git a/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js b/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
index b1d486c5d66..8ca4dc587a8 100644
--- a/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
+++ b/app/assets/javascripts/deprecated_jquery_dropdown/gl_dropdown_filter.js
@@ -2,6 +2,7 @@
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import $ from 'jquery';
+import { debounce } from 'lodash';
import { isObject } from '~/lib/utils/type_utility';
const BLUR_KEYCODES = [27, 40];
@@ -11,13 +12,21 @@ const HAS_VALUE_CLASS = 'has-value';
export class GitLabDropdownFilter {
constructor(input, options) {
let ref;
- let timeout;
this.input = input;
this.options = options;
// eslint-disable-next-line no-cond-assign
this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true;
const $inputContainer = this.input.parent();
const $clearButton = $inputContainer.find('.js-dropdown-input-clear');
+ const filterRemoteDebounced = debounce(() => {
+ $inputContainer.parent().addClass('is-loading');
+
+ return this.options.query(this.input.val(), (data) => {
+ $inputContainer.parent().removeClass('is-loading');
+ return this.options.callback(data);
+ });
+ }, 500);
+
$clearButton.on('click', (e) => {
// Clear click
e.preventDefault();
@@ -25,7 +34,6 @@ export class GitLabDropdownFilter {
return this.input.val('').trigger('input').focus();
});
// Key events
- timeout = '';
this.input
.on('keydown', (e) => {
const keyCode = e.which;
@@ -41,16 +49,7 @@ export class GitLabDropdownFilter {
}
// Only filter asynchronously only if option remote is set
if (this.options.remote) {
- clearTimeout(timeout);
- // eslint-disable-next-line no-return-assign
- return (timeout = setTimeout(() => {
- $inputContainer.parent().addClass('is-loading');
-
- return this.options.query(this.input.val(), (data) => {
- $inputContainer.parent().removeClass('is-loading');
- return this.options.callback(data);
- });
- }, 250));
+ return filterRemoteDebounced();
}
return this.filter(this.input.val());
});
diff --git a/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue b/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue
index 8535f818b9c..3ebcde817f9 100644
--- a/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue
+++ b/app/assets/javascripts/design_management/components/toolbar/design_navigation.vue
@@ -12,6 +12,10 @@ import allDesignsMixin from '../../mixins/all_designs';
import { DESIGN_ROUTE_NAME } from '../../router/constants';
export default {
+ i18n: {
+ nextButton: s__('DesignManagement|Go to next design'),
+ previousButton: s__('DesignManagement|Go to previous design'),
+ },
components: {
GlButton,
GlButtonGroup,
@@ -81,7 +85,8 @@ export default {
<gl-button
v-gl-tooltip.bottom
:disabled="!previousDesign"
- :title="s__('DesignManagement|Go to previous design')"
+ :title="$options.i18n.previousButton"
+ :aria-label="$options.i18n.previousButton"
icon="angle-left"
class="js-previous-design"
@click="navigateToDesign(previousDesign)"
@@ -89,7 +94,8 @@ export default {
<gl-button
v-gl-tooltip.bottom
:disabled="!nextDesign"
- :title="s__('DesignManagement|Go to next design')"
+ :title="$options.i18n.nextButton"
+ :aria-label="$options.i18n.nextButton"
icon="angle-right"
class="js-next-design"
@click="navigateToDesign(nextDesign)"
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 6b1e2bfb34e..3fb9787ac30 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -84,6 +84,7 @@ export default {
icon="file-tree"
class="gl-mr-3 js-toggle-tree-list"
:title="toggleFileBrowserTitle"
+ :aria-label="toggleFileBrowserTitle"
:selected="showTreeList"
@click="setShowTreeList({ showTreeList: !showTreeList })"
/>
diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue
index 397616c654f..c0b4e96cea2 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.vue
+++ b/app/assets/javascripts/environments/components/environment_rollback.vue
@@ -71,6 +71,7 @@ export default {
class="gl-display-none gl-md-display-block text-secondary"
:loading="isLoading"
:title="title"
+ :aria-label="title"
:icon="isLastDeployment ? 'repeat' : 'redo'"
@click="onClick"
/>
diff --git a/app/assets/javascripts/issuable/components/issuable_by_email.vue b/app/assets/javascripts/issuable/components/issuable_by_email.vue
index 6d063b59922..d0ce8c2c34b 100644
--- a/app/assets/javascripts/issuable/components/issuable_by_email.vue
+++ b/app/assets/javascripts/issuable/components/issuable_by_email.vue
@@ -14,6 +14,9 @@ import { sprintf, __ } from '~/locale';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
export default {
+ i18n: {
+ sendEmail: __('Send email'),
+ },
name: 'IssuableByEmail',
components: {
GlButton,
@@ -116,7 +119,8 @@ export default {
<gl-button
v-gl-tooltip.hover
:href="mailToLink"
- :title="__('Send email')"
+ :title="$options.i18n.sendEmail"
+ :aria-label="$options.i18n.sendEmail"
icon="mail"
data-testid="mail-to-btn"
/>
diff --git a/app/assets/javascripts/issuable_show/components/issuable_title.vue b/app/assets/javascripts/issuable_show/components/issuable_title.vue
index b7ea4a010a3..b96ce0c43f7 100644
--- a/app/assets/javascripts/issuable_show/components/issuable_title.vue
+++ b/app/assets/javascripts/issuable_show/components/issuable_title.vue
@@ -6,8 +6,12 @@ import {
GlTooltipDirective,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
+import { __ } from '~/locale';
export default {
+ i18n: {
+ editTitleAndDescription: __('Edit title and description'),
+ },
components: {
GlIcon,
GlButton,
@@ -58,7 +62,8 @@ export default {
<gl-button
v-if="enableEdit"
v-gl-tooltip.bottom
- :title="__('Edit title and description')"
+ :title="$options.i18n.editTitleAndDescription"
+ :aria-label="$options.i18n.editTitleAndDescription"
icon="pencil"
class="btn-edit js-issuable-edit qa-edit-button"
@click="$emit('edit-issuable', $event)"
diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue
index 806d95ca748..5e92211685a 100644
--- a/app/assets/javascripts/issue_show/components/title.vue
+++ b/app/assets/javascripts/issue_show/components/title.vue
@@ -1,9 +1,13 @@
<script>
import { GlButton, GlTooltipDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { __ } from '~/locale';
import eventHub from '../event_hub';
import animateMixin from '../mixins/animate';
export default {
+ i18n: {
+ editTitleAndDescription: __('Edit title and description'),
+ },
components: {
GlButton,
},
@@ -78,7 +82,8 @@ export default {
v-gl-tooltip.bottom
icon="pencil"
class="btn-edit js-issuable-edit qa-edit-button"
- title="Edit title and description"
+ :title="$options.i18n.editTitleAndDescription"
+ :aria-label="$options.i18n.editTitleAndDescription"
@click="edit"
/>
</div>
diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue
index 3c423bea368..05b5b760f0a 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_header.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue
@@ -14,6 +14,7 @@ import { debounce } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
import invalidUrl from '~/lib/utils/invalid_url';
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
+import { s__ } from '~/locale';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import { timeRanges } from '~/vue_shared/constants';
@@ -24,6 +25,9 @@ import DashboardsDropdown from './dashboards_dropdown.vue';
import RefreshButton from './refresh_button.vue';
export default {
+ i18n: {
+ metricsSettings: s__('Metrics|Metrics Settings'),
+ },
components: {
GlIcon,
GlButton,
@@ -282,7 +286,8 @@ export default {
data-testid="metrics-settings-button"
icon="settings"
:href="operationsSettingsPath"
- :title="s__('Metrics|Metrics Settings')"
+ :title="$options.i18n.metricsSettings"
+ :aria-label="$options.i18n.metricsSettings"
/>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/refresh_button.vue b/app/assets/javascripts/monitoring/components/refresh_button.vue
index 3daf5b38933..0b80043a92c 100644
--- a/app/assets/javascripts/monitoring/components/refresh_button.vue
+++ b/app/assets/javascripts/monitoring/components/refresh_button.vue
@@ -9,7 +9,7 @@ import {
} from '@gitlab/ui';
import Visibility from 'visibilityjs';
import { mapActions } from 'vuex';
-import { n__, __ } from '~/locale';
+import { n__, __, s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -45,6 +45,9 @@ const makeInterval = (length = 0, unit = 's') => {
};
export default {
+ i18n: {
+ refreshDashboard: s__('Metrics|Refresh dashboard'),
+ },
components: {
GlButtonGroup,
GlButton,
@@ -148,7 +151,8 @@ export default {
v-gl-tooltip
class="gl-flex-grow-1"
variant="default"
- :title="s__('Metrics|Refresh dashboard')"
+ :title="$options.i18n.refreshDashboard"
+ :aria-label="$options.i18n.refreshDashboard"
icon="retry"
@click="refresh"
/>
diff --git a/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue b/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue
index cace382ccd6..5f429cbf462 100644
--- a/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue
+++ b/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue
@@ -1,7 +1,11 @@
<script>
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
+import { s__ } from '~/locale';
export default {
+ i18n: {
+ buttonLabel: s__('MergeRequests|Resolve this thread in a new issue'),
+ },
name: 'ResolveWithIssueButton',
components: {
GlButton,
@@ -23,7 +27,8 @@ export default {
<gl-button
v-gl-tooltip
:href="url"
- :title="s__('MergeRequests|Resolve this thread in a new issue')"
+ :title="$options.i18n.buttonLabel"
+ :aria-label="$options.i18n.buttonLabel"
class="new-issue-for-discussion discussion-create-issue-btn"
icon="issue-new"
/>
diff --git a/app/assets/javascripts/notes/components/sort_discussion.vue b/app/assets/javascripts/notes/components/sort_discussion.vue
index ed1f456c174..92c39fbb9f0 100644
--- a/app/assets/javascripts/notes/components/sort_discussion.vue
+++ b/app/assets/javascripts/notes/components/sort_discussion.vue
@@ -49,18 +49,17 @@ export default {
</script>
<template>
- <div class="gl-mr-3 gl-display-inline-block gl-vertical-align-bottom full-width-mobile">
+ <div
+ data-testid="sort-discussion-filter"
+ class="gl-mr-3 gl-display-inline-block gl-vertical-align-bottom full-width-mobile"
+ >
<local-storage-sync
:value="sortDirection"
:storage-key="storageKey"
:persist="persistSortOrder"
@input="setDiscussionSortDirection({ direction: $event })"
/>
- <gl-dropdown
- :text="dropdownText"
- data-testid="sort-discussion-filter"
- class="js-dropdown-text full-width-mobile"
- >
+ <gl-dropdown :text="dropdownText" class="js-dropdown-text full-width-mobile">
<gl-dropdown-item
v-for="{ text, key, cls } in $options.SORT_OPTIONS"
:key="key"
diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue
index eb731c382e1..1360e09a75d 100644
--- a/app/assets/javascripts/registry/settings/components/settings_form.vue
+++ b/app/assets/javascripts/registry/settings/components/settings_form.vue
@@ -110,12 +110,12 @@ export default {
mutationVariables() {
return {
projectPath: this.projectPath,
- enabled: this.value.enabled,
- cadence: this.value.cadence,
- olderThan: this.value.olderThan,
- keepN: this.value.keepN,
- nameRegex: this.value.nameRegex,
- nameRegexKeep: this.value.nameRegexKeep,
+ enabled: this.prefilledForm.enabled,
+ cadence: this.prefilledForm.cadence,
+ olderThan: this.prefilledForm.olderThan,
+ keepN: this.prefilledForm.keepN,
+ nameRegex: this.prefilledForm.nameRegex,
+ nameRegexKeep: this.prefilledForm.nameRegexKeep,
};
},
},
@@ -291,8 +291,8 @@ export default {
type="submit"
:disabled="isSubmitButtonDisabled"
:loading="showLoadingIcon"
- variant="success"
category="primary"
+ variant="confirm"
class="js-no-auto-disable gl-mr-4"
>
{{ $options.i18n.SET_CLEANUP_POLICY_BUTTON }}
diff --git a/app/assets/javascripts/releases/components/release_block_header.vue b/app/assets/javascripts/releases/components/release_block_header.vue
index 356fc0f3bf3..89bc314db89 100644
--- a/app/assets/javascripts/releases/components/release_block_header.vue
+++ b/app/assets/javascripts/releases/components/release_block_header.vue
@@ -1,9 +1,13 @@
<script>
import { GlTooltipDirective, GlLink, GlBadge, GlButton, GlIcon } from '@gitlab/ui';
import { setUrlParams } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
import { BACK_URL_PARAM } from '~/releases/constants';
export default {
+ i18n: {
+ editButton: __('Edit this release'),
+ },
name: 'ReleaseBlockHeader',
components: {
GlLink,
@@ -69,7 +73,8 @@ export default {
variant="default"
icon="pencil"
class="gl-mr-3 js-edit-button ml-2 pb-2"
- :title="__('Edit this release')"
+ :title="$options.i18n.editButton"
+ :aria-label="$options.i18n.editButton"
:href="editLink"
/>
</div>
diff --git a/app/assets/javascripts/search/sort/components/app.vue b/app/assets/javascripts/search/sort/components/app.vue
index e4eba655e39..2bf144705c4 100644
--- a/app/assets/javascripts/search/sort/components/app.vue
+++ b/app/assets/javascripts/search/sort/components/app.vue
@@ -96,6 +96,7 @@ export default {
v-gl-tooltip
:disabled="!selectedSortOption.sortable"
:title="sortDirectionData.tooltip"
+ :aria-label="sortDirectionData.tooltip"
:icon="sortDirectionData.icon"
@click="handleSortDirectionChange"
/>
diff --git a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
index dd1d54d67f2..c6fef86c6ff 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
@@ -1,12 +1,15 @@
<script>
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
-import { sprintf, s__ } from '~/locale';
+import { __, sprintf, s__ } from '~/locale';
import ReviewerAvatarLink from './reviewer_avatar_link.vue';
const LOADING_STATE = 'loading';
const SUCCESS_STATE = 'success';
export default {
+ i18n: {
+ reRequestReview: __('Re-request review'),
+ },
components: {
GlButton,
GlIcon,
@@ -109,7 +112,8 @@ export default {
<gl-button
v-else-if="user.can_update_merge_request && user.reviewed"
v-gl-tooltip.left
- :title="__('Re-request review')"
+ :title="$options.i18n.reRequestReview"
+ :aria-label="$options.i18n.reRequestReview"
:loading="loadingStates[user.id] === $options.LOADING_STATE"
class="float-right gl-text-gray-500!"
size="small"
diff --git a/app/assets/javascripts/snippets/components/embed_dropdown.vue b/app/assets/javascripts/snippets/components/embed_dropdown.vue
index f6c9c569b5f..ad1b08a5a07 100644
--- a/app/assets/javascripts/snippets/components/embed_dropdown.vue
+++ b/app/assets/javascripts/snippets/components/embed_dropdown.vue
@@ -65,6 +65,7 @@ export default {
<gl-button
v-gl-tooltip.hover
:title="$options.MSG_COPY"
+ :aria-label="$options.MSG_COPY"
:data-clipboard-text="value"
icon="copy-to-clipboard"
data-qa-selector="copy_button"
diff --git a/app/assets/javascripts/static_site_editor/graphql/index.js b/app/assets/javascripts/static_site_editor/graphql/index.js
index 23f800517c9..2ae2baddbcc 100644
--- a/app/assets/javascripts/static_site_editor/graphql/index.js
+++ b/app/assets/javascripts/static_site_editor/graphql/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import appDataQuery from './queries/app_data.query.graphql';
import fileResolver from './resolvers/file';
import hasSubmittedChangesResolver from './resolvers/has_submitted_changes';
import submitContentChangesResolver from './resolvers/submit_content_changes';
@@ -28,7 +29,8 @@ const createApolloProvider = (appData) => {
// eslint-disable-next-line @gitlab/require-i18n-strings
const mounts = appData.mounts.map((mount) => ({ __typename: 'Mount', ...mount }));
- defaultClient.cache.writeData({
+ defaultClient.cache.writeQuery({
+ query: appDataQuery,
data: {
appData: {
__typename: 'AppData',
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
index cd5f63afc79..f14e1992901 100644
--- a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
@@ -56,6 +56,7 @@ export default {
<gl-button
v-gl-tooltip.hover
:title="$options.copyURLTooltip"
+ :aria-label="$options.copyURLTooltip"
:data-clipboard-text="sshLink"
data-qa-selector="copy_ssh_url_button"
icon="copy-to-clipboard"
@@ -75,6 +76,7 @@ export default {
<gl-button
v-gl-tooltip.hover
:title="$options.copyURLTooltip"
+ :aria-label="$options.copyURLTooltip"
:data-clipboard-text="httpLink"
data-qa-selector="copy_http_url_button"
icon="copy-to-clipboard"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index 6952c5def8c..107ced550c1 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -363,6 +363,7 @@ export default {
<gl-button
v-gl-tooltip
:title="sortDirectionTooltip"
+ :aria-label="sortDirectionTooltip"
:icon="sortDirectionIcon"
class="flex-shrink-1"
@click="handleSortDirectionClick"
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
index 9c2a644b7a9..76b005772ec 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue
@@ -46,7 +46,7 @@ export default {
},
activeLabel() {
return this.labels.find(
- (label) => label.title.toLowerCase() === stripQuotes(this.currentValue),
+ (label) => this.getLabelName(label).toLowerCase() === stripQuotes(this.currentValue),
);
},
containerStyle() {
@@ -69,6 +69,21 @@ export default {
},
},
methods: {
+ /**
+ * There's an inconsistency between private and public API
+ * for labels where label name is included in a different
+ * property;
+ *
+ * Private API => `label.title`
+ * Public API => `label.name`
+ *
+ * This method allows compatibility as there may be instances
+ * where `config.fetchLabels` provided externally may still be
+ * using either of the two APIs.
+ */
+ getLabelName(label) {
+ return label.name || label.title;
+ },
fetchLabelBySearchTerm(searchTerm) {
this.loading = true;
this.config
@@ -85,7 +100,7 @@ export default {
});
},
searchLabels: debounce(function debouncedSearch({ data }) {
- this.fetchLabelBySearchTerm(data);
+ if (!this.loading) this.fetchLabelBySearchTerm(data);
}, DEBOUNCE_DELAY),
},
};
@@ -100,7 +115,7 @@ export default {
>
<template #view-token="{ inputValue, cssClasses, listeners }">
<gl-token variant="search-value" :class="cssClasses" :style="containerStyle" v-on="listeners"
- >~{{ activeLabel ? activeLabel.title : inputValue }}</gl-token
+ >~{{ activeLabel ? getLabelName(activeLabel) : inputValue }}</gl-token
>
</template>
<template #suggestions>
@@ -114,13 +129,17 @@ export default {
<gl-dropdown-divider v-if="defaultLabels.length" />
<gl-loading-icon v-if="loading" />
<template v-else>
- <gl-filtered-search-suggestion v-for="label in labels" :key="label.id" :value="label.title">
- <div class="gl-display-flex">
+ <gl-filtered-search-suggestion
+ v-for="label in labels"
+ :key="label.id"
+ :value="getLabelName(label)"
+ >
+ <div class="gl-display-flex gl-align-items-center">
<span
:style="{ backgroundColor: label.color }"
class="gl-display-inline-block mr-2 p-2"
></span>
- <div>{{ label.title }}</div>
+ <div>{{ getLabelName(label) }}</div>
</div>
</gl-filtered-search-suggestion>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
index 7b36d57dfbf..38afd56bae6 100644
--- a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
+++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
@@ -101,6 +101,7 @@ export default {
:data-clipboard-target="target"
:data-clipboard-text="text"
:title="title"
+ :aria-label="title"
:category="category"
icon="copy-to-clipboard"
/>
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 834d28f1e3c..133c6217bc0 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -316,6 +316,7 @@ class MergeRequest < ApplicationRecord
}
scope :with_csv_entity_associations, -> { preload(:assignees, :approved_by_users, :author, :milestone, metrics: [:merged_by]) }
+ scope :with_jira_integration_associations, -> { preload(:metrics, :assignees, :author, :target_project, :source_project) }
scope :by_target_branch_wildcard, ->(wildcard_branch_name) do
where("target_branch LIKE ?", ApplicationRecord.sanitize_sql_like(wildcard_branch_name).tr('*', '%'))
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 13ab542b5d9..7b7c2ea9d7b 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -343,6 +343,10 @@ class Namespace < ApplicationRecord
Plan.default
end
+ def paid?
+ root? && actual_plan.paid?
+ end
+
def actual_limits
# We default to PlanLimits.new otherwise a lot of specs would fail
# On production each plan should already have associated limits record
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index 85887e64a8b..9a2e29a6ee3 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -41,7 +41,6 @@ class PipelineSerializer < BaseSerializer
def preloaded_relations
[
:cancelable_statuses,
- :latest_statuses_ordered_by_stage,
:retryable_builds,
:stages,
:latest_statuses,
diff --git a/app/views/groups/settings/_permanent_deletion.html.haml b/app/views/groups/settings/_permanent_deletion.html.haml
index 8bd47fbea44..125a20060ed 100644
--- a/app/views/groups/settings/_permanent_deletion.html.haml
+++ b/app/views/groups/settings/_permanent_deletion.html.haml
@@ -5,4 +5,5 @@
= _('Removing this group also removes all child projects, including archived projects, and their resources.')
%br
%strong= _('Removed group can not be restored!')
- = button_to _('Remove group'), '#', class: 'btn gl-button btn-danger js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(group) }
+
+ = render 'groups/settings/remove_button', group: group
diff --git a/app/views/groups/settings/_remove_button.html.haml b/app/views/groups/settings/_remove_button.html.haml
new file mode 100644
index 00000000000..a04dba68b92
--- /dev/null
+++ b/app/views/groups/settings/_remove_button.html.haml
@@ -0,0 +1,7 @@
+- if group.paid?
+ .gl-alert.gl-alert-info.gl-mb-5{ data: { testid: 'group-has-linked-subscription-alert' } }
+ = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-body
+ = html_escape(_("This group can't be removed because it is linked to a subscription. To remove this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "<a href=\"#{help_page_path('subscriptions/index', anchor: 'change-the-linked-namespace')}\">".html_safe, linkEnd: '</a>'.html_safe }
+
+= button_to _('Remove group'), '#', class: ['btn gl-button btn-danger js-confirm-danger', ('disabled' if group.paid?)], data: { 'confirm-danger-message' => remove_group_message(group), 'testid' => 'remove-group-button' }
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
index aeeffb6f4b6..81373a81f80 100644
--- a/app/views/layouts/nav/_breadcrumbs.html.haml
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -9,7 +9,7 @@
= button_tag class: 'toggle-mobile-nav', type: 'button' do
%span.sr-only= _("Open sidebar")
= sprite_icon('hamburger', size: 18)
- .breadcrumbs-links.js-title-container{ data: { qa_selector: 'breadcrumb_links_content' } }
+ .breadcrumbs-links{ data: { testid: 'breadcrumb-links', qa_selector: 'breadcrumb_links_content' } }
%ul.list-unstyled.breadcrumbs-list.js-breadcrumbs-list
- unless hide_top_links
= header_title
diff --git a/changelogs/unreleased/321788-drop-unused-preload.yml b/changelogs/unreleased/321788-drop-unused-preload.yml
new file mode 100644
index 00000000000..9d3590ecf42
--- /dev/null
+++ b/changelogs/unreleased/321788-drop-unused-preload.yml
@@ -0,0 +1,5 @@
+---
+title: Drop unused preload from PipelineSerializer
+merge_request: 56988
+author:
+type: performance
diff --git a/changelogs/unreleased/325429-container-registry-cleanup-policy-wiped-all-images.yml b/changelogs/unreleased/325429-container-registry-cleanup-policy-wiped-all-images.yml
new file mode 100644
index 00000000000..da4ed507f23
--- /dev/null
+++ b/changelogs/unreleased/325429-container-registry-cleanup-policy-wiped-all-images.yml
@@ -0,0 +1,5 @@
+---
+title: Always save default on empty values in Exp Policies
+merge_request: 57470
+author:
+type: fixed
diff --git a/changelogs/unreleased/325812-update-the-package-settings-to-use-the-blue-primary-button.yml b/changelogs/unreleased/325812-update-the-package-settings-to-use-the-blue-primary-button.yml
new file mode 100644
index 00000000000..197648875da
--- /dev/null
+++ b/changelogs/unreleased/325812-update-the-package-settings-to-use-the-blue-primary-button.yml
@@ -0,0 +1,5 @@
+---
+title: Update the Package settings to use the blue primary button
+merge_request: 57468
+author:
+type: fixed
diff --git a/changelogs/unreleased/cngo-add-aria-labels-to-icon-buttons.yml b/changelogs/unreleased/cngo-add-aria-labels-to-icon-buttons.yml
new file mode 100644
index 00000000000..98bc341d16f
--- /dev/null
+++ b/changelogs/unreleased/cngo-add-aria-labels-to-icon-buttons.yml
@@ -0,0 +1,5 @@
+---
+title: Add aria labels to icon buttons
+merge_request: 57261
+author:
+type: fixed
diff --git a/changelogs/unreleased/id-n-1-for-jira-pulls.yml b/changelogs/unreleased/id-n-1-for-jira-pulls.yml
new file mode 100644
index 00000000000..e6d37b54c49
--- /dev/null
+++ b/changelogs/unreleased/id-n-1-for-jira-pulls.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve N + 1 for JIRA pulls
+merge_request: 57482
+author:
+type: performance
diff --git a/config/feature_flags/development/usage_data_api.yml b/config/feature_flags/ops/usage_data_api.yml
index 9ba8180eb5a..edb78c151d5 100644
--- a/config/feature_flags/development/usage_data_api.yml
+++ b/config/feature_flags/ops/usage_data_api.yml
@@ -3,6 +3,6 @@ name: usage_data_api
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41301
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267114
milestone: '13.4'
-type: development
-group: group::product analytics
+type: ops
+group: group::product intelligence
default_enabled: true
diff --git a/doc/administration/geo/glossary.md b/doc/administration/geo/glossary.md
index 98a29c27a89..1ec552326aa 100644
--- a/doc/administration/geo/glossary.md
+++ b/doc/administration/geo/glossary.md
@@ -6,7 +6,7 @@ type: howto
---
-# Geo Glossary
+# Geo Glossary **(PREMIUM SELF)**
NOTE:
We are updating the Geo documentation, user interface and commands to reflect these changes. Not all pages comply with
diff --git a/doc/administration/geo/replication/version_specific_updates.md b/doc/administration/geo/replication/version_specific_updates.md
index 883e5d44b69..4f0a4dc638c 100644
--- a/doc/administration/geo/replication/version_specific_updates.md
+++ b/doc/administration/geo/replication/version_specific_updates.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
-# Version-specific update instructions
+# Version-specific update instructions **(PREMIUM SELF)**
Review this page for update instructions for your version. These steps
accompany the [general steps](updating_the_geo_nodes.md#general-update-steps)
diff --git a/doc/administration/geo/setup/index.md b/doc/administration/geo/setup/index.md
index 8308cbfa82e..5ec18e29f21 100644
--- a/doc/administration/geo/setup/index.md
+++ b/doc/administration/geo/setup/index.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
-# Setting up Geo
+# Setting up Geo **(PREMIUM SELF)**
These instructions assume you have a working instance of GitLab. They guide you through:
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 940f0c41c8c..1a2beeb40fb 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -4101,6 +4101,7 @@ finishes.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/19298) in GitLab 13.2.
Use `release` to create a [release](../../user/project/releases/index.md).
+Requires the `release-cli` to be available in your GitLab Runner Docker or shell executor.
These keywords are supported:
@@ -4122,6 +4123,69 @@ You must specify the Docker image to use for the `release-cli`:
image: registry.gitlab.com/gitlab-org/release-cli:latest
```
+#### `release-cli` for shell executors
+
+> [Introduced](https://gitlab.com/gitlab-org/release-cli/-/issues/21) in GitLab 13.8.
+
+For GitLab Runner shell executors, you can download and install the `release-cli` manually for your [supported OS and architecture](https://release-cli-downloads.s3.amazonaws.com/latest/index.html).
+Once installed, the `release` keyword should be available to you.
+
+**Install on Unix/Linux**
+
+1. Download the binary for your system, in the following example for amd64 systems:
+
+ ```shell
+ curl --location --output /usr/local/bin/release-cli "https://release-cli-downloads.s3.amazonaws.com/latest/release-cli-linux-amd64"
+ ```
+
+1. Give it permissions to execute:
+
+ ```shell
+ sudo chmod +x /usr/local/bin/release-cli
+ ```
+
+1. Verify `release-cli` is available:
+
+ ```shell
+ $ release-cli -v
+
+ release-cli version 0.6.0
+ ```
+
+**Install on Windows PowerShell**
+
+1. Create a folder somewhere in your system, for example `C:\GitLab\Release-CLI\bin`
+
+ ```shell
+ New-Item -Path 'C:\GitLab\Release-CLI\bin' -ItemType Directory
+ ```
+
+1. Download the executable file:
+
+ ```shell
+ PS C:\> Invoke-WebRequest -Uri "https://release-cli-downloads.s3.amazonaws.com/latest/release-cli-windows-amd64.exe" -OutFile "C:\GitLab\Release-CLI\bin\release-cli.exe"
+
+ Directory: C:\GitLab\Release-CLI
+ Mode LastWriteTime Length Name
+ ---- ------------- ------ ----
+ d----- 3/16/2021 4:17 AM bin
+
+ ```
+
+1. Add the directory to your `$env:PATH`:
+
+ ```shell
+ $env:PATH += ";C:\GitLab\Release-CLI\bin"
+ ```
+
+1. Verify `release-cli` is available:
+
+ ```shell
+ PS C:\> release-cli -v
+
+ release-cli version 0.6.0
+ ```
+
#### `script`
All jobs except [trigger](#trigger) jobs must have the `script` keyword. A `release`
diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md
index 87ac8109d3c..8f0c961cdf6 100644
--- a/doc/development/usage_ping/dictionary.md
+++ b/doc/development/usage_ping/dictionary.md
@@ -9956,6 +9956,54 @@ Status: `implemented`
Tiers: `premium`, `ultimate`
+### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_due_date_as_fixed_monthly`
+
+Counts of MAU setting epic due date as inherited
+
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210325060507_g_project_management_users_setting_epic_due_date_as_fixed_monthly.yml)
+
+Group: `group::product planning`
+
+Status: `implemented`
+
+Tiers: `premium`, `ultimate`
+
+### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_due_date_as_fixed_weekly`
+
+Counts of WAU setting epic due date as fixed
+
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210325060623_g_project_management_users_setting_epic_due_date_as_fixed_weekly.yml)
+
+Group: `group::product planning`
+
+Status: `implemented`
+
+Tiers: `premium`, `ultimate`
+
+### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_due_date_as_inherited_monthly`
+
+Counts of MAU setting epic due date as inherited
+
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210325060315_g_project_management_users_setting_epic_due_date_as_inherited_monthly.yml)
+
+Group: `group::product planning`
+
+Status: `implemented`
+
+Tiers: `premium`, `ultimate`
+
+### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_due_date_as_inherited_weekly`
+
+Counts of WAU setting epic due date as inherited
+
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210325060903_g_project_management_users_setting_epic_due_date_as_inherited_weekly.yml)
+
+Group: `group::product planning`
+
+Status: `implemented`
+
+Tiers: `premium`, `ultimate`
+
### `redis_hll_counters.epics_usage.g_project_management_users_setting_epic_start_date_as_fixed_monthly`
Counts of MAU setting epic start date as fixed
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index be5075d8230..d1ce3a6877b 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -326,10 +326,11 @@ To enable it, you need to enable [ActionCable in-app mode](https://docs.gitlab.c
## Cached issue count **(FREE SELF)**
> - [Introduced]([link-to-issue](https://gitlab.com/gitlab-org/gitlab/-/issues/243753)) in GitLab 13.9.
-> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
-> - It's disabled on GitLab.com.
-> - It's not recommended for production use.
-> - To use this feature in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-cached-issue-count) **(FREE SELF)**
+> - It was [deployed behind a feature flag](../../feature_flags.md), disabled by default.
+> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/323493) in GitLab 13.10.
+> - It's enabled on GitLab.com.
+> - It's recommended for production use.
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-cached-issue-count) **(FREE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
@@ -373,7 +374,7 @@ You can then see issue statuses in the issues list and the
## Enable or disable cached issue count **(FREE SELF)**
-Cached issue count in the left sidebar is under development and not ready for production use. It is
+Cached issue count in the left sidebar is under development but ready for production use. It is
deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can disable it.
diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb
index c7d63f8d6ac..622637e4fc3 100644
--- a/lib/api/usage_data.rb
+++ b/lib/api/usage_data.rb
@@ -8,7 +8,7 @@ module API
namespace 'usage_data' do
before do
- not_found! unless Feature.enabled?(:usage_data_api, default_enabled: true)
+ not_found! unless Feature.enabled?(:usage_data_api, default_enabled: :yaml, type: :ops)
forbidden!('Invalid CSRF token is provided') unless verified_request?
end
diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb
index 3a0609651f4..7938339e1b1 100644
--- a/lib/api/v3/github.rb
+++ b/lib/api/v3/github.rb
@@ -75,11 +75,14 @@ module API
# rubocop: enable CodeReuse/ActiveRecord
def authorized_merge_requests
- MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?).execute
+ MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?)
+ .execute.with_jira_integration_associations
end
def authorized_merge_requests_for_project(project)
- MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?, project_id: project.id).execute
+ MergeRequestsFinder
+ .new(current_user, authorized_only: !current_user.admin?, project_id: project.id)
+ .execute.with_jira_integration_associations
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index c7e215c143f..08c17058fcb 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -45,7 +45,7 @@ module Gitlab
# Initialize gon.features with any flags that should be
# made globally available to the frontend
push_frontend_feature_flag(:snippets_binary_blob, default_enabled: false)
- push_frontend_feature_flag(:usage_data_api, default_enabled: true)
+ push_frontend_feature_flag(:usage_data_api, type: :ops, default_enabled: :yaml)
push_frontend_feature_flag(:security_auto_fix, default_enabled: false)
end
diff --git a/lib/gitlab/usage_data_counters/known_events/epic_events.yml b/lib/gitlab/usage_data_counters/known_events/epic_events.yml
index 600c849db1f..2465dc0f1f1 100644
--- a/lib/gitlab/usage_data_counters/known_events/epic_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events/epic_events.yml
@@ -51,6 +51,18 @@
aggregation: daily
feature_flag: track_epics_activity
+- name: g_project_management_users_setting_epic_due_date_as_fixed
+ category: epics_usage
+ redis_slot: project_management
+ aggregation: daily
+ feature_flag: track_epics_activity
+
+- name: g_project_management_users_setting_epic_due_date_as_inherited
+ category: epics_usage
+ redis_slot: project_management
+ aggregation: daily
+ feature_flag: track_epics_activity
+
- name: g_project_management_epic_issue_added
category: epics_usage
redis_slot: project_management
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 18f064a6bb8..ce9dadc16e8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2917,6 +2917,9 @@ msgstr ""
msgid "AlertSettings|Delete integration"
msgstr ""
+msgid "AlertSettings|Edit integration"
+msgstr ""
+
msgid "AlertSettings|Edit payload"
msgstr ""
@@ -31061,6 +31064,9 @@ msgstr ""
msgid "This group"
msgstr ""
+msgid "This group can't be removed because it is linked to a subscription. To remove this group, %{linkStart}link the subscription%{linkEnd} with a different group."
+msgstr ""
+
msgid "This group cannot be invited to a project inside a group with enforced SSO"
msgstr ""
@@ -31070,6 +31076,9 @@ msgstr ""
msgid "This group has been scheduled for permanent removal on %{date}"
msgstr ""
+msgid "This group is linked to a subscription"
+msgstr ""
+
msgid "This group, including all subgroups, projects and git repositories, will be reachable from only the specified IP address ranges."
msgstr ""
diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb
index 6e4082d1391..bc60cdd2f8e 100644
--- a/spec/features/projects/settings/registry_settings_spec.rb
+++ b/spec/features/projects/settings/registry_settings_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
select('7 days', from: 'Remove tags older than:')
fill_in('Remove tags matching:', with: '.*-production')
- submit_button = find('.btn.gl-button.btn-success')
+ submit_button = find('[data-testid="save-button"')
expect(submit_button).not_to be_disabled
submit_button.click
end
@@ -53,7 +53,7 @@ RSpec.describe 'Project > Settings > CI/CD > Container registry tag expiration p
within '#js-registry-policies' do
fill_in('Remove tags matching:', with: '*-production')
- submit_button = find('.btn.gl-button.btn-success')
+ submit_button = find('[data-testid="save-button"')
expect(submit_button).not_to be_disabled
submit_button.click
end
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index 8c1d88276df..d7614201740 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -16,18 +16,18 @@ RSpec.describe 'Subgroup Issuables', :js do
it 'shows the full subgroup title when issues index page is empty' do
visit project_issues_path(project)
- expect_to_have_full_subgroup_title
+ expect_to_have_breadcrumb_links
end
it 'shows the full subgroup title when merge requests index page is empty' do
visit project_merge_requests_path(project)
- expect_to_have_full_subgroup_title
+ expect_to_have_breadcrumb_links
end
- def expect_to_have_full_subgroup_title
- title = find('.breadcrumbs-links')
+ def expect_to_have_breadcrumb_links
+ links = find('[data-testid="breadcrumb-links"]')
- expect(title).to have_content 'group subgroup project'
+ expect(links).to have_content 'group subgroup project'
end
end
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index f875e3bdb07..5523fe13cd6 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -264,18 +264,18 @@ describe('Api', () => {
it('fetches group labels', (done) => {
const options = { params: { search: 'foo' } };
const expectedGroup = 'gitlab-org';
- const expectedUrl = `${dummyUrlRoot}/groups/${expectedGroup}/-/labels`;
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${expectedGroup}/labels`;
mock.onGet(expectedUrl).reply(httpStatus.OK, [
{
id: 1,
- title: 'Foo Label',
+ name: 'Foo Label',
},
]);
Api.groupLabels(expectedGroup, options)
.then((res) => {
expect(res.length).toBe(1);
- expect(res[0].title).toBe('Foo Label');
+ expect(res[0].name).toBe('Foo Label');
})
.then(done)
.catch(done.fail);
@@ -593,7 +593,7 @@ describe('Api', () => {
});
describe('newLabel', () => {
- it('creates a new label', (done) => {
+ it('creates a new project label', (done) => {
const namespace = 'some namespace';
const project = 'some project';
const labelData = { some: 'data' };
@@ -618,26 +618,23 @@ describe('Api', () => {
});
});
- it('creates a group label', (done) => {
+ it('creates a new group label', (done) => {
const namespace = 'group/subgroup';
- const labelData = { some: 'data' };
+ const labelData = { name: 'Foo', color: '#000000' };
const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace);
- const expectedData = {
- label: labelData,
- };
mock.onPost(expectedUrl).reply((config) => {
- expect(config.data).toBe(JSON.stringify(expectedData));
+ expect(config.data).toBe(JSON.stringify({ color: labelData.color }));
return [
httpStatus.OK,
{
- name: 'test',
+ ...labelData,
},
];
});
Api.newLabel(namespace, undefined, labelData, (response) => {
- expect(response.name).toBe('test');
+ expect(response.name).toBe('Foo');
done();
});
});
diff --git a/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap b/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap
index 5eb86d4f9cb..3cb48d7632f 100644
--- a/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap
+++ b/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap
@@ -13,6 +13,7 @@ exports[`Design management pagination component renders navigation buttons 1`] =
class="gl-mx-5"
>
<gl-button-stub
+ aria-label="Go to previous design"
buttontextclasses=""
category="primary"
class="js-previous-design"
@@ -24,6 +25,7 @@ exports[`Design management pagination component renders navigation buttons 1`] =
/>
<gl-button-stub
+ aria-label="Go to next design"
buttontextclasses=""
category="primary"
class="js-next-design"
diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js
index 7527910ad59..ad94da6ca66 100644
--- a/spec/frontend/registry/settings/components/settings_form_spec.js
+++ b/spec/frontend/registry/settings/components/settings_form_spec.js
@@ -77,33 +77,47 @@ describe('Settings Form', () => {
});
};
- const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => {
+ const mountComponentWithApollo = ({
+ provide = defaultProvidedValues,
+ mutationResolver,
+ queryPayload = expirationPolicyPayload(),
+ } = {}) => {
localVue.use(VueApollo);
const requestHandlers = [
- [updateContainerExpirationPolicyMutation, resolver],
- [expirationPolicyQuery, jest.fn().mockResolvedValue(expirationPolicyPayload())],
+ [updateContainerExpirationPolicyMutation, mutationResolver],
+ [expirationPolicyQuery, jest.fn().mockResolvedValue(queryPayload)],
];
fakeApollo = createMockApollo(requestHandlers);
+ // This component does not do the query directly, but we need a proper cache to update
fakeApollo.defaultClient.cache.writeQuery({
query: expirationPolicyQuery,
variables: {
projectPath: provide.projectPath,
},
- ...expirationPolicyPayload(),
+ ...queryPayload,
});
+ // we keep in sync what prop we pass to the component with the cache
+ const {
+ data: {
+ project: { containerExpirationPolicy: value },
+ },
+ } = queryPayload;
+
mountComponent({
provide,
+ props: {
+ ...defaultProps,
+ value,
+ },
config: {
localVue,
apolloProvider: fakeApollo,
},
});
-
- return requestHandlers.map((resolvers) => resolvers[1]);
};
beforeEach(() => {
@@ -253,19 +267,44 @@ describe('Settings Form', () => {
expect(findSaveButton().attributes('type')).toBe('submit');
});
- it('dispatches the correct apollo mutation', async () => {
- const [expirationPolicyMutationResolver] = mountComponentWithApollo({
- resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
+ it('dispatches the correct apollo mutation', () => {
+ const mutationResolver = jest.fn().mockResolvedValue(expirationPolicyMutationPayload());
+ mountComponentWithApollo({
+ mutationResolver,
});
findForm().trigger('submit');
- await expirationPolicyMutationResolver();
- expect(expirationPolicyMutationResolver).toHaveBeenCalled();
+
+ expect(mutationResolver).toHaveBeenCalled();
+ });
+
+ it('saves the default values when a value is missing did not change the default options', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(expirationPolicyMutationPayload());
+ mountComponentWithApollo({
+ mutationResolver,
+ queryPayload: expirationPolicyPayload({ keepN: null, cadence: null, olderThan: null }),
+ });
+
+ await waitForPromises();
+
+ findForm().trigger('submit');
+
+ expect(mutationResolver).toHaveBeenCalledWith({
+ input: {
+ cadence: 'EVERY_DAY',
+ enabled: true,
+ keepN: 'TEN_TAGS',
+ nameRegex: 'asdasdssssdfdf',
+ nameRegexKeep: 'sss',
+ olderThan: 'NINETY_DAYS',
+ projectPath: 'path',
+ },
+ });
});
it('tracks the submit event', () => {
mountComponentWithApollo({
- resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
+ mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
});
findForm().trigger('submit');
@@ -274,12 +313,12 @@ describe('Settings Form', () => {
});
it('show a success toast when submit succeed', async () => {
- const handlers = mountComponentWithApollo({
- resolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
+ mountComponentWithApollo({
+ mutationResolver: jest.fn().mockResolvedValue(expirationPolicyMutationPayload()),
});
findForm().trigger('submit');
- await Promise.all(handlers);
+ await waitForPromises();
await wrapper.vm.$nextTick();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE, {
@@ -290,14 +329,14 @@ describe('Settings Form', () => {
describe('when submit fails', () => {
describe('user recoverable errors', () => {
it('when there is an error is shown in a toast', async () => {
- const handlers = mountComponentWithApollo({
- resolver: jest
+ mountComponentWithApollo({
+ mutationResolver: jest
.fn()
.mockResolvedValue(expirationPolicyMutationPayload({ errors: ['foo'] })),
});
findForm().trigger('submit');
- await Promise.all(handlers);
+ await waitForPromises();
await wrapper.vm.$nextTick();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('foo', {
@@ -308,13 +347,12 @@ describe('Settings Form', () => {
describe('global errors', () => {
it('shows an error', async () => {
- const handlers = mountComponentWithApollo({
- resolver: jest.fn().mockRejectedValue(expirationPolicyMutationPayload()),
+ mountComponentWithApollo({
+ mutationResolver: jest.fn().mockRejectedValue(expirationPolicyMutationPayload()),
});
findForm().trigger('submit');
- await Promise.all(handlers);
- await wrapper.vm.$nextTick();
+ await waitForPromises();
await wrapper.vm.$nextTick();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE, {
diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
index 1bf757ea312..bab928318ce 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
@@ -40,6 +40,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
tag="div"
>
<gl-button-stub
+ aria-label="Copy URL"
buttontextclasses=""
category="primary"
class="d-inline-flex"
@@ -82,6 +83,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
tag="div"
>
<gl-button-stub
+ aria-label="Copy URL"
buttontextclasses=""
category="primary"
class="d-inline-flex"
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
index 7676ce10ce0..8528c062426 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
@@ -118,6 +118,22 @@ describe('LabelToken', () => {
wrapper = createComponent();
});
+ describe('getLabelName', () => {
+ it('returns value of `name` or `title` property present in provided label param', () => {
+ let mockLabel = {
+ title: 'foo',
+ };
+
+ expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.title);
+
+ mockLabel = {
+ name: 'foo',
+ };
+
+ expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.name);
+ });
+ });
+
describe('fetchLabelBySearchTerm', () => {
it('calls `config.fetchLabels` with provided searchTerm param', () => {
jest.spyOn(wrapper.vm.config, 'fetchLabels');
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 2063de691a3..351486ae801 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -1415,6 +1415,12 @@ RSpec.describe Namespace do
end
end
+ describe '#paid?' do
+ it 'returns false for a root namespace with a free plan' do
+ expect(namespace.paid?).to eq(false)
+ end
+ end
+
describe '#shared_runners_setting' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb
index 197c6cbb0eb..521187f0a81 100644
--- a/spec/requests/api/v3/github_spec.rb
+++ b/spec/requests/api/v3/github_spec.rb
@@ -3,10 +3,10 @@
require 'spec_helper'
RSpec.describe API::V3::Github do
- let(:user) { create(:user) }
- let(:unauthorized_user) { create(:user) }
- let(:admin) { create(:user, :admin) }
- let(:project) { create(:project, :repository, creator: user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:unauthorized_user) { create(:user) }
+ let_it_be(:admin) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :repository, creator: user) }
before do
project.add_maintainer(user)
@@ -210,14 +210,14 @@ RSpec.describe API::V3::Github do
end
describe 'repo pulls' do
- let(:project2) { create(:project, :repository, creator: user) }
- let(:assignee) { create(:user) }
- let(:assignee2) { create(:user) }
- let!(:merge_request) do
+ let_it_be(:project2) { create(:project, :repository, creator: user) }
+ let_it_be(:assignee) { create(:user) }
+ let_it_be(:assignee2) { create(:user) }
+ let_it_be(:merge_request) do
create(:merge_request, source_project: project, target_project: project, author: user, assignees: [assignee])
end
- let!(:merge_request_2) do
+ let_it_be(:merge_request_2) do
create(:merge_request, source_project: project2, target_project: project2, author: user, assignees: [assignee, assignee2])
end
@@ -225,26 +225,54 @@ RSpec.describe API::V3::Github do
project2.add_maintainer(user)
end
+ def perform_request
+ jira_get v3_api(route, user)
+ end
+
describe 'GET /-/jira/pulls' do
+ let(:route) { '/repos/-/jira/pulls' }
+
it 'returns an array of merge requests with github format' do
- jira_get v3_api('/repos/-/jira/pulls', user)
+ perform_request
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an(Array)
expect(json_response.size).to eq(2)
expect(response).to match_response_schema('entities/github/pull_requests')
end
+
+ it 'returns multiple merge requests without N + 1' do
+ perform_request
+
+ control_count = ActiveRecord::QueryRecorder.new { perform_request }.count
+
+ create(:merge_request, source_project: project, source_branch: 'fix')
+
+ expect { perform_request }.not_to exceed_query_limit(control_count)
+ end
end
describe 'GET /repos/:namespace/:project/pulls' do
+ let(:route) { "/repos/#{project.namespace.path}/#{project.path}/pulls" }
+
it 'returns an array of merge requests for the proper project in github format' do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/pulls", user)
+ perform_request
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an(Array)
expect(json_response.size).to eq(1)
expect(response).to match_response_schema('entities/github/pull_requests')
end
+
+ it 'returns multiple merge requests without N + 1' do
+ perform_request
+
+ control_count = ActiveRecord::QueryRecorder.new { perform_request }.count
+
+ create(:merge_request, source_project: project, source_branch: 'fix')
+
+ expect { perform_request }.not_to exceed_query_limit(control_count)
+ end
end
describe 'GET /repos/:namespace/:project/pulls/:id' do
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index bcaaa61eb04..7784f8f53ff 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -202,7 +202,7 @@ RSpec.describe PipelineSerializer do
# Existing numbers are high and require performance optimization
# Ongoing issue:
# https://gitlab.com/gitlab-org/gitlab/-/issues/225156
- expected_queries = Gitlab.ee? ? 85 : 76
+ expected_queries = Gitlab.ee? ? 82 : 76
expect(recorded.count).to be_within(2).of(expected_queries)
expect(recorded.cached_count).to eq(0)
diff --git a/spec/support/shared_examples/features/error_tracking_shared_example.rb b/spec/support/shared_examples/features/error_tracking_shared_example.rb
index 92fc54ce0b0..1bdc5355408 100644
--- a/spec/support/shared_examples/features/error_tracking_shared_example.rb
+++ b/spec/support/shared_examples/features/error_tracking_shared_example.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'error tracking index page' do
it 'renders the error index page', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217810' } do
- within('div.js-title-container') do
+ within('[data-testid="breadcrumb-links"]') do
expect(page).to have_content(project.namespace.name)
expect(page).to have_content(project.name)
end
diff --git a/spec/views/groups/settings/_remove.html.haml_spec.rb b/spec/views/groups/settings/_remove.html.haml_spec.rb
new file mode 100644
index 00000000000..07fe900bc2d
--- /dev/null
+++ b/spec/views/groups/settings/_remove.html.haml_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'groups/settings/_remove.html.haml' do
+ describe 'render' do
+ it 'enables the Remove group button for a group' do
+ group = build(:group)
+
+ render 'groups/settings/remove', group: group
+
+ expect(rendered).to have_selector '[data-testid="remove-group-button"]'
+ expect(rendered).not_to have_selector '[data-testid="remove-group-button"].disabled'
+ expect(rendered).not_to have_selector '[data-testid="group-has-linked-subscription-alert"]'
+ end
+ end
+end