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--.gitlab/merge_request_templates/Revert To Resolve Incident.md2
-rw-r--r--.rubocop_todo/background_migration/feature_category.yml3
-rw-r--r--app/assets/javascripts/behaviors/index.js2
-rw-r--r--app/assets/javascripts/behaviors/select2.js30
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue23
-rw-r--r--app/assets/javascripts/ci/ci_variable_list/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js3
-rw-r--r--app/assets/javascripts/pages/shared/mount_runner_aws_deployments.js17
-rw-r--r--app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/move/move_issue_button.vue71
-rw-r--r--app/assets/javascripts/sidebar/lib/sidebar_move_issue.js89
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js35
-rw-r--r--app/assets/javascripts/sidebar/queries/move_issue.mutation.graphql4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue43
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue29
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/constants.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue37
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue5
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss14
-rw-r--r--app/controllers/admin/application_settings_controller.rb4
-rw-r--r--app/controllers/groups/email_campaigns_controller.rb2
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb4
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb4
-rw-r--r--app/helpers/ci/pipelines_helper.rb2
-rw-r--r--app/helpers/ci/variables_helper.rb4
-rw-r--r--app/models/concerns/ci/maskable.rb22
-rw-r--r--app/models/users/saved_reply.rb6
-rw-r--r--app/views/ci/runner/_setup_runner_in_aws.html.haml16
-rw-r--r--app/views/ci/variables/_index.html.haml1
-rw-r--r--app/views/projects/runners/_project_runners.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml17
-rw-r--r--config/feature_flags/development/ci_remove_character_limitation_raw_masked_var.yml8
-rw-r--r--doc/api/releases/index.md48
-rw-r--r--doc/development/packages/dependency_proxy.md5
-rw-r--r--doc/user/application_security/sast/analyzers.md14
-rw-r--r--doc/user/clusters/agent/install/index.md2
-rw-r--r--doc/user/clusters/migrating_from_gma_to_project_template.md2
-rw-r--r--doc/user/group/manage.md4
-rw-r--r--doc/user/packages/conan_repository/index.md6
-rw-r--r--doc/user/packages/container_registry/reduce_container_registry_data_transfer.md4
-rw-r--r--doc/user/packages/dependency_proxy/index.md2
-rw-r--r--doc/user/packages/generic_packages/index.md2
-rw-r--r--doc/user/packages/go_proxy/index.md4
-rw-r--r--doc/user/packages/package_registry/index.md4
-rw-r--r--doc/user/packages/pypi_repository/index.md2
-rw-r--r--doc/user/project/import/bitbucket.md6
-rw-r--r--doc/user/project/merge_requests/status_checks.md2
-rw-r--r--lib/api/container_registry_event.rb12
-rw-r--r--lib/api/entities/release.rb7
-rw-r--r--lib/gitlab/ci/runner_instructions.rb3
-rw-r--r--lib/gitlab/regex.rb9
-rw-r--r--locale/gitlab.pot26
-rw-r--r--package.json2
-rw-r--r--spec/features/issues/move_spec.rb24
-rw-r--r--spec/features/projects/commits/multi_view_diff_spec.rb4
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js54
-rw-r--r--spec/frontend/right_sidebar_spec.js3
-rw-r--r--spec/frontend/sidebar/components/move/move_issue_button_spec.js157
-rw-r--r--spec/frontend/sidebar/components/move/move_issues_button_spec.js7
-rw-r--r--spec/frontend/sidebar/lib/sidebar_move_issue_spec.js162
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js576
-rw-r--r--spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js72
-rw-r--r--spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_spec.js41
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js2
-rw-r--r--spec/graphql/mutations/saved_replies/create_spec.rb2
-rw-r--r--spec/graphql/mutations/saved_replies/update_spec.rb2
-rw-r--r--spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb2
-rw-r--r--spec/helpers/ci/variables_helper_spec.rb11
-rw-r--r--spec/lib/api/entities/release_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/runner_instructions_spec.rb1
-rw-r--r--spec/lib/gitlab/regex_spec.rb15
-rw-r--r--spec/models/concerns/ci/maskable_spec.rb133
-rw-r--r--spec/requests/api/container_registry_event_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb6
-rw-r--r--spec/support/shared_examples/views/themed_layout_examples.rb2
-rw-r--r--spec/views/layouts/application.html.haml_spec.rb4
-rw-r--r--yarn.lock8
79 files changed, 767 insertions, 1243 deletions
diff --git a/.gitlab/merge_request_templates/Revert To Resolve Incident.md b/.gitlab/merge_request_templates/Revert To Resolve Incident.md
index ab6e65b1388..4e77846575a 100644
--- a/.gitlab/merge_request_templates/Revert To Resolve Incident.md
+++ b/.gitlab/merge_request_templates/Revert To Resolve Incident.md
@@ -11,7 +11,7 @@
### Checklist
- [ ] Create an issue to reinstate the merge request and assign it to the author of the reverted merge request.
-- [ ] If the revert is to resolve a [broken `master' incident](https://about.gitlab.com/handbook/engineering/workflow/#broken-master), please read through the [Responsibilities of the Broken `master` resolution DRI](https://about.gitlab.com/handbook/engineering/workflow/#responsibilities-of-the-resolution-dri).
+- [ ] If the revert is to resolve a [broken 'master' incident](https://about.gitlab.com/handbook/engineering/workflow/#broken-master), please read through the [Responsibilities of the Broken `master` resolution DRI](https://about.gitlab.com/handbook/engineering/workflow/#responsibilities-of-the-resolution-dri).
- [ ] Add the appropriate labels **before** the MR is created. We can skip CI/CD jobs only if the labels are added **before** the CI/CD pipeline is created.
### Milestone info
diff --git a/.rubocop_todo/background_migration/feature_category.yml b/.rubocop_todo/background_migration/feature_category.yml
deleted file mode 100644
index 69467a0b492..00000000000
--- a/.rubocop_todo/background_migration/feature_category.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-BackgroundMigration/FeatureCategory:
- Details: grace period
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 220064e6673..1d36661ee63 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -7,7 +7,6 @@ import { loadStartupCSS } from './load_startup_css';
import initCopyAsGFM from './markdown/copy_as_gfm';
import './quick_submit';
import './requires_input';
-import initSelect2Dropdowns from './select2';
import initPageShortcuts from './shortcuts';
import './toggler_behavior';
import './preview_markdown';
@@ -21,7 +20,6 @@ initCopyToClipboard();
initPageShortcuts();
initCollapseSidebarOnWindowResize();
-initSelect2Dropdowns();
window.requestIdleCallback(
() => {
diff --git a/app/assets/javascripts/behaviors/select2.js b/app/assets/javascripts/behaviors/select2.js
deleted file mode 100644
index 1f222d8c1f6..00000000000
--- a/app/assets/javascripts/behaviors/select2.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import $ from 'jquery';
-import { loadCSSFile } from '../lib/utils/css_utils';
-
-export default () => {
- const $select2Elements = $('select.select2');
- if ($select2Elements.length) {
- import(/* webpackChunkName: 'select2' */ 'select2/select2')
- .then(() => {
- // eslint-disable-next-line promise/no-nesting
- loadCSSFile(gon.select2_css_path)
- .then(() => {
- $select2Elements.select2({
- width: 'resolve',
- minimumResultsForSearch: 10,
- dropdownAutoWidth: true,
- });
-
- // Close select2 on escape
- $('.js-select2').on('select2-close', () => {
- requestAnimationFrame(() => {
- $('.select2-container-active').removeClass('select2-container-active');
- $(':focus').blur();
- });
- });
- })
- .catch(() => {});
- })
- .catch(() => {});
- }
-};
diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
index 2f595342a31..967125c7b0a 100644
--- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
+++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue
@@ -17,7 +17,6 @@ import {
import { getCookie, setCookie } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import Tracking from '~/tracking';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
allEnvironments,
@@ -65,7 +64,7 @@ export default {
GlModal,
GlSprintf,
},
- mixins: [glFeatureFlagsMixin(), trackingMixin],
+ mixins: [trackingMixin],
inject: [
'awsLogoSvgPath',
'awsTipCommandsLink',
@@ -75,7 +74,6 @@ export default {
'environmentScopeLink',
'isProtectedByDefault',
'maskedEnvironmentVariablesLink',
- 'maskableRawRegex',
'maskableRegex',
'protectedEnvironmentVariablesLink',
],
@@ -123,7 +121,7 @@ export default {
},
computed: {
canMask() {
- const regex = RegExp(this.useRawMaskableRegexp ? this.maskableRawRegex : this.maskableRegex);
+ const regex = RegExp(this.maskableRegex);
return regex.test(this.variable.value);
},
canSubmit() {
@@ -136,17 +134,11 @@ export default {
displayMaskedError() {
return !this.canMask && this.variable.masked;
},
- isUsingRawRegexFlag() {
- return this.glFeatures.ciRemoveCharacterLimitationRawMaskedVar;
- },
isEditing() {
return this.mode === EDIT_VARIABLE_ACTION;
},
isExpanded() {
- return !this.isRaw;
- },
- isRaw() {
- return this.variable.raw;
+ return !this.variable.raw;
},
isTipVisible() {
return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key);
@@ -182,9 +174,6 @@ export default {
return true;
},
- useRawMaskableRegexp() {
- return this.isRaw && this.isUsingRawRegexFlag;
- },
variableValidationFeedback() {
return `${this.tokenValidationFeedback} ${this.maskedFeedback}`;
},
@@ -326,7 +315,11 @@ export default {
class="gl-font-monospace!"
spellcheck="false"
/>
- <p v-if="isRaw" class="gl-mt-2 gl-mb-0 text-secondary" data-testid="raw-variable-tip">
+ <p
+ v-if="variable.raw"
+ class="gl-mt-2 gl-mb-0 text-secondary"
+ data-testid="raw-variable-tip"
+ >
{{ __('Variable value will be evaluated as raw string.') }}
</p>
</gl-form-group>
diff --git a/app/assets/javascripts/ci/ci_variable_list/index.js b/app/assets/javascripts/ci/ci_variable_list/index.js
index 33ffbee432f..174a59aba42 100644
--- a/app/assets/javascripts/ci/ci_variable_list/index.js
+++ b/app/assets/javascripts/ci/ci_variable_list/index.js
@@ -21,7 +21,6 @@ const mountCiVariableListApp = (containerEl) => {
isGroup,
isProject,
maskedEnvironmentVariablesLink,
- maskableRawRegex,
maskableRegex,
projectFullPath,
projectId,
@@ -64,7 +63,6 @@ const mountCiVariableListApp = (containerEl) => {
isProject: parsedIsProject,
isProtectedByDefault,
maskedEnvironmentVariablesLink,
- maskableRawRegex,
maskableRegex,
projectFullPath,
projectId,
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index 895c7d0a18e..964c6ca9792 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -4,7 +4,6 @@ import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
import initVariableList from '~/ci/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze';
import registrySettingsApp from '~/packages_and_registries/settings/project/registry_settings_bundle';
-import { initRunnerAwsDeployments } from '~/pages/shared/mount_runner_aws_deployments';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle';
import initSettingsPanels from '~/settings_panels';
@@ -44,7 +43,5 @@ initArtifactsSettings();
initProjectRunners();
initSharedRunnersToggle();
initInstallRunner();
-initRunnerAwsDeployments();
-
initTokenAccess();
initCiSecureFiles();
diff --git a/app/assets/javascripts/pages/shared/mount_runner_aws_deployments.js b/app/assets/javascripts/pages/shared/mount_runner_aws_deployments.js
deleted file mode 100644
index f3807a33a2b..00000000000
--- a/app/assets/javascripts/pages/shared/mount_runner_aws_deployments.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import Vue from 'vue';
-import RunnerAwsDeployments from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue';
-
-export function initRunnerAwsDeployments(componentId = 'js-runner-aws-deployments') {
- const el = document.getElementById(componentId);
-
- if (!el) {
- return null;
- }
-
- return new Vue({
- el,
- render(createElement) {
- return createElement(RunnerAwsDeployments);
- },
- });
-}
diff --git a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
index 02323e5a0c6..9f64ddc8721 100644
--- a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
@@ -206,7 +206,7 @@ export default {
category="primary"
variant="confirm"
:disabled="!Boolean(selectedProject)"
- class="gl-text-center! issuable-move-button"
+ class="gl-w-full issuable-move-button"
@click="handleMoveClick"
>{{ __('Move') }}</gl-button
>
diff --git a/app/assets/javascripts/sidebar/components/move/move_issue_button.vue b/app/assets/javascripts/sidebar/components/move/move_issue_button.vue
new file mode 100644
index 00000000000..e1259fad6a7
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/move/move_issue_button.vue
@@ -0,0 +1,71 @@
+<script>
+import ProjectSelect from '~/sidebar/components/move/issuable_move_dropdown.vue';
+import { __ } from '~/locale';
+import { createAlert } from '~/flash';
+import { visitUrl } from '~/lib/utils/url_utility';
+import moveIssueMutation from '../../queries/move_issue.mutation.graphql';
+
+export default {
+ name: 'MoveIssueButton',
+ components: { ProjectSelect },
+ inject: ['projectsAutocompleteEndpoint', 'projectFullPath', 'issueIid'],
+
+ i18n: {
+ title: __('Move issue'),
+ titleInProgress: __('Moving issue'),
+ moveErrorMessage: __('An error occurred while moving the issue.'),
+ },
+ data() {
+ return {
+ moveInProgress: false,
+ };
+ },
+ computed: {
+ dropdownButtonTitle() {
+ return this.moveInProgress ? this.$options.i18n.titleInProgress : this.$options.i18n.title;
+ },
+ },
+ methods: {
+ moveIssue(targetProject) {
+ this.moveInProgress = true;
+ return this.$apollo
+ .mutate({
+ mutation: moveIssueMutation,
+ variables: {
+ moveIssueInput: {
+ projectPath: this.projectFullPath,
+ iid: this.issueIid,
+ targetProjectPath: targetProject.full_path,
+ },
+ },
+ })
+ .then(({ data = {} }) => {
+ if (!data.issueMove) return;
+
+ const { errors } = data.issueMove;
+ if (errors?.length > 0) {
+ throw new Error(`Error moving the issue. Error message: ${errors[0].message}`);
+ }
+ visitUrl(data.issueMove?.issue.webUrl);
+ })
+ .catch((error) => {
+ this.moveInProgress = false;
+ createAlert({
+ message: this.$options.i18n.moveErrorMessage,
+ captureError: true,
+ error,
+ });
+ });
+ },
+ },
+};
+</script>
+<template>
+ <project-select
+ :projects-fetch-path="projectsAutocompleteEndpoint"
+ :dropdown-button-title="dropdownButtonTitle"
+ :dropdown-header-title="$options.i18n.title"
+ :move-in-progress="moveInProgress"
+ @move-issuable="moveIssue"
+ />
+</template>
diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
deleted file mode 100644
index 2cce27df598..00000000000
--- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import $ from 'jquery';
-import { escape } from 'lodash';
-import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
-import { createAlert } from '~/flash';
-import { __ } from '~/locale';
-
-function isValidProjectId(id) {
- return id > 0;
-}
-
-class SidebarMoveIssue {
- constructor(mediator, dropdownToggle, confirmButton) {
- this.mediator = mediator;
-
- this.$dropdownToggle = $(dropdownToggle);
- this.$confirmButton = $(confirmButton);
-
- this.onConfirmClickedWrapper = this.onConfirmClicked.bind(this);
- }
-
- init() {
- this.initDropdown();
- this.addEventListeners();
- }
-
- destroy() {
- this.removeEventListeners();
- }
-
- initDropdown() {
- initDeprecatedJQueryDropdown(this.$dropdownToggle, {
- search: {
- fields: ['name_with_namespace'],
- },
- showMenuAbove: true,
- selectable: true,
- filterable: true,
- filterRemote: true,
- multiSelect: false,
- // Keep the dropdown open after selecting an option
- shouldPropagate: false,
- data: (searchTerm, callback) => {
- this.mediator
- .fetchAutocompleteProjects(searchTerm)
- .then(callback)
- .catch(() =>
- createAlert({
- message: __('An error occurred while fetching projects autocomplete.'),
- }),
- );
- },
- renderRow: (project) => `
- <li>
- <a href="#" class="js-move-issue-dropdown-item">
- ${escape(project.name_with_namespace)}
- </a>
- </li>
- `,
- clicked: (options) => {
- const project = options.selectedObj;
- const selectedProjectId = options.isMarking ? project.id : 0;
- this.mediator.setMoveToProjectId(selectedProjectId);
-
- this.$confirmButton.prop('disabled', !isValidProjectId(selectedProjectId));
- },
- });
- }
-
- addEventListeners() {
- this.$confirmButton.on('click', this.onConfirmClickedWrapper);
- }
-
- removeEventListeners() {
- this.$confirmButton.off('click', this.onConfirmClickedWrapper);
- }
-
- onConfirmClicked() {
- if (isValidProjectId(this.mediator.store.moveToProjectId)) {
- this.$confirmButton.disable().addClass('is-loading');
-
- this.mediator.moveIssue().catch(() => {
- createAlert({ message: __('An error occurred while moving the issue.') });
- this.$confirmButton.enable().removeClass('is-loading');
- });
- }
- }
-}
-
-export default SidebarMoveIssue;
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index fc12b0b04eb..facc07e8ce5 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -1,4 +1,3 @@
-import $ from 'jquery';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
@@ -43,8 +42,8 @@ import SidebarTimeTracking from './components/time_tracking/sidebar_time_trackin
import SidebarTodoWidget from './components/todo_toggle/sidebar_todo_widget.vue';
import { IssuableAttributeType } from './constants';
import CrmContacts from './components/crm_contacts/crm_contacts.vue';
-import SidebarMoveIssue from './lib/sidebar_move_issue';
import trackShowInviteMemberLink from './track_invite_members';
+import MoveIssueButton from './components/move/move_issue_button.vue';
Vue.use(Translate);
Vue.use(VueApollo);
@@ -701,6 +700,31 @@ export function mountSubscriptionsDropdown() {
});
}
+export function mountMoveIssueButton() {
+ const el = document.querySelector('.js-sidebar-move-issue-block');
+
+ if (!el) {
+ return null;
+ }
+
+ const { projectsAutocompleteEndpoint } = getSidebarOptions();
+ const { projectFullPath, issueIid } = el.dataset;
+
+ Vue.use(VueApollo);
+
+ return new Vue({
+ el,
+ name: 'MoveIssueDropdownRoot',
+ apolloProvider,
+ provide: {
+ projectsAutocompleteEndpoint,
+ projectFullPath,
+ issueIid,
+ },
+ render: (createElement) => createElement(MoveIssueButton),
+ });
+}
+
const isAssigneesWidgetShown =
(isInIssuePage() || isInDesignPage() || isInMRPage()) && gon.features.issueAssigneesWidget;
@@ -727,12 +751,7 @@ export function mountSidebar(mediator, store) {
mountSidebarTimeTracking();
mountSidebarSeverityWidget();
mountSidebarEscalationStatus();
-
- new SidebarMoveIssue(
- mediator,
- $('.js-move-issue'),
- $('.js-move-issue-confirmation-button'),
- ).init();
+ mountMoveIssueButton();
}
export { getSidebarOptions };
diff --git a/app/assets/javascripts/sidebar/queries/move_issue.mutation.graphql b/app/assets/javascripts/sidebar/queries/move_issue.mutation.graphql
index d350072425b..e3ed3c5089b 100644
--- a/app/assets/javascripts/sidebar/queries/move_issue.mutation.graphql
+++ b/app/assets/javascripts/sidebar/queries/move_issue.mutation.graphql
@@ -1,5 +1,9 @@
mutation moveIssue($moveIssueInput: IssueMoveInput!) {
issueMove(input: $moveIssueInput) {
+ issue {
+ id
+ webUrl
+ }
errors
}
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 6ce4e1b4706..77151ab4307 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -524,6 +524,7 @@ export default {
v-model="removeSourceBranch"
:disabled="isRemoveSourceBranchButtonDisabled"
class="js-remove-source-branch-checkbox gl-display-flex gl-align-items-center gl-mr-5 gl-mb-3 gl-md-mb-0"
+ data-testid="delete-source-branch-checkbox"
>
{{ __('Delete source branch') }}
</gl-form-checkbox>
@@ -699,7 +700,11 @@ export default {
:merge-commit-path="mr.mergeCommitPath"
/>
</li>
- <li v-if="mr.state !== 'closed'" class="gl-line-height-normal">
+ <li
+ v-if="mr.state !== 'closed'"
+ class="gl-line-height-normal"
+ data-testid="source-branch-deleted-text"
+ >
{{ sourceBranchDeletedText }}
</li>
<li v-if="mr.relatedLinks" class="gl-line-height-normal">
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue
deleted file mode 100644
index e3e3b9abc3c..00000000000
--- a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<script>
-import { GlButton, GlModalDirective } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import RunnerAwsDeploymentsModal from './runner_aws_deployments_modal.vue';
-
-export default {
- components: {
- GlButton,
- RunnerAwsDeploymentsModal,
- },
- directives: {
- GlModalDirective,
- },
- modalId: 'runner-aws-deployments-modal',
- i18n: {
- buttonText: s__('Runners|Deploy GitLab Runner in AWS'),
- },
- data() {
- return {
- opened: false,
- };
- },
- methods: {
- onClick() {
- this.opened = true;
- },
- },
-};
-</script>
-<template>
- <div>
- <gl-button
- v-gl-modal-directive="$options.modalId"
- class="gl-mt-4"
- data-testid="show-modal-button"
- variant="confirm"
- @click="onClick"
- >
- {{ $options.i18n.buttonText }}
- </gl-button>
- <runner-aws-deployments-modal v-if="opened" :modal-id="$options.modalId" />
- </div>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
deleted file mode 100644
index 08acde1aefc..00000000000
--- a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
+++ /dev/null
@@ -1,29 +0,0 @@
-<script>
-import { GlModal } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import RunnerAwsInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue';
-
-export default {
- components: {
- GlModal,
- RunnerAwsInstructions,
- },
- props: {
- modalId: {
- type: String,
- required: true,
- },
- },
- methods: {
- onClose() {
- this.$refs.modal.close();
- },
- },
- i18n_title: s__('Runners|Deploy GitLab Runner in AWS'),
-};
-</script>
-<template>
- <gl-modal ref="modal" :modal-id="modalId" :title="$options.i18n_title" hide-footer size="sm">
- <runner-aws-instructions @close="onClose" />
- </gl-modal>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
index 3dbc5246c3d..b66c89d1372 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
@@ -4,6 +4,7 @@ export const REGISTRATION_TOKEN_PLACEHOLDER = '$REGISTRATION_TOKEN';
export const PLATFORM_DOCKER = 'docker';
export const PLATFORM_KUBERNETES = 'kubernetes';
+export const PLATFORM_AWS = 'aws';
export const AWS_README_URL =
'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/easybuttons.md';
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue
index cafebdfe5f4..8a234889e6f 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue
@@ -2,6 +2,7 @@
import {
GlButton,
GlSprintf,
+ GlIcon,
GlLink,
GlFormRadioGroup,
GlFormRadio,
@@ -11,6 +12,7 @@ import {
import Tracking from '~/tracking';
import { getBaseURL, objectToQuery, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import {
AWS_README_URL,
AWS_CF_BASE_URL,
@@ -22,13 +24,22 @@ export default {
components: {
GlButton,
GlSprintf,
+ GlIcon,
GlLink,
GlFormRadioGroup,
GlFormRadio,
GlAccordion,
GlAccordionItem,
+ ModalCopyButton,
},
mixins: [Tracking.mixin()],
+ props: {
+ registrationToken: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
data() {
return {
selectedIndex: 0,
@@ -65,16 +76,20 @@ export default {
},
},
i18n: {
- title: s__('Runners|Deploy GitLab Runner in AWS'),
instructions: s__(
- 'Runners|Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console.',
+ 'Runners|Select your preferred runner, then choose the capacity for the runner in the AWS CloudFormation console.',
),
chooseRunner: s__('Runners|Choose your preferred GitLab Runner'),
dontSeeWhatYouAreLookingFor: s__(
"Runners|Don't see what you are looking for? See the full list of options, including a fully customizable option %{linkStart}here%{linkEnd}.",
),
+ runnerRegistrationToken: s__('Runners|Runner Registration token'),
+ copyInstructions: s__('Runners|Copy registration token'),
moreDetails: __('More Details'),
lessDetails: __('Less Details'),
+ close: __('Close'),
+ deployRunnerInAws: s__('Runners|Deploy GitLab Runner in AWS'),
+ externalLink: __('(external link)'),
},
readmeUrl: AWS_README_URL,
easyButtons: AWS_EASY_BUTTONS,
@@ -83,6 +98,7 @@ export default {
<template>
<div>
<p>{{ $options.i18n.instructions }}</p>
+
<gl-form-radio-group v-model="selectedIndex" :label="$options.i18n.chooseRunner" label-sr-only>
<gl-form-radio
v-for="(easyButton, idx) in $options.easyButtons"
@@ -113,10 +129,23 @@ export default {
</template>
</gl-sprintf>
</p>
+ <template v-if="registrationToken">
+ <h5 class="gl-mb-3">{{ $options.i18n.runnerRegistrationToken }}</h5>
+ <div class="gl-display-flex">
+ <pre class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line">{{ registrationToken }}</pre>
+ <modal-copy-button
+ :title="$options.i18n.copyInstructions"
+ :text="registrationToken"
+ css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
+ category="tertiary"
+ />
+ </div>
+ </template>
<footer class="gl-display-flex gl-justify-content-end gl-pt-3 gl-gap-3">
- <gl-button @click="onClose()">{{ __('Close') }}</gl-button>
+ <gl-button @click="onClose()">{{ $options.i18n.close }}</gl-button>
<gl-button variant="confirm" @click="onOk()">
- {{ s__('Runners|Deploy GitLab Runner in AWS') }}
+ {{ $options.i18n.deployRunnerInAws }}
+ <gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-button>
</footer>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
index 729fe9c462c..22d9b88fa41 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
@@ -14,11 +14,12 @@ import {
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { __, s__ } from '~/locale';
import getRunnerPlatformsQuery from './graphql/get_runner_platforms.query.graphql';
-import { PLATFORM_DOCKER, PLATFORM_KUBERNETES } from './constants';
+import { PLATFORM_DOCKER, PLATFORM_KUBERNETES, PLATFORM_AWS } from './constants';
import RunnerCliInstructions from './instructions/runner_cli_instructions.vue';
import RunnerDockerInstructions from './instructions/runner_docker_instructions.vue';
import RunnerKubernetesInstructions from './instructions/runner_kubernetes_instructions.vue';
+import RunnerAwsInstructions from './instructions/runner_aws_instructions.vue';
export default {
components: {
@@ -104,6 +105,8 @@ export default {
return RunnerDockerInstructions;
case PLATFORM_KUBERNETES:
return RunnerKubernetesInstructions;
+ case PLATFORM_AWS:
+ return RunnerAwsInstructions;
default:
return null;
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index eb34d91476b..a07a57f40f7 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -741,20 +741,6 @@
top: calc(#{$header-height} + #{$performance-bar-height});
}
-.sidebar-move-issue-confirmation-button {
- width: 100%;
-
- &.is-loading {
- .sidebar-move-issue-confirmation-loading-icon {
- display: inline-block;
- }
- }
-}
-
-.sidebar-move-issue-confirmation-loading-icon {
- display: none;
-}
-
.issuable-show-labels {
.gl-label {
margin-bottom: 5px;
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 332e465914b..ade58ca0970 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -13,10 +13,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
before_action :disable_query_limiting, only: [:usage_data]
- before_action do
- push_frontend_feature_flag(:ci_remove_character_limitation_raw_masked_var, type: :development)
- end
-
feature_category :not_owned, [ # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
:general, :reporting, :metrics_and_profiling, :network,
:preferences, :update, :reset_health_check_token
diff --git a/app/controllers/groups/email_campaigns_controller.rb b/app/controllers/groups/email_campaigns_controller.rb
index 1ccaf5798b6..8ae429de490 100644
--- a/app/controllers/groups/email_campaigns_controller.rb
+++ b/app/controllers/groups/email_campaigns_controller.rb
@@ -44,7 +44,7 @@ class Groups::EmailCampaignsController < Groups::ApplicationController
when :team, :team_short
group_group_members_url(group)
when :admin_verify
- project_settings_ci_cd_path(group.projects.first, ci_runner_templates: true, anchor: 'js-runners-settings')
+ project_settings_ci_cd_path(group.projects.first, anchor: 'js-runners-settings')
end
end
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index 3562ee55c36..78e3ffa4af9 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -11,10 +11,6 @@ module Groups
before_action :push_licensed_features, only: [:show]
before_action :assign_variables_to_gon, only: [:show]
- before_action do
- push_frontend_feature_flag(:ci_remove_character_limitation_raw_masked_var, type: :development)
- end
-
feature_category :continuous_integration
urgency :low
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 5ec1f606ef2..b330aacf3e9 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -12,10 +12,6 @@ module Projects
before_action :check_builds_available!
before_action :define_variables
- before_action do
- push_frontend_feature_flag(:ci_remove_character_limitation_raw_masked_var, type: :development)
- end
-
helper_method :highlight_badge
feature_category :continuous_integration
diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb
index c93c8dd8d76..823332c3d1d 100644
--- a/app/helpers/ci/pipelines_helper.rb
+++ b/app/helpers/ci/pipelines_helper.rb
@@ -101,7 +101,7 @@ module Ci
has_gitlab_ci: has_gitlab_ci?(project).to_s,
pipeline_editor_path: can?(current_user, :create_pipeline, project) && project_ci_pipeline_editor_path(project),
suggested_ci_templates: suggested_ci_templates.to_json,
- ci_runner_settings_path: project_settings_ci_cd_path(project, ci_runner_templates: true, anchor: 'js-runners-settings')
+ ci_runner_settings_path: project_settings_ci_cd_path(project, anchor: 'js-runners-settings')
}
experiment(:runners_availability_section, namespace: project.root_ancestor) do |e|
diff --git a/app/helpers/ci/variables_helper.rb b/app/helpers/ci/variables_helper.rb
index a492c48e58c..84572363a8d 100644
--- a/app/helpers/ci/variables_helper.rb
+++ b/app/helpers/ci/variables_helper.rb
@@ -47,10 +47,6 @@ module Ci
]
end
- def ci_variable_maskable_raw_regex
- Ci::Maskable::MASK_AND_RAW_REGEX.inspect.sub('\\A', '^').sub('\\z', '$')[1...-1]
- end
-
def ci_variable_maskable_regex
Ci::Maskable::REGEX.inspect.sub('\\A', '^').sub('\\z', '$').sub(%r{^/}, '').sub(%r{/[a-z]*$}, '').gsub('\/', '/')
end
diff --git a/app/models/concerns/ci/maskable.rb b/app/models/concerns/ci/maskable.rb
index eb0852f139b..62be0150ee0 100644
--- a/app/models/concerns/ci/maskable.rb
+++ b/app/models/concerns/ci/maskable.rb
@@ -12,30 +12,10 @@ module Ci
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~'
# * Absolutely no fun is allowed
REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}.freeze
- # * Single line
- # * No spaces
- # * Minimal length of 8 characters
- # * Some fun is allowed
- MASK_AND_RAW_REGEX = %r{\A\S{8,}\z}.freeze
included do
validates :masked, inclusion: { in: [true, false] }
- validates :value, format: { with: REGEX }, if: :masked_and_expanded?
- validates :value, format: { with: MASK_AND_RAW_REGEX }, if: :masked_and_raw?
- end
-
- def masked_and_raw?
- return false unless Feature.enabled?(:ci_remove_character_limitation_raw_masked_var)
- return false unless self.class.method_defined?(:raw)
-
- masked? && raw?
- end
-
- def masked_and_expanded?
- return true unless Feature.enabled?(:ci_remove_character_limitation_raw_masked_var)
- return true unless self.class.method_defined?(:raw)
-
- masked? && !raw?
+ validates :value, format: { with: REGEX }, if: :masked?
end
def to_runner_variable
diff --git a/app/models/users/saved_reply.rb b/app/models/users/saved_reply.rb
index 7737d826b05..61459abe24b 100644
--- a/app/models/users/saved_reply.rb
+++ b/app/models/users/saved_reply.rb
@@ -9,11 +9,7 @@ module Users
validates :user_id, :name, :content, presence: true
validates :name,
length: { maximum: 255 },
- uniqueness: { scope: [:user_id] },
- format: {
- with: Gitlab::Regex.saved_reply_name_regex,
- message: Gitlab::Regex.saved_reply_name_regex_message
- }
+ uniqueness: { scope: [:user_id] }
validates :content, length: { maximum: 10000 }
end
end
diff --git a/app/views/ci/runner/_setup_runner_in_aws.html.haml b/app/views/ci/runner/_setup_runner_in_aws.html.haml
deleted file mode 100644
index 09fa0176da6..00000000000
--- a/app/views/ci/runner/_setup_runner_in_aws.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-%h5= _('Use GitLab Runner in AWS')
-
-%p
- = _('Use an AWS CloudFormation Template (CFT) to install and configure GitLab Runner in AWS.')
-
-%ol
- %li
- = _('Copy this registration token.')
- %br
- %code#registration_token{ data: { testid: 'registration_token' } }= registration_token
- = clipboard_button(target: '#registration_token', title: _('Copy token'))
- %li
- = _('Choose the preferred Runner and populate the AWS CFT.')
- = link_to _('Learn more.'), 'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg', target: '_blank', rel: 'noopener noreferrer'
-
-#js-runner-aws-deployments
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index 692c9cbe7d9..af98025d257 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -17,7 +17,6 @@
is_group: is_group.to_s,
group_id: @group&.id || '',
group_path: @group&.full_path,
- maskable_raw_regex: ci_variable_maskable_raw_regex,
maskable_regex: ci_variable_maskable_regex,
protected_by_default: ci_variable_protected_by_default?.to_s,
aws_logo_svg_path: image_path('aws_logo.svg'),
diff --git a/app/views/projects/runners/_project_runners.html.haml b/app/views/projects/runners/_project_runners.html.haml
index fc4377e2f98..543a564568b 100644
--- a/app/views/projects/runners/_project_runners.html.haml
+++ b/app/views/projects/runners/_project_runners.html.haml
@@ -4,10 +4,6 @@
.bs-callout.help-callout
- if can?(current_user, :register_project_runners, @project)
= s_('Runners|These runners are assigned to this project.')
- - if params[:ci_runner_templates]
- %hr
- = render partial: 'ci/runner/setup_runner_in_aws',
- locals: { registration_token: @project.runners_token }
%hr
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @project.runners_token,
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 0fb3f16b60e..be4a9291d95 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -97,21 +97,8 @@
.block
.js-sidebar-copy-email-root
- if issuable_sidebar.dig(:current_user, :can_move)
- .block.js-sidebar-move-issue-block
- .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') }
- = sprite_icon('long-arrow')
- .dropdown.sidebar-move-issue-dropdown.hide-collapsed
- = render Pajamas::ButtonComponent.new(block: true, button_options: { class: 'js-sidebar-dropdown-toggle js-move-issue', data: { toggle: 'dropdown', display: 'static', track_label: "right_sidebar", track_property: "move_issue", track_action: "click_button", track_value: "" } }) do
- = _('Move issue')
- .dropdown-menu.dropdown-menu-selectable.dropdown-extended-height
- = dropdown_title(_('Move issue'))
- = dropdown_filter(_('Search project'), search_id: 'sidebar-move-issue-dropdown-search')
- = dropdown_content
- = dropdown_loading
- = dropdown_footer add_content_class: true do
- %button.gl-button.btn.btn-confirm.sidebar-move-issue-confirmation-button.js-move-issue-confirmation-button{ type: 'button', disabled: true }
- = gl_loading_icon(inline: true, css_class: 'sidebar-move-issue-confirmation-loading-icon gl-mr-2')
- = _('Move')
+ .block
+ .js-sidebar-move-issue-block{ data: { project_full_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } }
-# haml-lint:disable InlineJavaScript
%script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar).to_json.html_safe
diff --git a/config/feature_flags/development/ci_remove_character_limitation_raw_masked_var.yml b/config/feature_flags/development/ci_remove_character_limitation_raw_masked_var.yml
deleted file mode 100644
index bd293de9962..00000000000
--- a/config/feature_flags/development/ci_remove_character_limitation_raw_masked_var.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ci_remove_character_limitation_raw_masked_var
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109008
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/388414
-milestone: '15.9'
-type: development
-group: group::pipeline authoring
-default_enabled: false
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index 1ff4f916b4c..2e44a33cb89 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -44,11 +44,16 @@ GET /projects/:id/releases
If successful, returns [`200 OK`](../../api/rest/index.md#status-codes) and the following
response attributes:
-| Attribute | Type | Description |
-|:---------------------|:--------|:------------------------------------- |
-| `[]._links` | object | Links of the release. |
-| `[]._links.self` | string | HTTP URL of the release. |
-| `[]._links.edit_url` | string | HTTP URL of the release's edit page. |
+| Attribute | Type | Description |
+|:--------------------------------------|:-------|:-------------------------------------------------|
+| `[]._links` | object | Links of the release. |
+| `[]._links.closed_issues_url` | string | HTTP URL of the release's closed issues. |
+| `[]._links.closed_merge_requests_url` | string | HTTP URL of the release's closed merge requests. |
+| `[]._links.edit_url` | string | HTTP URL of the release's edit page. |
+| `[]._links.merged_merge_requests_url` | string | HTTP URL of the release's merged merge requests. |
+| `[]._links.opened_issues_url` | string | HTTP URL of the release's open issues. |
+| `[]._links.opened_merge_requests_url` | string | HTTP URL of the release's open merge requests. |
+| `[]._links.self` | string | HTTP URL of the release. |
Example request:
@@ -237,8 +242,13 @@ Example response:
}
],
"_links": {
- "self": "https://gitlab.example.com/root/awesome-app/-/releases/v0.1",
- "edit_url": "https://gitlab.example.com/root/awesome-app/-/releases/v0.1/edit"
+ "closed_issues_url": "https://gitlab.example.com/root/awesome-app/-/issues?release_tag=v0.1&scope=all&state=closed",
+ "closed_merge_requests_url": "https://gitlab.example.com/root/awesome-app/-/merge_requests?release_tag=v0.1&scope=all&state=closed",
+ "edit_url": "https://gitlab.example.com/root/awesome-app/-/releases/v0.1/edit",
+ "merged_merge_requests_url": "https://gitlab.example.com/root/awesome-app/-/merge_requests?release_tag=v0.1&scope=all&state=merged",
+ "opened_issues_url": "https://gitlab.example.com/root/awesome-app/-/issues?release_tag=v0.1&scope=all&state=opened",
+ "opened_merge_requests_url": "https://gitlab.example.com/root/awesome-app/-/merge_requests?release_tag=v0.1&scope=all&state=opened",
+ "self": "https://gitlab.example.com/root/awesome-app/-/releases/v0.1"
}
}
]
@@ -263,11 +273,16 @@ GET /projects/:id/releases/:tag_name
If successful, returns [`200 OK`](../../api/rest/index.md#status-codes) and the following
response attributes:
-| Attribute | Type | Description |
-|:------------------|:--------|:------------------------------------- |
-| `_links` | object | Links of the release. |
-| `_links.self` | string | HTTP URL of the release. |
-| `_links.edit_url` | string | HTTP URL of the release's edit page. |
+| Attribute | Type | Description |
+|:--------------------------------------|:-------|:-------------------------------------------------|
+| `[]._links` | object | Links of the release. |
+| `[]._links.closed_issues_url` | string | HTTP URL of the release's closed issues. |
+| `[]._links.closed_merge_requests_url` | string | HTTP URL of the release's closed merge requests. |
+| `[]._links.edit_url` | string | HTTP URL of the release's edit page. |
+| `[]._links.merged_merge_requests_url` | string | HTTP URL of the release's merged merge requests. |
+| `[]._links.opened_issues_url` | string | HTTP URL of the release's open issues. |
+| `[]._links.opened_merge_requests_url` | string | HTTP URL of the release's open merge requests. |
+| `[]._links.self` | string | HTTP URL of the release. |
Example request:
@@ -383,8 +398,13 @@ Example response:
"collected_at": "2019-07-16T14:00:12.256Z"
},
"_links": {
- "self": "https://gitlab.example.com/root/awesome-app/-/releases/v0.1",
- "edit_url": "https://gitlab.example.com/root/awesome-app/-/releases/v0.1/edit"
+ "closed_issues_url": "https://gitlab.example.com/root/awesome-app/-/issues?release_tag=v0.1&scope=all&state=closed",
+ "closed_merge_requests_url": "https://gitlab.example.com/root/awesome-app/-/merge_requests?release_tag=v0.1&scope=all&state=closed",
+ "edit_url": "https://gitlab.example.com/root/awesome-app/-/releases/v0.1/edit",
+ "merged_merge_requests_url": "https://gitlab.example.com/root/awesome-app/-/merge_requests?release_tag=v0.1&scope=all&state=merged",
+ "opened_issues_url": "https://gitlab.example.com/root/awesome-app/-/issues?release_tag=v0.1&scope=all&state=opened",
+ "opened_merge_requests_url": "https://gitlab.example.com/root/awesome-app/-/merge_requests?release_tag=v0.1&scope=all&state=opened",
+ "self": "https://gitlab.example.com/root/awesome-app/-/releases/v0.1"
}
]
}
diff --git a/doc/development/packages/dependency_proxy.md b/doc/development/packages/dependency_proxy.md
index cc8b202e556..e9568699c7e 100644
--- a/doc/development/packages/dependency_proxy.md
+++ b/doc/development/packages/dependency_proxy.md
@@ -6,9 +6,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Dependency Proxy
-The Dependency Proxy is a pull-through-cache for registry images from DockerHub. This document describes how this
+The Dependency Proxy is a pull-through-cache for public registry images from DockerHub. This document describes how this
feature is constructed in GitLab.
+NOTE:
+Support for private registry images is proposed in [issue 331741](https://gitlab.com/gitlab-org/gitlab/-/issues/331741).
+
## Container registry
The Dependency Proxy for the container registry acts a stand-in for a remote container registry. In our case,
diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md
index efbbf447845..8e58af35855 100644
--- a/doc/user/application_security/sast/analyzers.md
+++ b/doc/user/application_security/sast/analyzers.md
@@ -12,7 +12,7 @@ Static Application Security Testing (SAST) uses analyzers
to detect vulnerabilities in source code. Each analyzer is a wrapper around a [scanner](../terminology/index.md#scanner), a third-party code analysis tool.
The analyzers are published as Docker images that SAST uses to launch dedicated containers for each
-analysis. We recommend a minimum of 4GB RAM to ensure consistent performance of the analyzers.
+analysis. We recommend a minimum of 4 GB RAM to ensure consistent performance of the analyzers.
SAST default images are maintained by GitLab, but you can also integrate your own custom image.
@@ -77,8 +77,8 @@ Work to remove language-specific analyzers and replace them with the Semgrep-bas
You can choose to disable the other analyzers early and use Semgrep-based scanning for supported languages before the default behavior changes. If you do so:
-- You'll enjoy significantly faster scanning, reduced CI minutes usage, and more customizable scanning rules.
-- However, vulnerabilities previously reported by language-specific analyzers will be reported again under certain conditions, including if you've dismissed the vulnerabilities before. The system behavior depends on:
+- You enjoy significantly faster scanning, reduced CI minutes usage, and more customizable scanning rules.
+- However, vulnerabilities previously reported by language-specific analyzers are reported again under certain conditions, including if you've dismissed the vulnerabilities before. The system behavior depends on:
- whether you've excluded the Semgrep-based analyzer from running in the past.
- which analyzer first discovered the vulnerabilities shown in the project's [Vulnerability Report](../vulnerability_report/index.md).
@@ -92,7 +92,7 @@ The Vulnerability Management system automatically moves vulnerabilities from the
- For Go, a vulnerability is moved if it has only ever been detected by Gosec in pipelines where Semgrep also detected it. Semgrep coverage for Go was introduced by default into the CI/CD template in GitLab 14.2 (August 2021).
- For JavaScript and TypeScript, a vulnerability is moved if it has only ever been detected by ESLint in pipelines where Semgrep also detected it. Semgrep coverage for these languages was introduced into the CI/CD template in GitLab 13.12 (May 2021).
-However, you'll see old vulnerabilities re-created based on Semgrep results if:
+However, old vulnerabilities re-created based on Semgrep results are visible if:
- A vulnerability was created by Bandit or SpotBugs and you disable those analyzers. We only recommend disabling Bandit and SpotBugs now if the analyzers aren't working. Work to automatically translate Bandit and SpotBugs vulnerabilities to Semgrep is tracked in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/328062).
- A vulnerability was created by ESLint, Gosec, or Flawfinder in a default-branch pipeline where Semgrep scanning did not run successfully (before Semgrep coverage was introduced for the language, because you disabled Semgrep explicitly, or because the Semgrep scan failed in that pipeline). We do not currently plan to combine these vulnerabilities if they already exist.
@@ -119,13 +119,13 @@ To switch to Semgrep-based scanning early, you can:
1. Create a merge request (MR) to set the [`SAST_EXCLUDED_ANALYZERS` CI/CD variable](#disable-specific-default-analyzers) to `"bandit,gosec,eslint"`.
- If you also want to disable SpotBugs scanning, add `spotbugs` to the list. We only recommend this for Java projects. SpotBugs is the only current analyzer that can scan Groovy, Kotlin, and Scala.
- If you also want to disable Flawfinder scanning, add `flawfinder` to the list. We only recommend this for C projects. Flawfinder is the only current analyzer that can scan C++.
-1. Verify that scanning jobs succeed in the MR. You'll notice findings from the removed analyzers in _Fixed_ and findings from Semgrep in _New_. (Some findings may show different names, descriptions, and severities, since GitLab manages and edits the Semgrep rulesets.)
+1. Verify that scanning jobs succeed in the MR. Findings from the removed analyzers are available in _Fixed_ and findings from Semgrep in _New_. (Some findings may show different names, descriptions, and severities, since GitLab manages and edits the Semgrep rulesets.)
1. Merge the MR and wait for the default-branch pipeline to run.
1. Use the Vulnerability Report to dismiss the findings that are no longer detected by the language-specific analyzers.
#### Preview Semgrep-based scanning
-You can see how Semgrep-based scanning will work in your projects before the GitLab-managed Stable CI/CD template for SAST is updated.
+You can see how Semgrep-based scanning works in your projects before the GitLab-managed Stable CI/CD template for SAST is updated.
We recommend that you test this change in a merge request but continue using the Stable template in your default branch pipeline configuration.
In GitLab 15.3, we [activated a feature flag](https://gitlab.com/gitlab-org/gitlab/-/issues/362179) to migrate security findings on the default branch from other analyzers to Semgrep.
@@ -148,7 +148,7 @@ To preview the upcoming changes to the CI/CD configuration in GitLab 15.3 or ear
remote: 'https://gitlab.com/gitlab-org/gitlab/-/raw/2851f4d5/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml'
```
-1. Verify that scanning jobs succeed in the MR. You'll notice findings from the removed analyzers in _Fixed_ and findings from Semgrep in _New_. (Some findings may show different names, descriptions, and severities, since GitLab manages and edits the Semgrep rulesets.)
+1. Verify that scanning jobs succeed in the MR. You notice findings from the removed analyzers in _Fixed_ and findings from Semgrep in _New_. (Some findings may show different names, descriptions, and severities, since GitLab manages and edits the Semgrep rulesets.)
1. Close the MR.
To learn more about Stable and Latest templates, see documentation on [CI/CD template versioning](../../../development/cicd/templates.md#versioning).
diff --git a/doc/user/clusters/agent/install/index.md b/doc/user/clusters/agent/install/index.md
index 62767f1dfd9..297210ab8ef 100644
--- a/doc/user/clusters/agent/install/index.md
+++ b/doc/user/clusters/agent/install/index.md
@@ -22,7 +22,7 @@ Before you can install the agent in your cluster, you need:
- [Digital Ocean](https://docs.digitalocean.com/products/kubernetes/quickstart/)
- On self-managed GitLab instances, a GitLab administrator must set up the
[agent server](../../../../administration/clusters/kas.md).
- Then it will be available by default at `wss://gitlab.example.com/-/kubernetes-agent/`.
+ Then it is available by default at `wss://gitlab.example.com/-/kubernetes-agent/`.
On GitLab.com, the agent server is available at `wss://kas.gitlab.com`.
## Installation steps
diff --git a/doc/user/clusters/migrating_from_gma_to_project_template.md b/doc/user/clusters/migrating_from_gma_to_project_template.md
index a86a84fe9ae..e5d81091094 100644
--- a/doc/user/clusters/migrating_from_gma_to_project_template.md
+++ b/doc/user/clusters/migrating_from_gma_to_project_template.md
@@ -77,7 +77,7 @@ See also [video walk-throughs](#video-walk-throughs) with examples.
1. Overwrite `applications/gitlab-runner/values.yaml` with the output of the previous command.
- This safe step will guarantee that no unexpected default values overwrite your currently deployed values.
+ This safe step guarantees that no unexpected default values overwrite your currently deployed values.
For instance, your GitLab Runner could have its `gitlabUrl` or `runnerRegistrationToken` overwritten by mistake.
1. Some apps require special attention:
diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md
index a755447c47c..b524a17eae3 100644
--- a/doc/user/group/manage.md
+++ b/doc/user/group/manage.md
@@ -33,8 +33,8 @@ To create a group:
1. Choose the [visibility level](../public_access.md).
1. Personalize your GitLab experience by answering the following questions:
- What is your role?
- - Who will be using this group?
- - What will you use this group for?
+ - Who is using this group?
+ - What are you using this group for?
1. Invite GitLab members or other users to join the group.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
diff --git a/doc/user/packages/conan_repository/index.md b/doc/user/packages/conan_repository/index.md
index dd6605c2f01..05daa525893 100644
--- a/doc/user/packages/conan_repository/index.md
+++ b/doc/user/packages/conan_repository/index.md
@@ -121,7 +121,9 @@ To authenticate to the Package Registry, you need one of the following:
NOTE:
Packages from private and internal projects are hidden if you are not
-authenticated. If you try to search or download a package from a private or internal project without authenticating, you will receive the error `unable to find the package in remote` in the Conan client.
+authenticated. If you try to search or download a package from a private or internal
+project without authenticating, you receive the error `unable to find the package in remote`
+in the Conan client.
### Add your credentials to the GitLab remote
@@ -271,7 +273,7 @@ Prerequisites:
NOTE:
If you try installing the package you created in this tutorial, the install command
-will have no effect because the package already exists.
+has no effect because the package already exists.
Delete `~/.conan/data` to clean up the packages stored in the cache.
## Remove a Conan package
diff --git a/doc/user/packages/container_registry/reduce_container_registry_data_transfer.md b/doc/user/packages/container_registry/reduce_container_registry_data_transfer.md
index 0ce9635e05a..110f3ff908c 100644
--- a/doc/user/packages/container_registry/reduce_container_registry_data_transfer.md
+++ b/doc/user/packages/container_registry/reduce_container_registry_data_transfer.md
@@ -22,7 +22,7 @@ usage.
Use these tools and techniques to determine your image's size:
- [Skopeo](https://github.com/containers/skopeo):
- use Skopeo's `inspect` command to examine layer count and sizes through API calls. You can
+ use the Skopeo `inspect` command to examine layer count and sizes through API calls. You can
therefore inspect this data prior to running `docker pull IMAGE`.
- Docker in CI: examine and record the image size when using GitLab CI prior to pushing an image
@@ -41,7 +41,7 @@ Use these tools and techniques to determine your image's size:
### Use a smaller base image
Consider using a smaller base image, such as [Alpine Linux](https://alpinelinux.org/).
-An Alpine image is around 5MB, which is several times smaller than popular base images such as
+An Alpine image is around 5 MB, which is several times smaller than popular base images such as
[Debian](https://hub.docker.com/_/debian).
If your application is distributed as a self-contained static binary, such as for Go applications,
you can also consider using the Docker [scratch](https://hub.docker.com/_/scratch/)
diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md
index a1a9d2a4915..8dc3c98795b 100644
--- a/doc/user/packages/dependency_proxy/index.md
+++ b/doc/user/packages/dependency_proxy/index.md
@@ -322,7 +322,7 @@ services:
### Issues when authenticating to the Dependency Proxy from CI/CD jobs
-GitLab Runner will automatically authenticate to the Dependency Proxy. However, the underlying Docker engine is still subject to its [authorization resolving process](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/docs/configuration/advanced-configuration.md#precedence-of-docker-authorization-resolving).
+GitLab Runner authenticates automatically to the Dependency Proxy. However, the underlying Docker engine is still subject to its [authorization resolving process](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/docs/configuration/advanced-configuration.md#precedence-of-docker-authorization-resolving).
Misconfigurations in the authentication mechanism may cause `HTTP Basic: Access denied` and `403: Access forbidden` errors.
diff --git a/doc/user/packages/generic_packages/index.md b/doc/user/packages/generic_packages/index.md
index a4df3462059..932de0bcde6 100644
--- a/doc/user/packages/generic_packages/index.md
+++ b/doc/user/packages/generic_packages/index.md
@@ -215,7 +215,7 @@ It also demonstrates how to manage a semantic version for the generic package: s
### Internal Server error on large file uploads to S3
-S3-compatible object storage [limits the size of a single PUT request to 5GB](https://docs.aws.amazon.com/AmazonS3/latest/userguide/upload-objects.html). If the `aws_signature_version` is set to `2` in the [object storage connection settings](../../../administration/object_storage.md), attempting to publish a package file larger than the 5GB limit can result in a `HTTP 500: Internal Server Error` response.
+S3-compatible object storage [limits the size of a single PUT request to 5 GB](https://docs.aws.amazon.com/AmazonS3/latest/userguide/upload-objects.html). If the `aws_signature_version` is set to `2` in the [object storage connection settings](../../../administration/object_storage.md), attempting to publish a package file larger than the 5 GB limit can result in a `HTTP 500: Internal Server Error` response.
If you are receiving `HTTP 500: Internal Server Error` responses when publishing large files to S3, set the `aws_signature_version` to `4`:
diff --git a/doc/user/packages/go_proxy/index.md b/doc/user/packages/go_proxy/index.md
index a147c3656b7..1a089cd82be 100644
--- a/doc/user/packages/go_proxy/index.md
+++ b/doc/user/packages/go_proxy/index.md
@@ -107,8 +107,8 @@ Open your [`~/.netrc`](https://everything.curl.dev/usingcurl/netrc) file
and add the following text. Replace the variables in `< >` with your values.
WARNING:
-If you use an environment variable called `NETRC`, Go will use its value
-as a filename and ignore `~/.netrc`. If you intend to use `~/.netrc` in
+If you use an environment variable called `NETRC`, Go uses its value
+as a filename and ignores `~/.netrc`. If you intend to use `~/.netrc` in
the GitLab CI **do not use `NETRC` as an environment variable name**.
```plaintext
diff --git a/doc/user/packages/package_registry/index.md b/doc/user/packages/package_registry/index.md
index ab5d652b731..096a8273440 100644
--- a/doc/user/packages/package_registry/index.md
+++ b/doc/user/packages/package_registry/index.md
@@ -64,7 +64,7 @@ For most package types, the following credential types are valid:
- If you are publishing a package via CI/CD pipelines, you must use a CI job token.
NOTE:
-If you have not activated the "Packages" feature for your project at **Settings > General > Project features**, you will receive a 403 Forbidden response.
+If you have not activated the "Package registry" feature for your project at **Settings > General > Visibility, project features, permissions**, you receive a 403 Forbidden response.
Accessing package registry via deploy token is not available when external authorization is enabled.
## Use GitLab CI/CD to build packages
@@ -151,7 +151,7 @@ Several known issues exist when you allow anyone to pull from the Package Regist
- Project-level endpoints are supported. Group-level and instance-level endpoints are not supported. Support for group-level endpoints is proposed in [issue 383537](https://gitlab.com/gitlab-org/gitlab/-/issues/383537).
- It does not work with the [Composer](../composer_repository/index.md#install-a-composer-package), because Composer only has a group endpoint.
-- It will work with Conan, but using [`conan search`](../conan_repository/index.md#search-for-conan-packages-in-the-package-registry) does not work.
+- It works with Conan, but using [`conan search`](../conan_repository/index.md#search-for-conan-packages-in-the-package-registry) does not work.
## Accepting contributions
diff --git a/doc/user/packages/pypi_repository/index.md b/doc/user/packages/pypi_repository/index.md
index 3d58ef636df..e5b7f06f6ca 100644
--- a/doc/user/packages/pypi_repository/index.md
+++ b/doc/user/packages/pypi_repository/index.md
@@ -311,7 +311,7 @@ password <your_personal_token>
## Troubleshooting
-To improve performance, the pip command caches files related to a package. Note that pip doesn't remove data by
+To improve performance, the pip command caches files related to a package. Pip doesn't remove data by
itself. The cache grows as new packages are installed. If you encounter issues, clear the cache with
this command:
diff --git a/doc/user/project/import/bitbucket.md b/doc/user/project/import/bitbucket.md
index 29d29c12536..a3a4de844ad 100644
--- a/doc/user/project/import/bitbucket.md
+++ b/doc/user/project/import/bitbucket.md
@@ -46,8 +46,8 @@ the user is not found in the GitLab database, the project creator (most of the t
user that started the import process) is set as the author, but a reference on the issue about the
original Bitbucket author is kept.
-The importer will create any new namespaces (groups) if they don't exist or in
-the case the namespace is taken, the repository will be imported under the user's
+The importer creates any new namespaces (groups) if they don't exist or in
+the case the namespace is taken, the repository is imported under the user's
namespace that started the import process.
## Requirements for user-mapped contributions
@@ -76,7 +76,7 @@ For user contributions to be mapped, each user must complete the following befor
1. Select the projects that you'd like to import or import all projects.
You can filter projects by name and select the namespace
- each project will be imported for.
+ each project is imported for.
![Import projects](img/bitbucket_import_select_project_v12_3.png)
diff --git a/doc/user/project/merge_requests/status_checks.md b/doc/user/project/merge_requests/status_checks.md
index 2894b71e7e6..fc3240f3889 100644
--- a/doc/user/project/merge_requests/status_checks.md
+++ b/doc/user/project/merge_requests/status_checks.md
@@ -146,7 +146,7 @@ The **Remove status check?** modal is then shown.
To complete the deletion of the status check you must select the
**Remove status check** button. This **permanently** deletes
-the status check and it **will not** be recoverable.
+the status check and it **is not** recoverable.
## Status checks widget
diff --git a/lib/api/container_registry_event.rb b/lib/api/container_registry_event.rb
index 9467f09e4b6..a87ccb55193 100644
--- a/lib/api/container_registry_event.rb
+++ b/lib/api/container_registry_event.rb
@@ -32,18 +32,6 @@ module API
]
tags %w[container_registry_event]
end
- params do
- requires :events, type: Array, desc: 'Event notifications' do
- requires :action, type: String, desc: 'The action to perform, `push`, `delete`, `pull`',
- values: %w[push delete pull].freeze
- optional :target, type: Hash, desc: 'The target of the action' do
- optional :tag, type: String, desc: 'The target tag', documentation: { example: 'latest' }
- optional :repository, type: String, desc: 'The target repository', documentation: { example: 'group/p1' }
- optional :digest, type: String, desc: 'Unique identifier for target image manifest',
- documentation: { example: 'imagedigest' }
- end
- end
- end
# This endpoint is used by Docker Registry to push a set of event
# that took place recently.
diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb
index c1a48a46d64..4e4a500718f 100644
--- a/lib/api/entities/release.rb
+++ b/lib/api/entities/release.rb
@@ -28,8 +28,13 @@ module API
end
expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_read_code? }
expose :_links do
- expose :self_url, as: :self, expose_nil: false
+ expose :closed_issues_url, expose_nil: false
+ expose :closed_merge_requests_url, expose_nil: false
expose :edit_url, expose_nil: false
+ expose :merged_merge_requests_url, expose_nil: false
+ expose :opened_issues_url, expose_nil: false
+ expose :opened_merge_requests_url, expose_nil: false
+ expose :self_url, as: :self, expose_nil: false
end
private
diff --git a/lib/gitlab/ci/runner_instructions.rb b/lib/gitlab/ci/runner_instructions.rb
index bcda2fec5ba..1bf015a0aa0 100644
--- a/lib/gitlab/ci/runner_instructions.rb
+++ b/lib/gitlab/ci/runner_instructions.rb
@@ -47,6 +47,9 @@ module Gitlab
kubernetes: {
human_readable_name: "Kubernetes",
installation_instructions_url: "https://docs.gitlab.com/runner/install/kubernetes.html"
+ },
+ aws: {
+ human_readable_name: "AWS"
}
}.freeze
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index eccd500e910..ba02566e232 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -500,15 +500,6 @@ module Gitlab
"Must start with a letter, and cannot end with '-' or '_'"
end
- def saved_reply_name_regex
- @saved_reply_name_regex ||= /\A[a-z]([a-z0-9\-_]*[a-z0-9])?\z/.freeze
- end
-
- def saved_reply_name_regex_message
- "can contain only lowercase letters, digits, '_' and '-'. " \
- "Must start with a letter, and cannot end with '-' or '_'"
- end
-
# One or more `part`s, separated by separator
def sep_by_1(separator, part)
%r(#{part} (#{separator} #{part})*)x
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b010969b306..73615e02f54 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1306,6 +1306,9 @@ msgstr ""
msgid "(expired)"
msgstr ""
+msgid "(external link)"
+msgstr ""
+
msgid "(leave blank if you don't want to change it)"
msgstr ""
@@ -4372,9 +4375,6 @@ msgstr ""
msgid "An error occurred while fetching pending comments"
msgstr ""
-msgid "An error occurred while fetching projects autocomplete."
-msgstr ""
-
msgid "An error occurred while fetching reference"
msgstr ""
@@ -8591,9 +8591,6 @@ msgstr ""
msgid "Choose file…"
msgstr ""
-msgid "Choose the preferred Runner and populate the AWS CFT."
-msgstr ""
-
msgid "Choose the top-level group for your repository imports."
msgstr ""
@@ -11267,9 +11264,6 @@ msgstr ""
msgid "Copy source branch name"
msgstr ""
-msgid "Copy this registration token."
-msgstr ""
-
msgid "Copy to clipboard"
msgstr ""
@@ -27378,6 +27372,9 @@ msgstr ""
msgid "Moves this issue to %{path_to_project}."
msgstr ""
+msgid "Moving issue"
+msgstr ""
+
msgid "MrDeploymentActions|Deploy"
msgstr ""
@@ -36659,6 +36656,9 @@ msgstr ""
msgid "Runners|Runner Registration"
msgstr ""
+msgid "Runners|Runner Registration token"
+msgstr ""
+
msgid "Runners|Runner assigned to project."
msgstr ""
@@ -36743,7 +36743,7 @@ msgstr ""
msgid "Runners|Select projects to assign to this runner"
msgstr ""
-msgid "Runners|Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console."
+msgid "Runners|Select your preferred runner, then choose the capacity for the runner in the AWS CloudFormation console."
msgstr ""
msgid "Runners|Show only inherited"
@@ -45618,18 +45618,12 @@ msgstr ""
msgid "Use .gitlab-ci.yml"
msgstr ""
-msgid "Use GitLab Runner in AWS"
-msgstr ""
-
msgid "Use Secure Files to store files used by your pipelines such as Android keystores, or Apple provisioning profiles and signing certificates."
msgstr ""
msgid "Use a one-time password authenticator on your mobile device or computer to enable two-factor authentication (2FA)."
msgstr ""
-msgid "Use an AWS CloudFormation Template (CFT) to install and configure GitLab Runner in AWS."
-msgstr ""
-
msgid "Use authorized_keys file to authenticate SSH keys"
msgstr ""
diff --git a/package.json b/package.json
index 47efe37020a..1ffb8f5ba0e 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.1.2",
"@gitlab/svgs": "3.18.0",
- "@gitlab/ui": "53.2.0",
+ "@gitlab/ui": "54.1.1",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230120231236",
"@rails/actioncable": "6.1.4-7",
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 72c6e288168..ea68f2266b3 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning
end
it 'moving issue to another project not allowed' do
- expect(page).to have_no_selector('.js-sidebar-move-issue-block')
+ expect(page).to have_no_selector('.js-issuable-move-block')
end
end
@@ -42,10 +42,10 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning
end
it 'moving issue to another project', :js do
- find('.js-move-issue').click
+ click_button _('Move issue')
wait_for_requests
- all('.js-move-issue-dropdown-item')[0].click
- find('.js-move-issue-confirmation-button').click
+ all('.gl-dropdown-item')[0].click
+ click_button _('Move')
expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}")
@@ -56,11 +56,11 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning
it 'searching project dropdown', :js do
new_project_search.add_reporter(user)
- find('.js-move-issue').click
+ click_button _('Move issue')
wait_for_requests
- page.within '.js-sidebar-move-issue-block' do
- fill_in('sidebar-move-issue-dropdown-search', with: new_project_search.name)
+ page.within '.js-issuable-move-block' do
+ fill_in(_('Search project'), with: new_project_search.name)
expect(page).to have_content(new_project_search.name)
expect(page).not_to have_content(new_project.name)
@@ -76,10 +76,10 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning
end
it 'browsing projects in projects select' do
- find('.js-move-issue').click
+ click_button _('Move issue')
wait_for_requests
- page.within '.js-sidebar-move-issue-block' do
+ page.within '.js-issuable-move-block' do
expect(page).to have_content new_project.full_name
end
end
@@ -115,10 +115,10 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning
visit issue_path(service_desk_issue)
- find('.js-move-issue').click
+ click_button _('Move issue')
wait_for_requests
- find('.js-move-issue-dropdown-item', text: project_title).click
- find('.js-move-issue-confirmation-button').click
+ find('.gl-dropdown-item', text: project_title).click
+ click_button _('Move')
end
it 'shows an alert after being moved' do
diff --git a/spec/features/projects/commits/multi_view_diff_spec.rb b/spec/features/projects/commits/multi_view_diff_spec.rb
index b178a1c2171..f0a074e9b7f 100644
--- a/spec/features/projects/commits/multi_view_diff_spec.rb
+++ b/spec/features/projects/commits/multi_view_diff_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.shared_examples "no multiple viewers", feature_category: :source_code_management do |commit_ref|
+RSpec.shared_examples "no multiple viewers" do |commit_ref|
let(:ref) { commit_ref }
it "does not display multiple diff viewers" do
@@ -10,7 +10,7 @@ RSpec.shared_examples "no multiple viewers", feature_category: :source_code_mana
end
end
-RSpec.describe 'Multiple view Diffs', :js do
+RSpec.describe 'Multiple view Diffs', :js, feature_category: :source_code_management do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
index d5a877e29e8..7838e4884d8 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
@@ -21,8 +21,6 @@ describe('Ci variable modal', () => {
let trackingSpy;
const maskableRegex = '^[a-zA-Z0-9_+=/@:.~-]{8,}$';
- const maskableRawRegex = '^\\S{8,}$';
-
const mockVariables = mockVariablesWithScopes(instanceString);
const defaultProvide = {
@@ -32,12 +30,8 @@ describe('Ci variable modal', () => {
awsTipLearnLink: '/learn-link',
containsVariableReferenceLink: '/reference',
environmentScopeLink: '/help/environments',
- glFeatures: {
- ciRemoveCharacterLimitationRawMaskedVar: true,
- },
isProtectedByDefault: false,
maskedEnvironmentVariablesLink: '/variables-link',
- maskableRawRegex,
maskableRegex,
protectedEnvironmentVariablesLink: '/protected-link',
};
@@ -430,54 +424,6 @@ describe('Ci variable modal', () => {
describe('Validations', () => {
const maskError = 'This variable can not be masked.';
- describe('when the variable is raw', () => {
- const [variable] = mockVariables;
- const validRawMaskedVariable = {
- ...variable,
- value: 'd$%^asdsadas',
- masked: false,
- raw: true,
- };
-
- describe('and FF is enabled', () => {
- beforeEach(() => {
- createComponent({
- mountFn: mountExtended,
- props: { selectedVariable: validRawMaskedVariable },
- });
- });
-
- it('should not show an error with symbols', async () => {
- await findMaskedVariableCheckbox().trigger('click');
-
- expect(findModal().text()).not.toContain(maskError);
- });
-
- it('should not show an error when length is less than 8', async () => {
- await findValueField().vm.$emit('input', 'a');
- await findMaskedVariableCheckbox().trigger('click');
-
- expect(findModal().text()).toContain(maskError);
- });
- });
-
- describe('and FF is disabled', () => {
- beforeEach(() => {
- createComponent({
- mountFn: mountExtended,
- props: { selectedVariable: validRawMaskedVariable },
- provide: { glFeatures: { ciRemoveCharacterLimitationRawMaskedVar: false } },
- });
- });
-
- it('should show an error with symbols', async () => {
- await findMaskedVariableCheckbox().trigger('click');
-
- expect(findModal().text()).toContain(maskError);
- });
- });
- });
-
describe('when the mask state is invalid', () => {
beforeEach(async () => {
const [variable] = mockVariables;
diff --git a/spec/frontend/right_sidebar_spec.js b/spec/frontend/right_sidebar_spec.js
index 3b220ba8351..f51d51ee182 100644
--- a/spec/frontend/right_sidebar_spec.js
+++ b/spec/frontend/right_sidebar_spec.js
@@ -69,6 +69,9 @@ describe('RightSidebar', () => {
});
it('should not hide collapsed icons', () => {
+ $toggle.click();
+ assertSidebarState('collapsed');
+
[].forEach.call(document.querySelectorAll('.sidebar-collapsed-icon'), (el) => {
expect(el.querySelector('.fa, svg').classList.contains('hidden')).toBe(false);
});
diff --git a/spec/frontend/sidebar/components/move/move_issue_button_spec.js b/spec/frontend/sidebar/components/move/move_issue_button_spec.js
new file mode 100644
index 00000000000..acd6b23c1f5
--- /dev/null
+++ b/spec/frontend/sidebar/components/move/move_issue_button_spec.js
@@ -0,0 +1,157 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { createAlert } from '~/flash';
+import ProjectSelect from '~/sidebar/components/move/issuable_move_dropdown.vue';
+import MoveIssueButton from '~/sidebar/components/move/move_issue_button.vue';
+import moveIssueMutation from '~/sidebar/queries/move_issue.mutation.graphql';
+
+Vue.use(VueApollo);
+
+jest.mock('~/flash');
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+}));
+
+const projectFullPath = 'flight/FlightJS';
+const projectsAutocompleteEndpoint = '/-/autocomplete/projects?project_id=1';
+const issueIid = '15';
+
+const mockDestinationProject = {
+ full_path: 'gitlab-org/GitLabTest',
+};
+
+const mockWebUrl = `${mockDestinationProject.full_path}/issues/${issueIid}`;
+
+const mockMutationErrorMessage = 'Example error message';
+
+const resolvedMutationWithoutErrorsMock = jest.fn().mockResolvedValue({
+ data: {
+ issueMove: {
+ issue: {
+ id: issueIid,
+ webUrl: mockWebUrl,
+ },
+ errors: [],
+ },
+ },
+});
+
+const resolvedMutationWithErrorsMock = jest.fn().mockResolvedValue({
+ data: {
+ issueMove: {
+ errors: [{ message: mockMutationErrorMessage }],
+ },
+ },
+});
+
+const rejectedMutationMock = jest.fn().mockRejectedValue({});
+
+describe('MoveIssueButton', () => {
+ let wrapper;
+ let fakeApollo;
+
+ const findProjectSelect = () => wrapper.findComponent(ProjectSelect);
+ const emitProjectSelectEvent = () => {
+ findProjectSelect().vm.$emit('move-issuable', mockDestinationProject);
+ };
+ const createComponent = (mutationResolverMock = rejectedMutationMock) => {
+ fakeApollo = createMockApollo([[moveIssueMutation, mutationResolverMock]]);
+
+ wrapper = shallowMount(MoveIssueButton, {
+ provide: {
+ projectFullPath,
+ projectsAutocompleteEndpoint,
+ issueIid,
+ },
+ apolloProvider: fakeApollo,
+ });
+ };
+
+ afterEach(() => {
+ fakeApollo = null;
+ });
+
+ it('renders the project select dropdown', () => {
+ createComponent();
+
+ expect(findProjectSelect().props()).toMatchObject({
+ projectsFetchPath: projectsAutocompleteEndpoint,
+ dropdownButtonTitle: MoveIssueButton.i18n.title,
+ dropdownHeaderTitle: MoveIssueButton.i18n.title,
+ moveInProgress: false,
+ });
+ });
+
+ describe('when the project is selected', () => {
+ it('sets loading state and dropdown button text when issue is moving', async () => {
+ createComponent();
+ expect(findProjectSelect().props()).toMatchObject({
+ dropdownButtonTitle: MoveIssueButton.i18n.title,
+ moveInProgress: false,
+ });
+
+ emitProjectSelectEvent();
+ await nextTick();
+
+ expect(findProjectSelect().props()).toMatchObject({
+ dropdownButtonTitle: MoveIssueButton.i18n.titleInProgress,
+ moveInProgress: true,
+ });
+ });
+
+ it.each`
+ condition | mutation
+ ${'a mutation returns errors'} | ${resolvedMutationWithErrorsMock}
+ ${'a mutation is rejected'} | ${rejectedMutationMock}
+ `('sets loading state to false when $condition', async ({ mutation }) => {
+ createComponent(mutation);
+ emitProjectSelectEvent();
+
+ await nextTick();
+ expect(findProjectSelect().props('moveInProgress')).toBe(true);
+
+ await waitForPromises();
+ expect(findProjectSelect().props('moveInProgress')).toBe(false);
+ });
+
+ it('creates a flash and logs errors when a mutation returns errors', async () => {
+ createComponent(resolvedMutationWithErrorsMock);
+ emitProjectSelectEvent();
+
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: MoveIssueButton.i18n.moveErrorMessage,
+ captureError: true,
+ error: expect.any(Object),
+ });
+ });
+
+ it('calls a mutation for the selected issue', async () => {
+ createComponent(resolvedMutationWithoutErrorsMock);
+ emitProjectSelectEvent();
+
+ await waitForPromises();
+
+ expect(resolvedMutationWithoutErrorsMock).toHaveBeenCalledWith({
+ moveIssueInput: {
+ projectPath: projectFullPath,
+ iid: issueIid,
+ targetProjectPath: mockDestinationProject.full_path,
+ },
+ });
+ });
+
+ it('redirects to the correct page when the mutation succeeds', async () => {
+ createComponent(resolvedMutationWithoutErrorsMock);
+ emitProjectSelectEvent();
+ await waitForPromises();
+
+ expect(visitUrl).toHaveBeenCalledWith(mockWebUrl);
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/move/move_issues_button_spec.js b/spec/frontend/sidebar/components/move/move_issues_button_spec.js
index 999340da27c..c65bad642a0 100644
--- a/spec/frontend/sidebar/components/move/move_issues_button_spec.js
+++ b/spec/frontend/sidebar/components/move/move_issues_button_spec.js
@@ -75,9 +75,15 @@ if (IS_EE) {
getIssuesQueryCompleteResponse.data.project.issues.nodes[0].weight = 5;
}
+const mockIssueResult = {
+ id: mockIssue.iid,
+ webUrl: `${mockDestinationProject.full_path}/issues/${mockIssue.iid}`,
+};
+
const resolvedMutationWithoutErrorsMock = jest.fn().mockResolvedValue({
data: {
issueMove: {
+ issue: mockIssueResult,
errors: [],
},
},
@@ -86,6 +92,7 @@ const resolvedMutationWithoutErrorsMock = jest.fn().mockResolvedValue({
const resolvedMutationWithErrorsMock = jest.fn().mockResolvedValue({
data: {
issueMove: {
+ issue: mockIssueResult,
errors: [{ message: mockMutationErrorMessage }],
},
},
diff --git a/spec/frontend/sidebar/lib/sidebar_move_issue_spec.js b/spec/frontend/sidebar/lib/sidebar_move_issue_spec.js
deleted file mode 100644
index 6e365df329b..00000000000
--- a/spec/frontend/sidebar/lib/sidebar_move_issue_spec.js
+++ /dev/null
@@ -1,162 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import $ from 'jquery';
-import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
-import SidebarService from '~/sidebar/services/sidebar_service';
-import SidebarMediator from '~/sidebar/sidebar_mediator';
-import SidebarStore from '~/sidebar/stores/sidebar_store';
-import { GitLabDropdown } from '~/deprecated_jquery_dropdown/gl_dropdown';
-import Mock from '../mock_data';
-
-jest.mock('~/flash');
-
-describe('SidebarMoveIssue', () => {
- let mock;
- const test = {};
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- const mockData = Mock.responseMap.GET['/autocomplete/projects?project_id=15'];
- mock.onGet('/autocomplete/projects?project_id=15').reply(200, mockData);
- test.mediator = new SidebarMediator(Mock.mediator);
- test.$content = $(`
- <div class="dropdown">
- <div class="js-toggle"></div>
- <div class="dropdown-menu">
- <div class="dropdown-content"></div>
- </div>
- <div class="js-confirm-button"></div>
- </div>
- `);
- test.$toggleButton = test.$content.find('.js-toggle');
- test.$confirmButton = test.$content.find('.js-confirm-button');
-
- test.sidebarMoveIssue = new SidebarMoveIssue(
- test.mediator,
- test.$toggleButton,
- test.$confirmButton,
- );
- test.sidebarMoveIssue.init();
- });
-
- afterEach(() => {
- SidebarService.singleton = null;
- SidebarStore.singleton = null;
- SidebarMediator.singleton = null;
-
- test.sidebarMoveIssue.destroy();
- mock.restore();
- });
-
- describe('init', () => {
- it('should initialize the dropdown and listeners', () => {
- jest.spyOn(test.sidebarMoveIssue, 'initDropdown').mockImplementation(() => {});
- jest.spyOn(test.sidebarMoveIssue, 'addEventListeners').mockImplementation(() => {});
-
- test.sidebarMoveIssue.init();
-
- expect(test.sidebarMoveIssue.initDropdown).toHaveBeenCalled();
- expect(test.sidebarMoveIssue.addEventListeners).toHaveBeenCalled();
- });
- });
-
- describe('destroy', () => {
- it('should remove the listeners', () => {
- jest.spyOn(test.sidebarMoveIssue, 'removeEventListeners').mockImplementation(() => {});
-
- test.sidebarMoveIssue.destroy();
-
- expect(test.sidebarMoveIssue.removeEventListeners).toHaveBeenCalled();
- });
- });
-
- describe('initDropdown', () => {
- it('should initialize the deprecatedJQueryDropdown', () => {
- test.sidebarMoveIssue.initDropdown();
-
- expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeInstanceOf(
- GitLabDropdown,
- );
- });
-
- it('escapes html from project name', async () => {
- test.$toggleButton.dropdown('toggle');
-
- await waitForPromises();
-
- expect(test.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual(
- '&lt;img src=x onerror=alert(document.domain)&gt; foo / bar',
- );
- });
- });
-
- describe('onConfirmClicked', () => {
- it('should move the issue with valid project ID', () => {
- jest.spyOn(test.mediator, 'moveIssue').mockReturnValue(Promise.resolve());
- test.mediator.setMoveToProjectId(7);
-
- test.sidebarMoveIssue.onConfirmClicked();
-
- expect(test.mediator.moveIssue).toHaveBeenCalled();
- expect(test.$confirmButton.prop('disabled')).toBe(true);
- expect(test.$confirmButton.hasClass('is-loading')).toBe(true);
- });
-
- it('should remove loading state from confirm button on failure', async () => {
- jest.spyOn(test.mediator, 'moveIssue').mockReturnValue(Promise.reject());
- test.mediator.setMoveToProjectId(7);
-
- test.sidebarMoveIssue.onConfirmClicked();
-
- expect(test.mediator.moveIssue).toHaveBeenCalled();
-
- // Wait for the move issue request to fail
- await waitForPromises();
-
- expect(createAlert).toHaveBeenCalled();
- expect(test.$confirmButton.prop('disabled')).toBe(false);
- expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
- });
-
- it('should not move the issue with id=0', () => {
- jest.spyOn(test.mediator, 'moveIssue').mockImplementation(() => {});
- test.mediator.setMoveToProjectId(0);
-
- test.sidebarMoveIssue.onConfirmClicked();
-
- expect(test.mediator.moveIssue).not.toHaveBeenCalled();
- });
- });
-
- it('should set moveToProjectId on dropdown item "No project" click', async () => {
- jest.spyOn(test.mediator, 'setMoveToProjectId').mockImplementation(() => {});
-
- // Open the dropdown
- test.$toggleButton.dropdown('toggle');
-
- // Wait for the autocomplete request to finish
- await waitForPromises();
-
- test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
-
- expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
- expect(test.$confirmButton.prop('disabled')).toBe(true);
- });
-
- it('should set moveToProjectId on dropdown item click', async () => {
- jest.spyOn(test.mediator, 'setMoveToProjectId').mockImplementation(() => {});
-
- // Open the dropdown
- test.$toggleButton.dropdown('toggle');
-
- // Wait for the autocomplete request to finish
- await waitForPromises();
-
- test.$content.find('.js-move-issue-dropdown-item').eq(1).trigger('click');
-
- expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(20);
- expect(test.$confirmButton.attr('disabled')).toBe(undefined);
- });
-});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
index d34fc0c1e61..1e4e089e7c1 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -79,13 +79,10 @@ const createTestService = () => ({
Vue.use(VueApollo);
+let service;
let wrapper;
let readyToMergeResponseSpy;
-const findMergeButton = () => wrapper.find('[data-testid="merge-button"]');
-const findPipelineFailedConfirmModal = () =>
- wrapper.findComponent(MergeFailedPipelineConfirmationDialog);
-
const createReadyToMergeResponse = (customMr) => {
return produce(readyToMergeResponse, (draft) => {
Object.assign(draft.data.project.mergeRequest, customMr);
@@ -96,7 +93,7 @@ const createComponent = (customConfig = {}, createState = true) => {
wrapper = shallowMount(ReadyToMerge, {
propsData: {
mr: createTestMr(customConfig),
- service: createTestService(),
+ service,
},
data() {
if (createState) {
@@ -119,6 +116,13 @@ const createComponent = (customConfig = {}, createState = true) => {
});
};
+const findMergeButton = () => wrapper.find('[data-testid="merge-button"]');
+const findMergeImmediatelyDropdown = () =>
+ wrapper.find('[data-testid="merge-immediately-dropdown"');
+const findSourceBranchDeletedText = () =>
+ wrapper.find('[data-testid="source-branch-deleted-text"]');
+const findPipelineFailedConfirmModal = () =>
+ wrapper.findComponent(MergeFailedPipelineConfirmationDialog);
const findCheckboxElement = () => wrapper.findComponent(SquashBeforeMerge);
const findCommitEditElements = () => wrapper.findAllComponents(CommitEdit);
const findCommitDropdownElement = () => wrapper.findComponent(CommitMessageDropdown);
@@ -129,33 +133,20 @@ const findCommitEditWithInputId = (inputId) =>
const findMergeCommitMessage = () => findCommitEditWithInputId('merge-message-edit').props('value');
const findSquashCommitMessage = () =>
findCommitEditWithInputId('squash-message-edit').props('value');
+const findDeleteSourceBranchCheckbox = () =>
+ wrapper.find('[data-testid="delete-source-branch-checkbox"]');
const triggerApprovalUpdated = () => eventHub.$emit('ApprovalUpdated');
+const triggerEditCommitInput = () =>
+ wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
describe('ReadyToMerge', () => {
beforeEach(() => {
+ service = createTestService();
readyToMergeResponseSpy = jest.fn().mockResolvedValueOnce(readyToMergeResponse);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
- describe('isAutoMergeAvailable', () => {
- it('should return true when at least one merge strategy is available', () => {
- createComponent({});
-
- expect(wrapper.vm.isAutoMergeAvailable).toBe(true);
- });
-
- it('should return false when no merge strategies are available', () => {
- createComponent({ mr: { availableAutoMergeStrategies: [] } });
-
- expect(wrapper.vm.isAutoMergeAvailable).toBe(false);
- });
- });
-
describe('status', () => {
it('defaults to success', () => {
createComponent({ mr: { pipeline: true, availableAutoMergeStrategies: [] } });
@@ -190,16 +181,6 @@ describe('ReadyToMerge', () => {
});
});
- describe('Merge Button Variant', () => {
- it('defaults to confirm class', () => {
- createComponent({
- mr: { availableAutoMergeStrategies: [], mergeable: true },
- });
-
- expect(findMergeButton().attributes('variant')).toBe('confirm');
- });
- });
-
describe('status icon', () => {
it('defaults to tick icon', () => {
createComponent({ mr: { mergeable: true } });
@@ -219,334 +200,313 @@ describe('ReadyToMerge', () => {
expect(wrapper.vm.iconClass).toEqual('success');
});
});
+ });
- describe('mergeButtonText', () => {
- it('should return "Merge" when no auto merge strategies are available', () => {
- createComponent({ mr: { availableAutoMergeStrategies: [] } });
+ describe('merge button text', () => {
+ it('should return "Merge" when no auto merge strategies are available', () => {
+ createComponent({ mr: { availableAutoMergeStrategies: [] } });
+
+ expect(findMergeButton().text()).toBe('Merge');
+ });
- expect(wrapper.vm.mergeButtonText).toEqual('Merge');
+ it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => {
+ createComponent({
+ mr: { preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY },
});
- it('should return "Merge in progress"', async () => {
- createComponent();
+ expect(findMergeButton().text()).toBe('Merge when pipeline succeeds');
+ });
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ isMergingImmediately: true });
+ it('should return Merge when pipeline succeeds', () => {
+ createComponent({ mr: { preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY } });
- await nextTick();
+ expect(findMergeButton().text()).toBe('Merge when pipeline succeeds');
+ });
+ });
- expect(wrapper.vm.mergeButtonText).toEqual('Merge in progress');
+ describe('merge immediately dropdown', () => {
+ it('dropdown should be hidden if no pipeline is active', () => {
+ createComponent({
+ mr: { isPipelineActive: false, onlyAllowMergeIfPipelineSucceeds: false },
});
- it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => {
- createComponent({
- mr: { isMergingImmediately: false, preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY },
- });
+ expect(findMergeImmediatelyDropdown().exists()).toBe(false);
+ });
- expect(wrapper.vm.mergeButtonText).toEqual('Merge when pipeline succeeds');
- });
+ it('dropdown should be hidden if "Pipelines must succeed" is enabled', () => {
+ createComponent({ mr: { isPipelineActive: true, onlyAllowMergeIfPipelineSucceeds: true } });
+
+ expect(findMergeImmediatelyDropdown().exists()).toBe(false);
});
+ });
- describe('autoMergeText', () => {
- it('should return Merge when pipeline succeeds', () => {
- createComponent({ mr: { preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY } });
+ describe('merge button disabled state', () => {
+ it('should not be disabled initally', () => {
+ createComponent();
- expect(wrapper.vm.autoMergeText).toEqual('Merge when pipeline succeeds');
- });
+ expect(findMergeButton().props('disabled')).toBe(false);
});
- describe('shouldShowMergeImmediatelyDropdown', () => {
- it('should return false if no pipeline is active', () => {
- createComponent({
- mr: { isPipelineActive: false, onlyAllowMergeIfPipelineSucceeds: false },
- });
+ it('should be disabled when there is no commit message', () => {
+ createComponent({ mr: { commitMessage: '' } });
- expect(wrapper.vm.shouldShowMergeImmediatelyDropdown).toBe(false);
- });
+ expect(findMergeButton().props('disabled')).toBe(true);
+ });
- it('should return false if "Pipelines must succeed" is enabled for the current project', () => {
- createComponent({ mr: { isPipelineActive: true, onlyAllowMergeIfPipelineSucceeds: true } });
+ it('should be disabled if merge is not allowed', () => {
+ createComponent({ mr: { preventMerge: true } });
- expect(wrapper.vm.shouldShowMergeImmediatelyDropdown).toBe(false);
- });
+ expect(findMergeButton().props('disabled')).toBe(true);
});
- describe('isMergeButtonDisabled', () => {
- it('should return false with initial data', () => {
- createComponent({ mr: { isMergeAllowed: true, mergeable: false } });
+ it('should be disabled when making request', async () => {
+ createComponent({ mr: { isMergeAllowed: true } }, true);
- expect(wrapper.vm.isMergeButtonDisabled).toBe(false);
- });
+ findMergeButton().vm.$emit('click');
- it('should return true when there is no commit message', () => {
- createComponent({ mr: { isMergeAllowed: true, commitMessage: '' } });
+ await nextTick();
- expect(wrapper.vm.isMergeButtonDisabled).toBe(true);
- });
+ expect(findMergeButton().props('disabled')).toBe(true);
+ });
+ });
- it('should return true if merge is not allowed', () => {
+ describe('sourceBranchDeletedText', () => {
+ const should = 'Source branch will be deleted.';
+ const shouldNot = 'Source branch will not be deleted.';
+ const did = 'Deleted the source branch.';
+ const didNot = 'Did not delete the source branch.';
+ const scenarios = [
+ "the MR hasn't merged yet, and the backend-provided value expects to delete the branch",
+ "the MR hasn't merged yet, and the backend-provided value expects to leave the branch",
+ "the MR hasn't merged yet, and the backend-provided value is a non-boolean falsey value",
+ "the MR hasn't merged yet, and the backend-provided value is a non-boolean truthy value",
+ 'the MR has been merged, and the backend reports that the branch has been removed',
+ 'the MR has been merged, and the backend reports that the branch has not been removed',
+ 'the MR has been merged, and the backend reports a non-boolean falsey value',
+ 'the MR has been merged, and the backend reports a non-boolean truthy value',
+ ];
+
+ it.each`
+ describe | premerge | mrShould | mrRemoved | output
+ ${scenarios[0]} | ${true} | ${true} | ${null} | ${should}
+ ${scenarios[1]} | ${true} | ${false} | ${null} | ${shouldNot}
+ ${scenarios[2]} | ${true} | ${null} | ${null} | ${shouldNot}
+ ${scenarios[3]} | ${true} | ${'yeah'} | ${null} | ${should}
+ ${scenarios[4]} | ${false} | ${null} | ${true} | ${did}
+ ${scenarios[5]} | ${false} | ${null} | ${false} | ${didNot}
+ ${scenarios[6]} | ${false} | ${null} | ${null} | ${didNot}
+ ${scenarios[7]} | ${false} | ${null} | ${'yep'} | ${did}
+ `(
+ 'in the case that $describe, returns "$output"',
+ ({ premerge, mrShould, mrRemoved, output }) => {
createComponent({
mr: {
- isMergeAllowed: false,
- availableAutoMergeStrategies: [],
- onlyAllowMergeIfPipelineSucceeds: true,
- mergeable: false,
+ state: !premerge ? 'merged' : 'literally-anything-else',
+ shouldRemoveSourceBranch: mrShould,
+ sourceBranchRemoved: mrRemoved,
+ autoMergeEnabled: true,
},
});
- expect(wrapper.vm.isMergeButtonDisabled).toBe(true);
- });
-
- it('should return true when the vm instance is making request', async () => {
- createComponent({ mr: { isMergeAllowed: true } });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ isMakingRequest: true });
-
- await nextTick();
-
- expect(wrapper.vm.isMergeButtonDisabled).toBe(true);
- });
- });
-
- describe('sourceBranchDeletedText', () => {
- const should = 'Source branch will be deleted.';
- const shouldNot = 'Source branch will not be deleted.';
- const did = 'Deleted the source branch.';
- const didNot = 'Did not delete the source branch.';
- const scenarios = [
- "the MR hasn't merged yet, and the backend-provided value expects to delete the branch",
- "the MR hasn't merged yet, and the backend-provided value expects to leave the branch",
- "the MR hasn't merged yet, and the backend-provided value is a non-boolean falsey value",
- "the MR hasn't merged yet, and the backend-provided value is a non-boolean truthy value",
- 'the MR has been merged, and the backend reports that the branch has been removed',
- 'the MR has been merged, and the backend reports that the branch has not been removed',
- 'the MR has been merged, and the backend reports a non-boolean falsey value',
- 'the MR has been merged, and the backend reports a non-boolean truthy value',
- ];
-
- it.each`
- describe | premerge | mrShould | mrRemoved | output
- ${scenarios[0]} | ${true} | ${true} | ${null} | ${should}
- ${scenarios[1]} | ${true} | ${false} | ${null} | ${shouldNot}
- ${scenarios[2]} | ${true} | ${null} | ${null} | ${shouldNot}
- ${scenarios[3]} | ${true} | ${'yeah'} | ${null} | ${should}
- ${scenarios[4]} | ${false} | ${null} | ${true} | ${did}
- ${scenarios[5]} | ${false} | ${null} | ${false} | ${didNot}
- ${scenarios[6]} | ${false} | ${null} | ${null} | ${didNot}
- ${scenarios[7]} | ${false} | ${null} | ${'yep'} | ${did}
- `(
- 'in the case that $describe, returns "$output"',
- ({ premerge, mrShould, mrRemoved, output }) => {
- createComponent({
- mr: {
- state: !premerge ? 'merged' : 'literally-anything-else',
- shouldRemoveSourceBranch: mrShould,
- sourceBranchRemoved: mrRemoved,
- },
- });
-
- expect(wrapper.vm.sourceBranchDeletedText).toBe(output);
- },
- );
- });
+ expect(findSourceBranchDeletedText().text()).toBe(output);
+ },
+ );
});
- describe('methods', () => {
- describe('handleMergeButtonClick', () => {
- const response = (status) => ({
- data: {
- status,
- },
+ describe('Merge Button Variant', () => {
+ it('defaults to confirm class', () => {
+ createComponent({
+ mr: { availableAutoMergeStrategies: [], mergeable: true },
});
- beforeEach(() => {
- readyToMergeResponseSpy = jest
- .fn()
- .mockResolvedValueOnce(createReadyToMergeResponse({ squash: true, squashOnMerge: true }))
- .mockResolvedValue(
- createReadyToMergeResponse({
- squash: true,
- squashOnMerge: true,
- defaultMergeCommitMessage: '',
- defaultSquashCommitMessage: '',
- }),
- );
- });
+ expect(findMergeButton().attributes('variant')).toBe('confirm');
+ });
+ });
- it('should handle merge when pipeline succeeds', async () => {
- createComponent();
+ describe('Merge button click', () => {
+ const response = (status) => ({
+ data: {
+ status,
+ },
+ });
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest
- .spyOn(wrapper.vm.service, 'merge')
- .mockResolvedValue(response('merge_when_pipeline_succeeds'));
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ removeSourceBranch: false });
+ beforeEach(() => {
+ readyToMergeResponseSpy = jest
+ .fn()
+ .mockResolvedValueOnce(createReadyToMergeResponse({ squash: true, squashOnMerge: true }))
+ .mockResolvedValue(
+ createReadyToMergeResponse({
+ squash: true,
+ squashOnMerge: true,
+ defaultMergeCommitMessage: '',
+ defaultSquashCommitMessage: '',
+ }),
+ );
+ });
- wrapper.vm.handleMergeButtonClick(true);
+ it('should handle merge when pipeline succeeds', async () => {
+ createComponent({ mr: { shouldRemoveSourceBranch: false } }, true);
- await waitForPromises();
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ jest.spyOn(service, 'merge').mockResolvedValue(response('merge_when_pipeline_succeeds'));
- expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
- expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', {
- transition: 'start-auto-merge',
- });
+ findMergeButton().vm.$emit('click');
- const params = wrapper.vm.service.merge.mock.calls[0][0];
+ await waitForPromises();
- expect(params).toEqual(
- expect.objectContaining({
- sha: wrapper.vm.mr.sha,
- commit_message: wrapper.vm.mr.commitMessage,
- should_remove_source_branch: false,
- auto_merge_strategy: 'merge_when_pipeline_succeeds',
- }),
- );
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', {
+ transition: 'start-auto-merge',
});
- it('should handle merge failed', async () => {
- createComponent();
+ const params = service.merge.mock.calls[0][0];
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('failed'));
- wrapper.vm.handleMergeButtonClick(false, true);
+ expect(params).toEqual(
+ expect.objectContaining({
+ sha: '12345678',
+ commit_message: commitMessage,
+ should_remove_source_branch: false,
+ auto_merge_strategy: 'merge_when_pipeline_succeeds',
+ }),
+ );
+ });
- await waitForPromises();
+ it('should handle merge failed', async () => {
+ createComponent({ mr: { availableAutoMergeStrategies: [] } });
- expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ jest.spyOn(service, 'merge').mockResolvedValue(response('failed'));
- const params = wrapper.vm.service.merge.mock.calls[0][0];
+ findMergeButton().vm.$emit('click');
- expect(params.should_remove_source_branch).toBe(true);
- expect(params.auto_merge_strategy).toBeUndefined();
- });
+ await waitForPromises();
- it('should handle merge action accepted case', async () => {
- createComponent();
+ expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('success'));
- jest.spyOn(wrapper.vm.mr, 'transitionStateMachine');
- wrapper.vm.handleMergeButtonClick();
+ const params = service.merge.mock.calls[0][0];
- expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', {
- transition: 'start-merge',
- });
+ expect(params.should_remove_source_branch).toBe(true);
+ expect(params.auto_merge_strategy).toBeUndefined();
+ });
- await waitForPromises();
+ it('should handle merge action accepted case', async () => {
+ createComponent({ mr: { availableAutoMergeStrategies: [] } });
- expect(wrapper.vm.mr.transitionStateMachine).toHaveBeenCalledWith({
- transition: 'start-merge',
- });
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ jest.spyOn(service, 'merge').mockResolvedValue(response('success'));
+ jest.spyOn(wrapper.vm.mr, 'transitionStateMachine');
- const params = wrapper.vm.service.merge.mock.calls[0][0];
+ findMergeButton().vm.$emit('click');
- expect(params.should_remove_source_branch).toBe(true);
- expect(params.auto_merge_strategy).toBeUndefined();
+ expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', {
+ transition: 'start-merge',
});
- it('hides edit commit message', async () => {
- createComponent({}, true, true);
+ await waitForPromises();
- await waitForPromises();
+ expect(wrapper.vm.mr.transitionStateMachine).toHaveBeenCalledWith({
+ transition: 'start-merge',
+ });
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('success'));
+ const params = service.merge.mock.calls[0][0];
- await wrapper
- .findComponent('[data-testid="widget_edit_commit_message"]')
- .vm.$emit('input', true);
+ expect(params.should_remove_source_branch).toBe(true);
+ expect(params.auto_merge_strategy).toBeUndefined();
+ });
- expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(true);
+ it('hides edit commit message', async () => {
+ createComponent();
- wrapper.vm.handleMergeButtonClick();
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ jest.spyOn(service, 'merge').mockResolvedValue(response('success'));
- await waitForPromises();
+ await triggerEditCommitInput();
- expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(false);
- });
+ expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(true);
+
+ findMergeButton().vm.$emit('click');
+
+ await waitForPromises();
+
+ expect(wrapper.findComponent('[data-testid="edit_commit_message"]').exists()).toBe(false);
});
+ });
- describe('initiateRemoveSourceBranchPolling', () => {
- it('should emit event and call simplePoll', () => {
- createComponent();
+ describe('initiateRemoveSourceBranchPolling', () => {
+ it('should emit event and call simplePoll', () => {
+ createComponent();
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- wrapper.vm.initiateRemoveSourceBranchPolling();
+ wrapper.vm.initiateRemoveSourceBranchPolling();
- expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]);
- expect(simplePoll).toHaveBeenCalled();
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]);
+ expect(simplePoll).toHaveBeenCalled();
});
+ });
- describe('handleRemoveBranchPolling', () => {
- const response = (state) => ({
- data: {
- source_branch_exists: state,
- },
- });
+ describe('handleRemoveBranchPolling', () => {
+ const response = (state) => ({
+ data: {
+ source_branch_exists: state,
+ },
+ });
- it('should call start and stop polling when MR merged', async () => {
- createComponent();
+ it('should call start and stop polling when MR merged', async () => {
+ createComponent();
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(false));
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ jest.spyOn(service, 'poll').mockResolvedValue(response(false));
- let cpc = false; // continuePollingCalled
- let spc = false; // stopPollingCalled
+ let cpc = false; // continuePollingCalled
+ let spc = false; // stopPollingCalled
- wrapper.vm.handleRemoveBranchPolling(
- () => {
- cpc = true;
- },
- () => {
- spc = true;
- },
- );
+ wrapper.vm.handleRemoveBranchPolling(
+ () => {
+ cpc = true;
+ },
+ () => {
+ spc = true;
+ },
+ );
- await waitForPromises();
+ await waitForPromises();
- expect(wrapper.vm.service.poll).toHaveBeenCalled();
+ expect(service.poll).toHaveBeenCalled();
- const args = eventHub.$emit.mock.calls[0];
+ const args = eventHub.$emit.mock.calls[0];
- expect(args[0]).toEqual('MRWidgetUpdateRequested');
- expect(args[1]).toBeDefined();
- args[1]();
+ expect(args[0]).toEqual('MRWidgetUpdateRequested');
+ expect(args[1]).toBeDefined();
+ args[1]();
- expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]);
+ expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]);
- expect(cpc).toBe(false);
- expect(spc).toBe(true);
- });
+ expect(cpc).toBe(false);
+ expect(spc).toBe(true);
+ });
- it('should continue polling until MR is merged', async () => {
- createComponent();
+ it('should continue polling until MR is merged', async () => {
+ createComponent();
- jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(true));
+ jest.spyOn(service, 'poll').mockResolvedValue(response(true));
- let cpc = false; // continuePollingCalled
- let spc = false; // stopPollingCalled
+ let cpc = false; // continuePollingCalled
+ let spc = false; // stopPollingCalled
- wrapper.vm.handleRemoveBranchPolling(
- () => {
- cpc = true;
- },
- () => {
- spc = true;
- },
- );
+ wrapper.vm.handleRemoveBranchPolling(
+ () => {
+ cpc = true;
+ },
+ () => {
+ spc = true;
+ },
+ );
- await waitForPromises();
+ await waitForPromises();
- expect(cpc).toBe(true);
- expect(spc).toBe(false);
- });
+ expect(cpc).toBe(true);
+ expect(spc).toBe(false);
});
});
@@ -563,7 +523,7 @@ describe('ReadyToMerge', () => {
},
});
- expect(wrapper.find('#remove-source-branch-input').exists()).toBe(false);
+ expect(findDeleteSourceBranchCheckbox().exists()).toBe(false);
});
});
@@ -575,7 +535,7 @@ describe('ReadyToMerge', () => {
});
it('isRemoveSourceBranchButtonDisabled should be false', () => {
- expect(wrapper.find('#remove-source-branch-input').props('disabled')).toBe(undefined);
+ expect(findDeleteSourceBranchCheckbox().props('disabled')).toBe(undefined);
});
});
});
@@ -646,7 +606,7 @@ describe('ReadyToMerge', () => {
},
});
- expect(findCommitEditElements().length).toBe(0);
+ expect(findCommitEditElements()).toHaveLength(0);
});
it('should not be rendered if squash before merge is disabled', () => {
@@ -659,7 +619,7 @@ describe('ReadyToMerge', () => {
},
});
- expect(findCommitEditElements().length).toBe(0);
+ expect(findCommitEditElements()).toHaveLength(0);
});
it('should not be rendered if there is only one commit', () => {
@@ -672,7 +632,7 @@ describe('ReadyToMerge', () => {
},
});
- expect(findCommitEditElements().length).toBe(0);
+ expect(findCommitEditElements()).toHaveLength(0);
});
it('should have one edit component if squash is enabled and there is more than 1 commit', async () => {
@@ -686,9 +646,9 @@ describe('ReadyToMerge', () => {
},
});
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
+ await triggerEditCommitInput();
- expect(findCommitEditElements().length).toBe(1);
+ expect(findCommitEditElements()).toHaveLength(1);
expect(findFirstCommitEditLabel()).toBe('Squash commit message');
});
});
@@ -702,16 +662,15 @@ describe('ReadyToMerge', () => {
},
});
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
+ await triggerEditCommitInput();
- expect(findCommitEditElements().length).toBe(2);
+ expect(findCommitEditElements()).toHaveLength(2);
});
- it('should have two edit components when squash is enabled and there is more than 1 commit and mergeRequestWidgetGraphql is enabled', async () => {
+ it('should have two edit components when squash is enabled', async () => {
createComponent(
{
mr: {
- commitsCount: 2,
squashIsSelected: true,
enableSquashBeforeMerge: true,
},
@@ -719,37 +678,9 @@ describe('ReadyToMerge', () => {
true,
);
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- loading: false,
- state: {
- ...createTestMr({}),
- userPermissions: {},
- squash: true,
- mergeable: true,
- commitCount: 2,
- commitsWithoutMergeCommits: {},
- },
- });
- await nextTick();
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
-
- expect(findCommitEditElements().length).toBe(2);
- });
-
- it('should have one edit components when squash is enabled and there is 1 commit only', async () => {
- createComponent({
- mr: {
- commitsCount: 1,
- squash: true,
- enableSquashBeforeMerge: true,
- },
- });
+ await triggerEditCommitInput();
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
-
- expect(findCommitEditElements().length).toBe(1);
+ expect(findCommitEditElements()).toHaveLength(2);
});
it('should have correct edit squash commit label', async () => {
@@ -761,7 +692,7 @@ describe('ReadyToMerge', () => {
},
});
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
+ await triggerEditCommitInput();
expect(findFirstCommitEditLabel()).toBe('Squash commit message');
});
@@ -779,7 +710,7 @@ describe('ReadyToMerge', () => {
mr: { enableSquashBeforeMerge: true, squashIsSelected: true, commitsCount: 2 },
});
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
+ await triggerEditCommitInput();
expect(findCommitDropdownElement().exists()).toBe(true);
});
@@ -788,7 +719,7 @@ describe('ReadyToMerge', () => {
it('renders a tip including a link to docs on templates', async () => {
createComponent();
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
+ await triggerEditCommitInput();
expect(findTipLink().exists()).toBe(true);
});
@@ -891,7 +822,8 @@ describe('ReadyToMerge', () => {
createDefaultGqlComponent();
await waitForPromises();
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
+
+ await triggerEditCommitInput();
expect(finderFn()).toBe(initialValue);
});
@@ -899,7 +831,7 @@ describe('ReadyToMerge', () => {
it('should have updated value after graphql refetch', async () => {
createDefaultGqlComponent();
await waitForPromises();
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
+ await triggerEditCommitInput();
triggerApprovalUpdated();
await waitForPromises();
@@ -910,7 +842,7 @@ describe('ReadyToMerge', () => {
it('should not update if user has touched', async () => {
createDefaultGqlComponent();
await waitForPromises();
- await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true);
+ await triggerEditCommitInput();
const input = wrapper.find(inputId);
input.element.value = USER_COMMIT_MESSAGE;
diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js
deleted file mode 100644
index c8ca75787f1..00000000000
--- a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal_spec.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { GlModal } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import { s__ } from '~/locale';
-import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue';
-import RunnerAwsInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue';
-
-jest.mock('~/lib/utils/url_utility', () => ({
- ...jest.requireActual('~/lib/utils/url_utility'),
- visitUrl: jest.fn(),
-}));
-
-const mockModalId = 'runner-aws-deployments-modal';
-
-describe('RunnerAwsDeploymentsModal', () => {
- let wrapper;
-
- const findModal = () => wrapper.findComponent(GlModal);
- const findRunnerAwsInstructions = () => wrapper.findComponent(RunnerAwsInstructions);
-
- const createComponent = (options) => {
- wrapper = shallowMount(RunnerAwsDeploymentsModal, {
- propsData: {
- modalId: mockModalId,
- },
- ...options,
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders modal', () => {
- expect(findModal().props()).toMatchObject({
- size: 'sm',
- modalId: mockModalId,
- title: s__('Runners|Deploy GitLab Runner in AWS'),
- });
- expect(findModal().attributes()).toMatchObject({
- 'hide-footer': '',
- });
- });
-
- it('renders modal contents', () => {
- expect(findRunnerAwsInstructions().exists()).toBe(true);
- });
-
- it('when contents trigger closing, modal closes', () => {
- const mockClose = jest.fn();
-
- createComponent({
- stubs: {
- GlModal: {
- template: '<div><slot/></div>',
- methods: {
- close: mockClose,
- },
- },
- },
- });
-
- expect(mockClose).toHaveBeenCalledTimes(0);
-
- findRunnerAwsInstructions().vm.$emit('close');
-
- expect(mockClose).toHaveBeenCalledTimes(1);
- });
-});
diff --git a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_spec.js b/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_spec.js
deleted file mode 100644
index 639668761ea..00000000000
--- a/spec/frontend/vue_shared/components/runner_aws_deployments/runner_aws_deployments_spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import RunnerAwsDeployments from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments.vue';
-import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue';
-
-describe('RunnerAwsDeployments component', () => {
- let wrapper;
-
- const findModalButton = () => wrapper.findByTestId('show-modal-button');
- const findModal = () => wrapper.findComponent(RunnerAwsDeploymentsModal);
-
- const createComponent = () => {
- wrapper = extendedWrapper(shallowMount(RunnerAwsDeployments));
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('should show the "Deploy GitLab Runner in AWS" button', () => {
- expect(findModalButton().exists()).toBe(true);
- expect(findModalButton().text()).toBe('Deploy GitLab Runner in AWS');
- });
-
- it('should not render the modal once mounted', () => {
- expect(findModal().exists()).toBe(false);
- });
-
- it('should render the modal once clicked', async () => {
- findModalButton().vm.$emit('click');
-
- await nextTick();
-
- expect(findModal().exists()).toBe(true);
- });
-});
diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js
index 4d566dbec0c..6d8f895a185 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_aws_instructions_spec.js
@@ -16,14 +16,18 @@ import {
AWS_TEMPLATES_BASE_URL,
AWS_EASY_BUTTONS,
} from '~/vue_shared/components/runner_instructions/constants';
-import RunnerAwsInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue';
import { __ } from '~/locale';
+import RunnerAwsInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
visitUrl: jest.fn(),
}));
+const mockRegistrationToken = 'MY_TOKEN';
+
describe('RunnerAwsInstructions', () => {
let wrapper;
@@ -31,6 +35,7 @@ describe('RunnerAwsInstructions', () => {
const findEasyButtons = () => wrapper.findAllComponents(GlFormRadio);
const findEasyButtonAt = (i) => findEasyButtons().at(i);
const findLink = () => wrapper.findComponent(GlLink);
+ const findModalCopyButton = () => wrapper.findComponent(ModalCopyButton);
const findOkButton = () =>
wrapper
.findAllComponents(GlButton)
@@ -38,8 +43,12 @@ describe('RunnerAwsInstructions', () => {
.at(0);
const findCloseButton = () => wrapper.findByText(__('Close'));
- const createComponent = () => {
+ const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(RunnerAwsInstructions, {
+ propsData: {
+ registrationToken: mockRegistrationToken,
+ ...props,
+ },
stubs: {
GlSprintf,
},
@@ -109,6 +118,22 @@ describe('RunnerAwsInstructions', () => {
expect(findLink().attributes('href')).toBe(AWS_README_URL);
});
+ it('shows registration token and copy button', () => {
+ const token = wrapper.findByText(mockRegistrationToken);
+
+ expect(token.exists()).toBe(true);
+ expect(token.element.tagName).toBe('PRE');
+
+ expect(findModalCopyButton().props('text')).toBe(mockRegistrationToken);
+ });
+
+ it('does not show registration token and copy button when token is not present', () => {
+ createComponent({ props: { registrationToken: null } });
+
+ expect(wrapper.find('pre').exists()).toBe(false);
+ expect(findModalCopyButton().exists()).toBe(false);
+ });
+
it('triggers the modal to close', () => {
findCloseButton().vm.$emit('click');
diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
index 19f2dd137ff..8f593b6aa1b 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
@@ -11,6 +11,7 @@ import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions
import RunnerCliInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue';
import RunnerDockerInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue';
import RunnerKubernetesInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue';
+import RunnerAwsInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue';
import { mockRunnerPlatforms } from './mock_data';
@@ -156,6 +157,7 @@ describe('RunnerInstructionsModal component', () => {
platform | component
${'docker'} | ${RunnerDockerInstructions}
${'kubernetes'} | ${RunnerKubernetesInstructions}
+ ${'aws'} | ${RunnerAwsInstructions}
`('with platform "$platform"', ({ platform, component }) => {
beforeEach(async () => {
createComponent({ props: { defaultPlatformName: platform } });
diff --git a/spec/graphql/mutations/saved_replies/create_spec.rb b/spec/graphql/mutations/saved_replies/create_spec.rb
index 5141c537b06..9423ba2b354 100644
--- a/spec/graphql/mutations/saved_replies/create_spec.rb
+++ b/spec/graphql/mutations/saved_replies/create_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Mutations::SavedReplies::Create do
let(:mutation_arguments) { { name: '', content: '' } }
it { expect(subject[:saved_reply]).to be_nil }
- it { expect(subject[:errors]).to match_array(["Content can't be blank", "Name can't be blank", "Name can contain only lowercase letters, digits, '_' and '-'. Must start with a letter, and cannot end with '-' or '_'"]) }
+ it { expect(subject[:errors]).to match_array(["Content can't be blank", "Name can't be blank"]) }
end
context 'when service successfully creates a new saved reply' do
diff --git a/spec/graphql/mutations/saved_replies/update_spec.rb b/spec/graphql/mutations/saved_replies/update_spec.rb
index 67c2d1348f7..9b0e90b7b41 100644
--- a/spec/graphql/mutations/saved_replies/update_spec.rb
+++ b/spec/graphql/mutations/saved_replies/update_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe Mutations::SavedReplies::Update do
let(:mutation_arguments) { { name: '', content: '' } }
it { expect(subject[:saved_reply]).to be_nil }
- it { expect(subject[:errors]).to match_array(["Content can't be blank", "Name can't be blank", "Name can contain only lowercase letters, digits, '_' and '-'. Must start with a letter, and cannot end with '-' or '_'"]) }
+ it { expect(subject[:errors]).to match_array(["Content can't be blank", "Name can't be blank"]) }
end
context 'when service successfully updates the saved reply' do
diff --git a/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb
index 1d1fb4a9967..da6a84cec44 100644
--- a/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb
+++ b/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Resolvers::Ci::RunnerPlatformsResolver, feature_category: :runner
expect(resolve_subject).to contain_exactly(
hash_including(name: :linux), hash_including(name: :osx),
hash_including(name: :windows), hash_including(name: :docker),
- hash_including(name: :kubernetes)
+ hash_including(name: :kubernetes), hash_including(name: :aws)
)
end
end
diff --git a/spec/helpers/ci/variables_helper_spec.rb b/spec/helpers/ci/variables_helper_spec.rb
deleted file mode 100644
index d032e7f9087..00000000000
--- a/spec/helpers/ci/variables_helper_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ci::VariablesHelper, feature_category: :pipeline_authoring do
- describe '#ci_variable_maskable_raw_regex' do
- it 'converts to a javascript regex' do
- expect(helper.ci_variable_maskable_raw_regex).to eq("^\\S{8,}$")
- end
- end
-end
diff --git a/spec/lib/api/entities/release_spec.rb b/spec/lib/api/entities/release_spec.rb
index d1e5f191614..e750b82011b 100644
--- a/spec/lib/api/entities/release_spec.rb
+++ b/spec/lib/api/entities/release_spec.rb
@@ -77,4 +77,16 @@ RSpec.describe API::Entities::Release do
end
end
end
+
+ describe 'links' do
+ subject(:links) { entity.as_json['_links'] }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'includes links' do
+ expect(links.keys).to include('closed_issues_url', 'closed_merge_requests_url', 'edit_url', 'merged_merge_requests_url', 'opened_issues_url', 'opened_merge_requests_url', 'self')
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/runner_instructions_spec.rb b/spec/lib/gitlab/ci/runner_instructions_spec.rb
index 56f69720b87..dd547a0161a 100644
--- a/spec/lib/gitlab/ci/runner_instructions_spec.rb
+++ b/spec/lib/gitlab/ci/runner_instructions_spec.rb
@@ -29,7 +29,6 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
context name do
it 'has the required fields' do
expect(subject).to have_key(:human_readable_name)
- expect(subject).to have_key(:installation_instructions_url)
end
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 0770e7b9f7d..353b8eb7edb 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -978,21 +978,6 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
end
- describe '.saved_reply_name_regex' do
- subject { described_class.saved_reply_name_regex }
-
- it { is_expected.to match('test') }
- it { is_expected.to match('test123') }
- it { is_expected.to match('test-test') }
- it { is_expected.to match('test-test_0123') }
- it { is_expected.not_to match('test test') }
- it { is_expected.not_to match('test-') }
- it { is_expected.not_to match('/z/test_') }
- it { is_expected.not_to match('.xtest_') }
- it { is_expected.not_to match('.xt.est_') }
- it { is_expected.not_to match('0test1') }
- end
-
describe '.sha256_regex' do
subject { described_class.sha256_regex }
diff --git a/spec/models/concerns/ci/maskable_spec.rb b/spec/models/concerns/ci/maskable_spec.rb
index b58d0625e2e..2b13fc21fe8 100644
--- a/spec/models/concerns/ci/maskable_spec.rb
+++ b/spec/models/concerns/ci/maskable_spec.rb
@@ -2,15 +2,15 @@
require 'spec_helper'
-RSpec.describe Ci::Maskable, feature_category: :pipeline_authoring do
+RSpec.describe Ci::Maskable do
let(:variable) { build(:ci_variable) }
describe 'masked value validations' do
subject { variable }
- context 'when variable is masked and expanded' do
+ context 'when variable is masked' do
before do
- subject.update!(masked: true, raw: false)
+ subject.masked = true
end
it { is_expected.not_to allow_value('hello').for(:value) }
@@ -20,53 +20,6 @@ RSpec.describe Ci::Maskable, feature_category: :pipeline_authoring do
it { is_expected.to allow_value('helloworld').for(:value) }
end
- context 'when method :raw is not defined' do
- let(:test_var_class) do
- Struct.new(:masked?) do
- include ActiveModel::Validations
- include Ci::Maskable
- end
- end
-
- let(:variable) { test_var_class.new }
-
- it 'evaluates masked variables as expanded' do
- expect(subject).not_to be_masked_and_raw
- expect(subject).to be_masked_and_expanded
- end
- end
-
- context 'when the ci_remove_character_limitation_raw_masked_var FF is disabled' do
- context 'when variable is masked and raw' do
- before do
- subject.update!(masked: true, raw: true)
- stub_feature_flags(ci_remove_character_limitation_raw_masked_var: false)
- end
-
- it { is_expected.not_to allow_value('hello').for(:value) }
- it { is_expected.not_to allow_value('hello world').for(:value) }
- it { is_expected.not_to allow_value('hello$VARIABLEworld').for(:value) }
- it { is_expected.not_to allow_value('hello\rworld').for(:value) }
- it { is_expected.not_to allow_value('hello&&&world').for(:value) }
- it { is_expected.not_to allow_value('helloworld!!!!').for(:value) }
- it { is_expected.to allow_value('helloworld').for(:value) }
- end
- end
-
- context 'when variable is masked and raw' do
- before do
- subject.update!(masked: true, raw: true)
- end
-
- it { is_expected.not_to allow_value('hello').for(:value) }
- it { is_expected.not_to allow_value('hello world').for(:value) }
- it { is_expected.to allow_value('hello\rworld').for(:value) }
- it { is_expected.to allow_value('hello$VARIABLEworld').for(:value) }
- it { is_expected.to allow_value('helloworld!!!').for(:value) }
- it { is_expected.to allow_value('hell******world').for(:value) }
- it { is_expected.to allow_value('helloworld123').for(:value) }
- end
-
context 'when variable is not masked' do
before do
subject.masked = false
@@ -80,70 +33,40 @@ RSpec.describe Ci::Maskable, feature_category: :pipeline_authoring do
end
end
- describe 'Regexes' do
- context 'with MASK_AND_RAW_REGEX' do
- subject { Ci::Maskable::MASK_AND_RAW_REGEX }
-
- it 'does not match strings shorter than 8 letters' do
- expect(subject.match?('hello')).to eq(false)
- end
-
- it 'does not match strings with spaces' do
- expect(subject.match?('hello world')).to eq(false)
- end
-
- it 'does not match strings that span more than one line' do
- string = <<~EOS
- hello
- world
- EOS
-
- expect(subject.match?(string)).to eq(false)
- end
+ describe 'REGEX' do
+ subject { Ci::Maskable::REGEX }
- it 'matches valid strings' do
- expect(subject.match?('hello$VARIABLEworld')).to eq(true)
- expect(subject.match?('Hello+World_123/@:-~.')).to eq(true)
- expect(subject.match?('hello\rworld')).to eq(true)
- expect(subject.match?('HelloWorld%#^')).to eq(true)
- end
+ it 'does not match strings shorter than 8 letters' do
+ expect(subject.match?('hello')).to eq(false)
end
- context 'with REGEX' do
- subject { Ci::Maskable::REGEX }
-
- it 'does not match strings shorter than 8 letters' do
- expect(subject.match?('hello')).to eq(false)
- end
-
- it 'does not match strings with spaces' do
- expect(subject.match?('hello world')).to eq(false)
- end
+ it 'does not match strings with spaces' do
+ expect(subject.match?('hello world')).to eq(false)
+ end
- it 'does not match strings with shell variables' do
- expect(subject.match?('hello$VARIABLEworld')).to eq(false)
- end
+ it 'does not match strings with shell variables' do
+ expect(subject.match?('hello$VARIABLEworld')).to eq(false)
+ end
- it 'does not match strings with escape characters' do
- expect(subject.match?('hello\rworld')).to eq(false)
- end
+ it 'does not match strings with escape characters' do
+ expect(subject.match?('hello\rworld')).to eq(false)
+ end
- it 'does not match strings that span more than one line' do
- string = <<~EOS
- hello
- world
- EOS
+ it 'does not match strings that span more than one line' do
+ string = <<~EOS
+ hello
+ world
+ EOS
- expect(subject.match?(string)).to eq(false)
- end
+ expect(subject.match?(string)).to eq(false)
+ end
- it 'does not match strings using unsupported characters' do
- expect(subject.match?('HelloWorld%#^')).to eq(false)
- end
+ it 'does not match strings using unsupported characters' do
+ expect(subject.match?('HelloWorld%#^')).to eq(false)
+ end
- it 'matches valid strings' do
- expect(subject.match?('Hello+World_123/@:-~.')).to eq(true)
- end
+ it 'matches valid strings' do
+ expect(subject.match?('Hello+World_123/@:-~.')).to eq(true)
end
end
diff --git a/spec/requests/api/container_registry_event_spec.rb b/spec/requests/api/container_registry_event_spec.rb
index 64c17bc2eef..e83a3faae93 100644
--- a/spec/requests/api/container_registry_event_spec.rb
+++ b/spec/requests/api/container_registry_event_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe API::ContainerRegistryEvent, feature_category: :container_registry do
let(:secret_token) { 'secret_token' }
- let(:events) { [{ action: 'push' }, { action: 'pull' }] }
+ let(:events) { [{ action: 'push' }, { action: 'pull' }, { action: 'mount' }] }
let(:registry_headers) { { 'Content-Type' => ::API::ContainerRegistryEvent::DOCKER_DISTRIBUTION_EVENTS_V1_JSON } }
describe 'POST /container_registry_event/events' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index d817c8fbb59..0026ef291e3 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.shared_examples 'languages and percentages JSON response', feature_category: :projects do
+RSpec.shared_examples 'languages and percentages JSON response' do
let(:expected_languages) { project.repository.languages.to_h { |language| language.values_at(:label, :value) } }
before do
@@ -46,7 +46,7 @@ RSpec.shared_examples 'languages and percentages JSON response', feature_categor
end
end
-RSpec.describe API::Projects do
+RSpec.describe API::Projects, feature_category: :projects do
include ProjectForksHelper
include WorkhorseHelpers
include StubRequests
@@ -207,7 +207,7 @@ RSpec.describe API::Projects do
let(:current_user) { user }
end
- shared_examples 'includes container_registry_access_level', :aggregate_failures do
+ shared_examples 'includes container_registry_access_level' do
it do
project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)
diff --git a/spec/support/shared_examples/views/themed_layout_examples.rb b/spec/support/shared_examples/views/themed_layout_examples.rb
index b6c53dce4cb..599fd141dd7 100644
--- a/spec/support/shared_examples/views/themed_layout_examples.rb
+++ b/spec/support/shared_examples/views/themed_layout_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples "a layout which reflects the application theme setting", :themed_layout do
+RSpec.shared_examples "a layout which reflects the application theme setting" do
context 'as a themed layout' do
let(:default_theme_class) { ::Gitlab::Themes.default.css_class }
diff --git a/spec/views/layouts/application.html.haml_spec.rb b/spec/views/layouts/application.html.haml_spec.rb
index 30c27078ad8..527ba1498b9 100644
--- a/spec/views/layouts/application.html.haml_spec.rb
+++ b/spec/views/layouts/application.html.haml_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'layouts/application', :themed_layout do
+RSpec.describe 'layouts/application' do
let(:user) { create(:user) }
before do
@@ -14,6 +14,8 @@ RSpec.describe 'layouts/application', :themed_layout do
allow(view).to receive(:current_user_mode).and_return(Gitlab::Auth::CurrentUserMode.new(user))
end
+ it_behaves_like 'a layout which reflects the application theme setting'
+
describe "visual review toolbar" do
context "ENV['REVIEW_APPS_ENABLED'] is set to true" do
before do
diff --git a/yarn.lock b/yarn.lock
index ff6bfe361d5..b61feaaf987 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1145,10 +1145,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.18.0.tgz#d89364feb42404a35824a54d518b51af8c1e900f"
integrity sha512-ni9TmhusXpt/3k/DZzovMEUeB/6UTXiDpuujI8HDBqR4Mwlah6FBco5ZfolkW6YjFL0YvtcLWhnwZA0iM3hfMw==
-"@gitlab/ui@53.2.0":
- version "53.2.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-53.2.0.tgz#71e602ac8e6db4452e647ffcfd5892f636c7a6b4"
- integrity sha512-9GwwBTj6TKqsEa83QCjzUe+ObLkALn/G+vvfkQ5UNJIu2PiDjTpAEtySEXEWz4sJoUPZLhxdVXWeukjyBGinAg==
+"@gitlab/ui@54.1.1":
+ version "54.1.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-54.1.1.tgz#f95e51997b07011db319ed9cbd20b713b41cd224"
+ integrity sha512-yYvktCYM2xOzU2s8plMtnSFtCU9PIi5qBDSUAyyDspauIa95wvo79M1iVTwtfCZve+Qwr+vejxEF0GhqtVI8yA==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1"