Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ea1aa2dade
commit
a288697b47
|
|
@ -164,12 +164,12 @@ include:
|
|||
|
||||
.rules:test:smoke-for-omnibus-mr:
|
||||
rules:
|
||||
- if: '$CI_PROJECT_NAME == "omnibus-gitlab" && $PIPELINE_TYPE =~ /TRIGGERED_(CE|EE)_PIPELINE/ && $QA_OMNIBUS_MR_TESTS == "only-smoke-reliable"'
|
||||
- if: '$CI_PROJECT_NAME == "omnibus-gitlab" && $PIPELINE_TYPE =~ /TRIGGERED_(CE|EE)_PIPELINE/ && $QA_OMNIBUS_MR_TESTS == "only-smoke"'
|
||||
variables:
|
||||
QA_RSPEC_TAGS: "--tag smoke --tag reliable --tag ~orchestrated --tag ~skip_live_env"
|
||||
- if: '$CI_PROJECT_NAME == "omnibus-gitlab" && $PIPELINE_TYPE =~ /TRIGGERED_(CE|EE)_PIPELINE/ && $QA_OMNIBUS_MR_TESTS == "except-smoke-reliable"'
|
||||
QA_RSPEC_TAGS: "--tag smoke --tag ~orchestrated --tag ~skip_live_env"
|
||||
- if: '$CI_PROJECT_NAME == "omnibus-gitlab" && $PIPELINE_TYPE =~ /TRIGGERED_(CE|EE)_PIPELINE/ && $QA_OMNIBUS_MR_TESTS == "except-smoke"'
|
||||
variables:
|
||||
QA_RSPEC_TAGS: "--tag ~smoke --tag ~reliable --tag ~orchestrated --tag ~skip_live_env --tag ~transient"
|
||||
QA_RSPEC_TAGS: "--tag ~smoke --tag ~orchestrated --tag ~skip_live_env --tag ~transient"
|
||||
|
||||
# ------------------------------------------
|
||||
# Report
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ variables:
|
|||
QA_RUN_ALL_TESTS: "true"
|
||||
# Used by gitlab-qa to set up a volume for `${CI_PROJECT_DIR}/qa/rspec:/home/gitlab/qa/rspec/`
|
||||
QA_RSPEC_REPORT_PATH: "${CI_PROJECT_DIR}/qa/rspec"
|
||||
QA_OMNIBUS_MR_TESTS: "only-smoke-reliable"
|
||||
QA_OMNIBUS_MR_TESTS: "only-smoke"
|
||||
# Retry failed specs in separate process
|
||||
QA_RETRY_FAILED_SPECS: "true"
|
||||
|
|
|
|||
|
|
@ -3010,6 +3010,20 @@
|
|||
- <<: *if-merge-request-labels-run-all-e2e
|
||||
- <<: *if-merge-request-labels-pipeline-expedite
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
changes: *setup-test-env-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *db-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-qa-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *db-patterns
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-frontend-and-feature-flag
|
||||
|
|
@ -3029,20 +3043,6 @@
|
|||
changes: *redis-patterns
|
||||
- <<: *if-merge-request
|
||||
changes: *feature-flag-development-config-patterns
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
changes: *setup-test-env-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *db-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-qa-patterns
|
||||
- <<: *if-security-merge-request
|
||||
changes: *db-patterns
|
||||
|
||||
.as-if-foss:rules:start-as-if-foss:allow-failure:manual:
|
||||
rules:
|
||||
|
|
@ -3076,6 +3076,34 @@
|
|||
when: manual
|
||||
- <<: *if-merge-request-labels-pipeline-expedite
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
changes: *setup-test-env-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *db-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-qa-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-security-merge-request
|
||||
changes: *db-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-frontend-and-feature-flag
|
||||
|
|
@ -3113,34 +3141,6 @@
|
|||
changes: *feature-flag-development-config-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
changes: *setup-test-env-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *db-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-qa-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
- <<: *if-security-merge-request
|
||||
changes: *db-patterns
|
||||
allow_failure: true
|
||||
when: manual
|
||||
|
||||
.as-if-foss:rules:start-as-if-foss:allow-failure:
|
||||
rules:
|
||||
|
|
@ -3167,6 +3167,27 @@
|
|||
allow_failure: true
|
||||
- <<: *if-merge-request-labels-pipeline-expedite
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
changes: *setup-test-env-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *db-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-qa-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-security-merge-request
|
||||
changes: *db-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request-not-approved
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-frontend-and-feature-flag
|
||||
|
|
@ -3195,27 +3216,6 @@
|
|||
- <<: *if-merge-request
|
||||
changes: *feature-flag-development-config-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
changes: *setup-test-env-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *db-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *backend-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-automated-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-security-merge-request
|
||||
changes: *backend-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-qa-patterns
|
||||
allow_failure: true
|
||||
- <<: *if-security-merge-request
|
||||
changes: *db-patterns
|
||||
allow_failure: true
|
||||
|
||||
##################
|
||||
# as-if-jh rules #
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ include:
|
|||
.rules:gdk:qa-parallel:
|
||||
rules:
|
||||
# To account for cases where a group label is set which may trigger selective execution
|
||||
# But we want to execute full reliable suite on gdk in case of code-pattern-changes
|
||||
# But we want to execute full blocking suite on gdk in case of code-pattern-changes
|
||||
- <<: *code-pattern-changes
|
||||
variables:
|
||||
QA_TESTS: ""
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export default {
|
|||
return getTimeago().format(this.latestVersion?.createdAt);
|
||||
},
|
||||
resourceId() {
|
||||
return cleanLeadingSeparator(this.resource.webPath);
|
||||
return this.resource?.fullPath;
|
||||
},
|
||||
starCount() {
|
||||
return this.resource?.starCount || 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
fragment CatalogResourceFields on CiCatalogResource {
|
||||
id
|
||||
description
|
||||
fullPath
|
||||
icon
|
||||
name
|
||||
starCount
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ export default {
|
|||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
issueCount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
|
|
@ -98,8 +103,14 @@ Once deleted, it cannot be undone or recovered.`),
|
|||
});
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
this.onClose();
|
||||
});
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('deleteModalVisible', false);
|
||||
},
|
||||
},
|
||||
primaryProps: {
|
||||
text: s__('Milestones|Delete milestone'),
|
||||
|
|
@ -113,11 +124,13 @@ Once deleted, it cannot be undone or recovered.`),
|
|||
|
||||
<template>
|
||||
<gl-modal
|
||||
:visible="visible"
|
||||
modal-id="delete-milestone-modal"
|
||||
:title="title"
|
||||
:action-primary="$options.primaryProps"
|
||||
:action-cancel="$options.cancelProps"
|
||||
@primary="onSubmit"
|
||||
@hide="onClose"
|
||||
>
|
||||
<gl-sprintf :message="text">
|
||||
<template #milestoneTitle>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
<script>
|
||||
import {
|
||||
GlButton,
|
||||
GlIcon,
|
||||
GlDisclosureDropdownItem,
|
||||
GlDisclosureDropdownGroup,
|
||||
GlDisclosureDropdown,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import PromoteMilestoneModal from '~/milestones/components/promote_milestone_modal.vue';
|
||||
import DeleteMilestoneModal from '~/milestones/components/delete_milestone_modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
GlIcon,
|
||||
GlDisclosureDropdownItem,
|
||||
GlDisclosureDropdownGroup,
|
||||
GlDisclosureDropdown,
|
||||
PromoteMilestoneModal,
|
||||
DeleteMilestoneModal,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: [
|
||||
'id',
|
||||
'title',
|
||||
'isActive',
|
||||
'showDelete',
|
||||
'isDetailPage',
|
||||
'canReadMilestone',
|
||||
'milestoneUrl',
|
||||
'editUrl',
|
||||
'closeUrl',
|
||||
'reopenUrl',
|
||||
'promoteUrl',
|
||||
'groupName',
|
||||
'issueCount',
|
||||
'mergeRequestCount',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
isDropdownVisible: false,
|
||||
isPromotionModalVisible: false,
|
||||
isDeleteModalVisible: false,
|
||||
isPromoteModalVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasUrl() {
|
||||
return this.editUrl || this.closeUrl || this.reopenUrl || this.promoteUrl;
|
||||
},
|
||||
copiedToClipboard() {
|
||||
return this.$options.i18n.copiedToClipboard;
|
||||
},
|
||||
editItem() {
|
||||
return {
|
||||
text: this.$options.i18n.edit,
|
||||
href: this.editUrl,
|
||||
extraAttrs: {
|
||||
'data-testid': 'milestone-edit-item',
|
||||
},
|
||||
};
|
||||
},
|
||||
promoteItem() {
|
||||
return {
|
||||
text: this.$options.i18n.promote,
|
||||
extraAttrs: {
|
||||
'data-testid': 'milestone-promote-item',
|
||||
},
|
||||
};
|
||||
},
|
||||
closeItem() {
|
||||
return {
|
||||
text: this.$options.i18n.close,
|
||||
href: this.closeUrl,
|
||||
extraAttrs: {
|
||||
class: { 'gl-sm-display-none!': this.isDetailPage },
|
||||
'data-testid': 'milestone-close-item',
|
||||
'data-method': 'put',
|
||||
rel: 'nofollow',
|
||||
},
|
||||
};
|
||||
},
|
||||
reopenItem() {
|
||||
return {
|
||||
text: this.$options.i18n.reopen,
|
||||
href: this.reopenUrl,
|
||||
extraAttrs: {
|
||||
class: { 'gl-sm-display-none!': this.isDetailPage },
|
||||
'data-testid': 'milestone-reopen-item',
|
||||
'data-method': 'put',
|
||||
rel: 'nofollow',
|
||||
},
|
||||
};
|
||||
},
|
||||
deleteItem() {
|
||||
return {
|
||||
text: this.$options.i18n.delete,
|
||||
extraAttrs: {
|
||||
class: 'gl-text-red-500!',
|
||||
'data-testid': 'milestone-delete-item',
|
||||
},
|
||||
};
|
||||
},
|
||||
copyIdItem() {
|
||||
return {
|
||||
text: sprintf(this.$options.i18n.copyTitle, { id: this.id }),
|
||||
action: () => {
|
||||
this.$toast.show(this.copiedToClipboard);
|
||||
},
|
||||
extraAttrs: {
|
||||
'data-testid': 'copy-milestone-id',
|
||||
itemprop: 'identifier',
|
||||
},
|
||||
};
|
||||
},
|
||||
showDropdownTooltip() {
|
||||
return !this.isDropdownVisible ? this.$options.i18n.actionsLabel : '';
|
||||
},
|
||||
showTestIdIfNotDetailPage() {
|
||||
return !this.isDetailPage ? 'milestone-more-actions-dropdown-toggle' : false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showDropdown() {
|
||||
this.isDropdownVisible = true;
|
||||
},
|
||||
hideDropdown() {
|
||||
this.isDropdownVisible = false;
|
||||
},
|
||||
setDeleteModalVisibility(visibility = false) {
|
||||
this.isDeleteModalVisible = visibility;
|
||||
},
|
||||
setPromoteModalVisibility(visibility = false) {
|
||||
this.isPromoteModalVisible = visibility;
|
||||
},
|
||||
},
|
||||
primaryAction: {
|
||||
text: s__('Milestones|Promote Milestone'),
|
||||
attributes: { variant: 'confirm' },
|
||||
},
|
||||
cancelAction: {
|
||||
text: __('Cancel'),
|
||||
attributes: {},
|
||||
},
|
||||
i18n: {
|
||||
actionsLabel: s__('Milestone|Milestone actions'),
|
||||
close: __('Close'),
|
||||
delete: __('Delete'),
|
||||
edit: __('Edit'),
|
||||
promote: __('Promote'),
|
||||
reopen: __('Reopen'),
|
||||
copyTitle: s__('Milestone|Copy milestone ID: %{id}'),
|
||||
copiedToClipboard: s__('Milestone|Milestone ID copied to clipboard.'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-disclosure-dropdown
|
||||
v-gl-tooltip="showDropdownTooltip"
|
||||
category="tertiary"
|
||||
icon="ellipsis_v"
|
||||
placement="bottom-end"
|
||||
block
|
||||
no-caret
|
||||
:toggle-text="$options.i18n.actionsLabel"
|
||||
text-sr-only
|
||||
class="gl-relative gl-w-full gl-sm-w-auto gl-min-w-7"
|
||||
:data-testid="showTestIdIfNotDetailPage"
|
||||
@shown="showDropdown"
|
||||
@hidden="hideDropdown"
|
||||
>
|
||||
<template v-if="isDetailPage" #toggle>
|
||||
<div class="gl-min-h-7">
|
||||
<gl-button
|
||||
class="gl-md-display-none! gl-new-dropdown-toggle gl-absolute gl-top-0 gl-left-0 gl-w-full gl-sm-w-auto"
|
||||
button-text-classes="gl-w-full"
|
||||
category="secondary"
|
||||
:aria-label="$options.i18n.actionsLabel"
|
||||
:title="$options.i18n.actionsLabel"
|
||||
>
|
||||
<span class="gl-new-dropdown-button-text">{{ $options.i18n.actionsLabel }}</span>
|
||||
<gl-icon class="dropdown-chevron" name="chevron-down" />
|
||||
</gl-button>
|
||||
<gl-button
|
||||
class="gl-display-none gl-md-display-flex! gl-new-dropdown-toggle gl-new-dropdown-icon-only gl-new-dropdown-toggle-no-caret"
|
||||
category="tertiary"
|
||||
icon="ellipsis_v"
|
||||
:aria-label="$options.i18n.actionsLabel"
|
||||
:title="$options.i18n.actionsLabel"
|
||||
data-testid="milestone-more-actions-dropdown-toggle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<gl-disclosure-dropdown-item v-if="isActive" :item="closeItem" />
|
||||
<gl-disclosure-dropdown-item v-else :item="reopenItem" />
|
||||
|
||||
<gl-disclosure-dropdown-item v-if="editUrl" :item="editItem" />
|
||||
|
||||
<gl-disclosure-dropdown-item
|
||||
v-if="promoteUrl"
|
||||
:item="promoteItem"
|
||||
@action="setPromoteModalVisibility(true)"
|
||||
/>
|
||||
|
||||
<gl-disclosure-dropdown-group v-if="canReadMilestone" bordered class="gl-border-t-gray-200!">
|
||||
<gl-disclosure-dropdown-item :item="copyIdItem" :data-clipboard-text="id" />
|
||||
</gl-disclosure-dropdown-group>
|
||||
|
||||
<gl-disclosure-dropdown-group v-if="showDelete" bordered class="gl-border-t-gray-200!">
|
||||
<gl-disclosure-dropdown-item :item="deleteItem" @action="setDeleteModalVisibility(true)" />
|
||||
</gl-disclosure-dropdown-group>
|
||||
|
||||
<promote-milestone-modal
|
||||
:visible="isPromoteModalVisible"
|
||||
:milestone-title="title"
|
||||
:promote-url="promoteUrl"
|
||||
:group-name="groupName"
|
||||
@promotionModalVisible="setPromoteModalVisibility"
|
||||
/>
|
||||
|
||||
<delete-milestone-modal
|
||||
:visible="isDeleteModalVisible"
|
||||
:issue-count="issueCount"
|
||||
:merge-request-count="mergeRequestCount"
|
||||
:milestone-id="id"
|
||||
:milestone-title="title"
|
||||
:milestone-url="milestoneUrl"
|
||||
@deleteModalVisible="setDeleteModalVisibility"
|
||||
/>
|
||||
</gl-disclosure-dropdown>
|
||||
</template>
|
||||
|
|
@ -9,14 +9,24 @@ export default {
|
|||
components: {
|
||||
GlModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
milestoneTitle: '',
|
||||
url: '',
|
||||
groupName: '',
|
||||
currentButton: null,
|
||||
visible: false,
|
||||
};
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
milestoneTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
promoteUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
groupName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
|
|
@ -32,33 +42,10 @@ export default {
|
|||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getButtons().forEach((button) => {
|
||||
button.addEventListener('click', this.onPromoteButtonClick);
|
||||
button.removeAttribute('disabled');
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.getButtons().forEach((button) => {
|
||||
button.removeEventListener('click', this.onPromoteButtonClick);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onPromoteButtonClick({ currentTarget }) {
|
||||
const { milestoneTitle, url, groupName } = currentTarget.dataset;
|
||||
currentTarget.setAttribute('disabled', '');
|
||||
this.visible = true;
|
||||
this.milestoneTitle = milestoneTitle;
|
||||
this.url = url;
|
||||
this.groupName = groupName;
|
||||
this.currentButton = currentTarget;
|
||||
},
|
||||
getButtons() {
|
||||
return document.querySelectorAll('.js-promote-project-milestone-button');
|
||||
},
|
||||
onSubmit() {
|
||||
return axios
|
||||
.post(this.url, { params: { format: 'json' } })
|
||||
.post(this.promoteUrl, { params: { format: 'json' } })
|
||||
.then((response) => {
|
||||
visitUrl(response.data.url);
|
||||
})
|
||||
|
|
@ -68,14 +55,11 @@ export default {
|
|||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.visible = false;
|
||||
this.onClose();
|
||||
});
|
||||
},
|
||||
onClose() {
|
||||
this.visible = false;
|
||||
if (this.currentButton) {
|
||||
this.currentButton.removeAttribute('disabled');
|
||||
}
|
||||
this.$emit('promotionModalVisible', false);
|
||||
},
|
||||
},
|
||||
primaryAction: {
|
||||
|
|
@ -92,9 +76,9 @@ export default {
|
|||
<gl-modal
|
||||
:visible="visible"
|
||||
modal-id="promote-milestone-modal"
|
||||
:title="title"
|
||||
:action-primary="$options.primaryAction"
|
||||
:action-cancel="$options.cancelAction"
|
||||
:title="title"
|
||||
@primary="onSubmit"
|
||||
@hide="onClose"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
import Vue from 'vue';
|
||||
import initDatePicker from '~/behaviors/date_picker';
|
||||
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
|
||||
import Milestone from '~/milestones/milestone';
|
||||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||
import { mountMarkdownEditor } from '~/vue_shared/components/markdown/mount_markdown_editor';
|
||||
import Sidebar from '~/right_sidebar';
|
||||
import MountMilestoneSidebar from '~/sidebar/mount_milestone_sidebar';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import ZenMode from '~/zen_mode';
|
||||
import TaskList from '~/task_list';
|
||||
import { TYPE_MILESTONE } from '~/issues/constants';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __ } from '~/locale';
|
||||
import DeleteMilestoneModal from './components/delete_milestone_modal.vue';
|
||||
import PromoteMilestoneModal from './components/promote_milestone_modal.vue';
|
||||
import eventHub from './event_hub';
|
||||
|
||||
// See app/views/shared/milestones/_description.html.haml
|
||||
export const MILESTONE_DESCRIPTION_ELEMENT = '.milestone-detail .description';
|
||||
|
|
@ -54,88 +48,3 @@ export function initShow() {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function initPromoteMilestoneModal() {
|
||||
Vue.use(Translate);
|
||||
|
||||
const promoteMilestoneModal = document.getElementById('promote-milestone-modal');
|
||||
if (!promoteMilestoneModal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Vue({
|
||||
el: promoteMilestoneModal,
|
||||
name: 'PromoteMilestoneModalRoot',
|
||||
render(createElement) {
|
||||
return createElement(PromoteMilestoneModal);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function initDeleteMilestoneModal() {
|
||||
Vue.use(Translate);
|
||||
|
||||
const onRequestFinished = ({ milestoneUrl, successful }) => {
|
||||
const button = document.querySelector(
|
||||
`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`,
|
||||
);
|
||||
|
||||
if (!successful) {
|
||||
button.removeAttribute('disabled');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button');
|
||||
|
||||
const onRequestStarted = (milestoneUrl) => {
|
||||
const button = document.querySelector(
|
||||
`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`,
|
||||
);
|
||||
button.setAttribute('disabled', '');
|
||||
eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished);
|
||||
};
|
||||
|
||||
return new Vue({
|
||||
el: '#js-delete-milestone-modal',
|
||||
name: 'DeleteMilestoneModalRoot',
|
||||
data() {
|
||||
return {
|
||||
modalProps: {
|
||||
milestoneId: -1,
|
||||
milestoneTitle: '',
|
||||
milestoneUrl: '',
|
||||
issueCount: -1,
|
||||
mergeRequestCount: -1,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
eventHub.$on('deleteMilestoneModal.props', this.setModalProps);
|
||||
deleteMilestoneButtons.forEach((button) => {
|
||||
button.removeAttribute('disabled');
|
||||
button.addEventListener('click', () => {
|
||||
this.$root.$emit(BV_SHOW_MODAL, 'delete-milestone-modal');
|
||||
eventHub.$once('deleteMilestoneModal.requestStarted', onRequestStarted);
|
||||
|
||||
this.setModalProps({
|
||||
milestoneId: parseInt(button.dataset.milestoneId, 10),
|
||||
milestoneTitle: button.dataset.milestoneTitle,
|
||||
milestoneUrl: button.dataset.milestoneUrl,
|
||||
issueCount: parseInt(button.dataset.milestoneIssueCount, 10),
|
||||
mergeRequestCount: parseInt(button.dataset.milestoneMergeRequestCount, 10),
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
setModalProps(modalProps) {
|
||||
this.modalProps = modalProps;
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(DeleteMilestoneModal, {
|
||||
props: this.modalProps,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import MoreActionsDropdown from '~/milestones/components/more_actions_dropdown.vue';
|
||||
|
||||
export default function InitMoreActionsDropdown() {
|
||||
const containers = document.querySelectorAll('.js-vue-milestone-actions');
|
||||
|
||||
if (!containers.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return containers.forEach((el) => {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
isActive,
|
||||
showDelete,
|
||||
isDetailPage,
|
||||
canReadMilestone,
|
||||
milestoneUrl,
|
||||
editUrl,
|
||||
closeUrl,
|
||||
reopenUrl,
|
||||
promoteUrl,
|
||||
groupName,
|
||||
issueCount,
|
||||
mergeRequestCount,
|
||||
} = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'MoreActionsDropdownRoot',
|
||||
provide: {
|
||||
id: Number(id),
|
||||
title,
|
||||
isActive: parseBoolean(isActive),
|
||||
showDelete: parseBoolean(showDelete),
|
||||
isDetailPage: parseBoolean(isDetailPage),
|
||||
canReadMilestone: parseBoolean(canReadMilestone),
|
||||
milestoneUrl,
|
||||
editUrl,
|
||||
closeUrl,
|
||||
reopenUrl,
|
||||
promoteUrl,
|
||||
groupName,
|
||||
issueCount: Number(issueCount),
|
||||
mergeRequestCount: Number(mergeRequestCount),
|
||||
},
|
||||
render: (createElement) => createElement(MoreActionsDropdown),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -139,6 +139,9 @@ export default {
|
|||
isLoading() {
|
||||
return this.$apollo.queries.packageFiles.loading || this.mutationLoading;
|
||||
},
|
||||
isLastPage() {
|
||||
return !this.pageInfo.hasPreviousPage && !this.pageInfo.hasNextPage;
|
||||
},
|
||||
filesTableHeaderFields() {
|
||||
return [
|
||||
{
|
||||
|
|
@ -263,7 +266,7 @@ export default {
|
|||
},
|
||||
handleFileDelete(files) {
|
||||
this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION);
|
||||
if (files.length === this.packageFiles.length && !this.pageInfo.hasNextPage) {
|
||||
if (files.length === this.packageFiles.length && this.isLastPage) {
|
||||
this.$emit(
|
||||
'delete-all-files',
|
||||
this.hasOneItem(files)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { initNewResourceDropdown } from '~/vue_shared/components/new_resource_dropdown/init_new_resource_dropdown';
|
||||
import { RESOURCE_TYPE_MILESTONE } from '~/vue_shared/components/new_resource_dropdown/constants';
|
||||
import searchUserGroupsAndProjects from '~/vue_shared/components/new_resource_dropdown/graphql/search_user_groups_and_projects.query.graphql';
|
||||
import InitMoreActionsDropdown from '~/milestones/init_more_actions_dropdown';
|
||||
|
||||
initNewResourceDropdown({
|
||||
resourceType: RESOURCE_TYPE_MILESTONE,
|
||||
|
|
@ -10,3 +11,4 @@ initNewResourceDropdown({
|
|||
...(data?.projects?.nodes ?? []),
|
||||
],
|
||||
});
|
||||
InitMoreActionsDropdown();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import { initDeleteMilestoneModal } from '~/milestones';
|
||||
import InitMoreActionsDropdown from '~/milestones/init_more_actions_dropdown';
|
||||
|
||||
initDeleteMilestoneModal();
|
||||
InitMoreActionsDropdown();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { initDeleteMilestoneModal, initShow } from '~/milestones';
|
||||
import { initShow } from '~/milestones';
|
||||
import InitMoreActionsDropdown from '~/milestones/init_more_actions_dropdown';
|
||||
|
||||
initShow();
|
||||
initDeleteMilestoneModal();
|
||||
InitMoreActionsDropdown();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { initDeleteMilestoneModal, initPromoteMilestoneModal } from '~/milestones';
|
||||
import InitMoreActionsDropdown from '~/milestones/init_more_actions_dropdown';
|
||||
|
||||
initDeleteMilestoneModal();
|
||||
initPromoteMilestoneModal();
|
||||
InitMoreActionsDropdown();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { initDeleteMilestoneModal, initPromoteMilestoneModal, initShow } from '~/milestones';
|
||||
import { initShow } from '~/milestones';
|
||||
import InitMoreActionsDropdown from '~/milestones/init_more_actions_dropdown';
|
||||
|
||||
initShow();
|
||||
initDeleteMilestoneModal();
|
||||
initPromoteMilestoneModal();
|
||||
InitMoreActionsDropdown();
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ module Types
|
|||
|
||||
field :project, ::Types::ProjectType, null: true, description: 'Project of the pipeline schedule.'
|
||||
|
||||
field :next_run_at, Types::TimeType, null: false, description: 'Time when the next pipeline will run.'
|
||||
field :next_run_at, Types::TimeType, null: true, description: 'Time when the next pipeline will run.'
|
||||
|
||||
field :real_next_run, Types::TimeType, null: false, description: 'Time when the next pipeline will run.'
|
||||
field :real_next_run, Types::TimeType, null: true, description: 'Time when the next pipeline will run.'
|
||||
|
||||
field :last_pipeline, PipelineType, null: true, description: 'Last pipeline object.'
|
||||
|
||||
|
|
|
|||
|
|
@ -17,23 +17,7 @@ module Members
|
|||
validates :new_access_level, presence: true
|
||||
validates :user, presence: true
|
||||
validates :member_namespace, presence: true
|
||||
validate :validate_unique_pending_approval, on: [:create, :update]
|
||||
|
||||
scope :pending_member_approvals, ->(member_namespace_id) do
|
||||
where(member_namespace_id: member_namespace_id).where(status: statuses[:pending])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_unique_pending_approval
|
||||
return unless pending?
|
||||
|
||||
scope = self.class.where(user_id: user_id, member_namespace_id: member_namespace_id,
|
||||
new_access_level: new_access_level, status: self.class.statuses[:pending])
|
||||
scope = scope.where.not(id: id) if persisted?
|
||||
return unless scope.exists?
|
||||
|
||||
errors.add(:base, 'A pending approval for the same user, namespace, and access level already exists.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Members::MemberApproval.prepend_mod
|
||||
|
|
|
|||
|
|
@ -92,8 +92,6 @@ module Deployments
|
|||
end
|
||||
|
||||
def link_fast_forward_merge_requests(commits)
|
||||
return if Feature.disabled?(:link_fast_forward_merge_requests_to_deployment, project, type: :gitlab_com_derisk)
|
||||
|
||||
deployment.link_merge_requests(merge_requests_by_head_commit_sha(commits))
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
- milestone_url = Gitlab::UrlBuilder.build(milestone, only_path: true)
|
||||
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary, button_options: { class: 'menu-item js-delete-milestone-button', data: { milestone_id: milestone.id, milestone_title: markdown_field(milestone, :title), milestone_url: milestone_url, milestone_issue_count: milestone.total_issues_count, milestone_merge_request_count: milestone.total_merge_requests_count }, disabled: true }) do
|
||||
.gl-dropdown-item-text-wrapper.gl-text-red-500
|
||||
= _('Delete')
|
||||
#js-delete-milestone-modal
|
||||
|
|
@ -1,11 +1,6 @@
|
|||
.detail-page-description.milestone-detail.gl-py-4
|
||||
%h2.gl-m-0{ data: { testid: "milestone-title-content" } }
|
||||
= markdown_field(milestone, :title)
|
||||
.gl-font-sm.gl-text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal{ itemprop: 'identifier' }
|
||||
- if can?(current_user, :read_milestone, @milestone)
|
||||
%span.gl-display-inline-block.gl-vertical-align-middle
|
||||
= s_('MilestonePage|Milestone ID: %{milestone_id}') % { milestone_id: @milestone.id }
|
||||
= clipboard_button(title: s_('MilestonePage|Copy milestone ID'), text: @milestone.id)
|
||||
|
||||
- if milestone.try(:description).present?
|
||||
%div{ data: { testid: "milestone-description-content" } }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@
|
|||
= render Pajamas::ButtonComponent.new(icon: 'chevron-double-lg-left', button_options: { 'aria-label' => _('Toggle sidebar'), class: 'btn-grouped !gl-float-right gl-sm-display-none js-sidebar-toggle' })
|
||||
|
||||
- if can?(current_user, :admin_milestone, @group || @project)
|
||||
.milestone-buttons.detail-page-header-actions.gl-display-flex.gl-align-self-start
|
||||
- can_promote = @project && can_admin_group_milestones? && milestone.project
|
||||
- can_read_milestone = can?(current_user, :read_milestone, @milestone)
|
||||
|
||||
.milestone-buttons.detail-page-header-actions.gl-display-flex.gl-align-self-start.gl-gap-3
|
||||
- if milestone.active?
|
||||
= render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :close }), method: :put, button_options: { class: 'btn-close gl-display-none gl-md-display-inline-block' }) do
|
||||
= _('Close milestone')
|
||||
|
|
@ -19,38 +22,18 @@
|
|||
= render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :activate }), method: :put, button_options: { class: 'gl-display-none gl-md-display-inline-block' }) do
|
||||
= _('Reopen milestone')
|
||||
|
||||
.gl-md-ml-3.gl-display-flex.dropdown.gl-dropdown.gl-md-w-auto.gl-w-full
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary, icon: 'ellipsis_v', button_options: { class: 'has-tooltip gl-display-none! gl-md-display-inline-flex!', 'aria-label': _('Milestone actions'), data: { toggle: 'dropdown', title: _('Milestone actions'), testid: 'milestone-actions' } })
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'btn-block gl-md-display-none!', data: { toggle: 'dropdown' } }) do
|
||||
= _('Milestone actions')
|
||||
= sprite_icon "chevron-down", size: 16, css_class: "dropdown-icon gl-icon"
|
||||
.dropdown-menu.dropdown-menu-right
|
||||
.gl-dropdown-inner
|
||||
.gl-dropdown-contents
|
||||
%ul
|
||||
%li.gl-dropdown-item
|
||||
= link_to edit_milestone_path(milestone), class: 'menu-item' do
|
||||
.gl-dropdown-item-text-wrapper
|
||||
= _('Edit')
|
||||
- if milestone.project_milestone? && milestone.project.group
|
||||
%li.gl-dropdown-item
|
||||
%button.js-promote-project-milestone-button{ data: { milestone_title: milestone.title,
|
||||
group_name: milestone.project.group.name,
|
||||
url: promote_project_milestone_path(milestone.project, milestone)},
|
||||
disabled: true,
|
||||
type: 'button' }
|
||||
.gl-dropdown-item-text-wrapper
|
||||
= _('Promote')
|
||||
#promote-milestone-modal
|
||||
- if milestone.active?
|
||||
%li.gl-dropdown-item{ class: "gl-md-display-none!" }
|
||||
= link_to update_milestone_path(milestone, { state_event: :close }), method: :put, class: 'menu-item' do
|
||||
.gl-dropdown-item-text-wrapper
|
||||
= _('Close milestone')
|
||||
- else
|
||||
%li.gl-dropdown-item{ class: "gl-md-display-none!" }
|
||||
= link_to update_milestone_path(milestone, { state_event: :activate }), method: :put, class: 'menu-item' do
|
||||
.gl-dropdown-item-text-wrapper
|
||||
= _('Reopen milestone')
|
||||
%li.gl-dropdown-item
|
||||
= render 'shared/milestones/delete_button', milestone: @milestone
|
||||
.js-vue-milestone-actions{ data: { id: @milestone.id,
|
||||
title: milestone.title,
|
||||
is_active: milestone.active?.to_s,
|
||||
show_delete: 'true',
|
||||
is_detail_page: 'true',
|
||||
can_read_milestone: can_read_milestone.to_s,
|
||||
milestone_url: Gitlab::UrlBuilder.build(milestone, only_path: true),
|
||||
edit_url: edit_milestone_path(milestone),
|
||||
close_url: update_milestone_path(milestone, { state_event: :close }),
|
||||
reopen_url: update_milestone_path(milestone, { state_event: :activate }),
|
||||
promote_url: can_promote ? promote_project_milestone_path(milestone.project, milestone) : '',
|
||||
group_name: can_promote && milestone.project_milestone? && milestone.project.group ? milestone.project.group.name : '',
|
||||
issue_count: @milestone.issues.count,
|
||||
merge_request_count: @milestone.merge_requests.count
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -48,39 +48,19 @@
|
|||
.float-lg-right.light
|
||||
= format(s_('Milestone|%{percentage}%{percent} complete'), percentage: milestone.percent_complete, percent: '%')
|
||||
- if can_admin_milestone
|
||||
- show_delete = @project.present? || @group.present?
|
||||
.col-1.order-2.order-md-3
|
||||
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary,
|
||||
size: :small,
|
||||
icon: 'ellipsis_v',
|
||||
button_options: { class: 'gl-ml-3 has-tooltip', 'aria_label': _('Milestone actions'), title: _('Milestone actions'), data: { toggle: 'dropdown' } })
|
||||
.dropdown-menu.dropdown-menu-right
|
||||
.gl-dropdown-inner
|
||||
.gl-dropdown-contents
|
||||
%ul
|
||||
%li.gl-dropdown-item
|
||||
- if milestone.closed?
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary,
|
||||
href: milestone_path(milestone, milestone: { state_event: :activate }),
|
||||
method: :put,
|
||||
variant: :link) do
|
||||
= s_('Milestones|Reopen')
|
||||
- else
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary,
|
||||
href: milestone_path(milestone, milestone: { state_event: :close }),
|
||||
method: :put,
|
||||
variant: :link) do
|
||||
= s_('Milestones|Close')
|
||||
%li.gl-dropdown-item
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary,
|
||||
href: edit_milestone_path(milestone),
|
||||
variant: :link) do
|
||||
= _('Edit')
|
||||
- if can_promote
|
||||
%li.gl-dropdown-item
|
||||
= render Pajamas::ButtonComponent.new(category: :tertiary, variant: :link,
|
||||
button_options: { class: 'js-promote-project-milestone-button', disabled: true, data: { toggle: 'tooltip', container: 'body', url: promote_project_milestone_path(milestone.project, milestone), milestone_title: milestone.title, group_name: @project.group.name } }) do
|
||||
= s_('Promote')
|
||||
- if @project || @group
|
||||
%li.gl-dropdown-item
|
||||
= render 'shared/milestones/delete_button', milestone: milestone
|
||||
.gl-display-flex.gl-justify-content-end
|
||||
.js-vue-milestone-actions{ data: { id: milestone.id,
|
||||
title: milestone.title,
|
||||
is_active: milestone.active?.to_s,
|
||||
show_delete: show_delete.to_s,
|
||||
milestone_url: Gitlab::UrlBuilder.build(milestone, only_path: true),
|
||||
edit_url: edit_milestone_path(milestone),
|
||||
close_url: milestone_path(milestone, milestone: { state_event: :close }),
|
||||
reopen_url: milestone_path(milestone, milestone: { state_event: :activate }),
|
||||
promote_url: can_promote ? promote_project_milestone_path(milestone.project, milestone) : '',
|
||||
group_name: can_promote ? @project.group.name : '',
|
||||
issue_count: milestone.issues.count,
|
||||
merge_request_count: milestone.merge_requests.count
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
name: link_fast_forward_merge_requests_to_deployment
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/384104
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145211
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442377
|
||||
milestone: '16.10'
|
||||
group: group::environments
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdateUniqueIndexOnMemberApprovals < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.0'
|
||||
disable_ddl_transaction!
|
||||
|
||||
OLD_INDEX_NAME = 'unique_idx_member_approvals_on_pending_status'
|
||||
NEW_INDEX_NAME = 'unique_index_member_approvals_on_pending_status'
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :member_approvals, OLD_INDEX_NAME
|
||||
|
||||
add_concurrent_index :member_approvals, [:user_id, :member_namespace_id, :new_access_level, :member_role_id],
|
||||
unique: true, where: "status = 0", name: NEW_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :member_approvals, NEW_INDEX_NAME
|
||||
|
||||
add_concurrent_index :member_approvals, [:user_id, :member_namespace_id, :new_access_level],
|
||||
unique: true, where: "status = 0", name: OLD_INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
e115fde7eaa73b72417f8e6ab24676b9e11d934c6ce696cd99873041ac3185b6
|
||||
|
|
@ -28036,8 +28036,6 @@ CREATE UNIQUE INDEX unique_external_audit_event_destination_namespace_id_and_nam
|
|||
|
||||
CREATE UNIQUE INDEX unique_google_cloud_logging_configurations_on_namespace_id ON audit_events_google_cloud_logging_configurations USING btree (namespace_id, google_project_id_name, log_id_name);
|
||||
|
||||
CREATE UNIQUE INDEX unique_idx_member_approvals_on_pending_status ON member_approvals USING btree (user_id, member_namespace_id, new_access_level) WHERE (status = 0);
|
||||
|
||||
CREATE UNIQUE INDEX unique_idx_namespaces_storage_limit_exclusions_on_namespace_id ON namespaces_storage_limit_exclusions USING btree (namespace_id);
|
||||
|
||||
CREATE UNIQUE INDEX unique_import_source_users_source_identifier_and_import_source ON import_source_users USING btree (source_user_identifier, namespace_id, source_hostname, import_type);
|
||||
|
|
@ -28048,6 +28046,8 @@ CREATE UNIQUE INDEX unique_index_for_credit_card_validation_payment_method_xid O
|
|||
|
||||
CREATE UNIQUE INDEX unique_index_for_project_pages_unique_domain ON project_settings USING btree (pages_unique_domain) WHERE (pages_unique_domain IS NOT NULL);
|
||||
|
||||
CREATE UNIQUE INDEX unique_index_member_approvals_on_pending_status ON member_approvals USING btree (user_id, member_namespace_id, new_access_level, member_role_id) WHERE (status = 0);
|
||||
|
||||
CREATE UNIQUE INDEX unique_index_ml_model_metadata_name ON ml_model_metadata USING btree (model_id, name);
|
||||
|
||||
CREATE UNIQUE INDEX unique_index_ml_model_version_metadata_name ON ml_model_version_metadata USING btree (model_version_id, name);
|
||||
|
|
|
|||
|
|
@ -26111,10 +26111,10 @@ Represents a pipeline schedule.
|
|||
| <a id="pipelineschedulefortag"></a>`forTag` | [`Boolean!`](#boolean) | Indicates if a pipelines schedule belongs to a tag. |
|
||||
| <a id="pipelinescheduleid"></a>`id` | [`ID!`](#id) | ID of the pipeline schedule. |
|
||||
| <a id="pipelineschedulelastpipeline"></a>`lastPipeline` | [`Pipeline`](#pipeline) | Last pipeline object. |
|
||||
| <a id="pipelineschedulenextrunat"></a>`nextRunAt` | [`Time!`](#time) | Time when the next pipeline will run. |
|
||||
| <a id="pipelineschedulenextrunat"></a>`nextRunAt` | [`Time`](#time) | Time when the next pipeline will run. |
|
||||
| <a id="pipelinescheduleowner"></a>`owner` | [`UserCore`](#usercore) | Owner of the pipeline schedule. |
|
||||
| <a id="pipelinescheduleproject"></a>`project` | [`Project`](#project) | Project of the pipeline schedule. |
|
||||
| <a id="pipelineschedulerealnextrun"></a>`realNextRun` | [`Time!`](#time) | Time when the next pipeline will run. |
|
||||
| <a id="pipelineschedulerealnextrun"></a>`realNextRun` | [`Time`](#time) | Time when the next pipeline will run. |
|
||||
| <a id="pipelinescheduleref"></a>`ref` | [`String`](#string) | Ref of the pipeline schedule. |
|
||||
| <a id="pipelineschedulereffordisplay"></a>`refForDisplay` | [`String`](#string) | Git ref for the pipeline schedule. |
|
||||
| <a id="pipelineschedulerefpath"></a>`refPath` | [`String`](#string) | Path to the ref that triggered the pipeline. |
|
||||
|
|
|
|||
|
|
@ -311,37 +311,27 @@ You can find the play button in the pipelines, environments, deployments, and jo
|
|||
|
||||
## Track newly included merge requests per deployment
|
||||
|
||||
> - Feature flag `link_fast_forward_merge_requests_to_deployment` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/384104) in GitLab 16.10. Disabled by default.
|
||||
|
||||
GitLab can track newly included merge requests per deployment.
|
||||
When a deployment succeeds, the system calculates commit-diffs between the latest deployment and the previous deployment.
|
||||
You can fetch tracking information with the [Deployment API](../../api/deployments.md#list-of-merge-requests-associated-with-a-deployment)
|
||||
or view it at a post-merge pipeline in [merge request pages](../../user/project/merge_requests/index.md).
|
||||
|
||||
To enable tracking:
|
||||
To enable tracking configure your environment so either:
|
||||
|
||||
1. Set your [project's merge method](../../user/project/merge_requests/methods/index.md).
|
||||
The merge method:
|
||||
- The [environment name](../yaml/index.md#environmentname) doesn't use folders with `/` (long-lived or top-level environments).
|
||||
- The [environment tier](#deployment-tier-of-environments) is either `production` or `staging`.
|
||||
|
||||
- Must _not_ be **Fast-forward merge**.
|
||||
- Can be **Fast-forward merge** if the `link_fast_forward_merge_requests_to_deployment` feature flag is enabled.
|
||||
Here are some example configurations using the [`environment` keyword](../yaml/index.md#environment) in `.gitlab-ci.yml`:
|
||||
|
||||
1. Configure your environment so either:
|
||||
```yaml
|
||||
# Trackable
|
||||
environment: production
|
||||
environment: production/aws
|
||||
environment: development
|
||||
|
||||
- The [environment name](../yaml/index.md#environmentname) doesn't use folders with `/` (long-lived or top-level environments).
|
||||
- The [environment tier](#deployment-tier-of-environments) is either `production` or `staging`.
|
||||
|
||||
Here are some example configurations using the [`environment` keyword](../yaml/index.md#environment) in `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
# Trackable
|
||||
environment: production
|
||||
environment: production/aws
|
||||
environment: development
|
||||
|
||||
# Non Trackable
|
||||
environment: review/$CI_COMMIT_REF_SLUG
|
||||
environment: testing/aws
|
||||
# Non Trackable
|
||||
environment: review/$CI_COMMIT_REF_SLUG
|
||||
environment: testing/aws
|
||||
```
|
||||
|
||||
Configuration changes apply only to new deployments. Existing deployment records do not have merge requests linked or unlinked from them.
|
||||
|
|
|
|||
|
|
@ -207,10 +207,12 @@ the webhooks yourself.
|
|||
> - Introduced in GitLab 15.2 [with a flag](../../../administration/feature_flags.md) named `webhooks_failed_callout`. Disabled by default.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/365535) in GitLab 15.7. Feature flag `webhooks_failed_callout` removed.
|
||||
|
||||
When a webhook is temporarily disabled, a `Webhook fails to connect` error appears at the top
|
||||
with information on when the webhook is re-enabled automatically.
|
||||
When a webhook is permanently disabled, a `Webhook failed to connect` error appears at the top
|
||||
with information on how to re-enable the webhook yourself.
|
||||
Webhooks can be temporarily or permanently disabled:
|
||||
|
||||
- When a webhook is **temporarily disabled**, a `Webhook fails to connect` error appears
|
||||
with information on when the webhook is re-enabled automatically.
|
||||
- When a webhook is **permanently disabled**, a `Webhook failed to connect` error appears
|
||||
with information on how to re-enable the webhook yourself.
|
||||
|
||||
To re-enable a temporarily or permanently disabled webhook manually, [send a test request](#test-a-webhook).
|
||||
If the test request returns a response code in the `2xx` range, the webhook is re-enabled.
|
||||
|
|
|
|||
|
|
@ -32404,9 +32404,6 @@ msgid_plural "Milestones"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Milestone actions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestone due date"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32431,12 +32428,6 @@ msgstr ""
|
|||
msgid "MilestoneCombobox|Select milestone"
|
||||
msgstr ""
|
||||
|
||||
msgid "MilestonePage|Copy milestone ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "MilestonePage|Milestone ID: %{milestone_id}"
|
||||
msgstr ""
|
||||
|
||||
msgid "MilestoneSidebar|Closed:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32503,9 +32494,6 @@ msgstr ""
|
|||
msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestones|Close"
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestones|Completed Issues (closed)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32545,9 +32533,6 @@ msgstr ""
|
|||
msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged."
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestones|Reopen"
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestones|There are no closed milestones"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32566,6 +32551,15 @@ msgstr ""
|
|||
msgid "Milestone|%{percentage}%{percent} complete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestone|Copy milestone ID: %{id}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestone|Milestone ID copied to clipboard."
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestone|Milestone actions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Min Value"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -600,10 +600,6 @@ module QA
|
|||
enabled?(ENV['QA_VALIDATE_RESOURCE_REUSE'], default: false)
|
||||
end
|
||||
|
||||
def skip_smoke_reliable?
|
||||
enabled?(ENV['QA_SKIP_SMOKE_RELIABLE'], default: false)
|
||||
end
|
||||
|
||||
def fips?
|
||||
enabled?(ENV['FIPS'], default: false)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ module QA
|
|||
|
||||
tags_for_rspec.push(%w[--tag ~geo]) unless QA::Runtime::Env.geo_environment?
|
||||
tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
|
||||
tags_for_rspec.push(%w[--tag ~smoke --tag ~reliable]) if QA::Runtime::Env.skip_smoke_reliable?
|
||||
tags_for_rspec.push(%w[--tag ~skip_live_env]) if QA::Specs::Helpers::ContextSelector.dot_com?
|
||||
|
||||
QA::Runtime::Env.supported_features.each_key do |key|
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ RSpec.describe 'Group milestones', feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when milestones exists' do
|
||||
context 'when milestones exists', :js do
|
||||
let_it_be(:other_project) { create(:project_empty_repo, group: group) }
|
||||
|
||||
let_it_be(:active_project_milestone1) do
|
||||
|
|
@ -116,12 +116,42 @@ RSpec.describe 'Group milestones', feature_category: :team_planning do
|
|||
end
|
||||
|
||||
page.within('.detail-page-header') do
|
||||
find_by_testid('milestone-more-actions-dropdown-toggle').click
|
||||
click_link('Edit')
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.milestone-form')
|
||||
end
|
||||
|
||||
it 'shows milestone id' do
|
||||
page.within(".milestones #milestone_#{active_group_milestone.id}") do
|
||||
click_link(active_group_milestone.title)
|
||||
end
|
||||
|
||||
page.within('.detail-page-header') do
|
||||
find_by_testid('milestone-more-actions-dropdown-toggle').click
|
||||
end
|
||||
|
||||
expect(page).to have_selector('[data-testid="copy-milestone-id"]')
|
||||
expect(page).to have_content("Copy milestone ID: #{active_group_milestone.id}")
|
||||
end
|
||||
|
||||
it 'delete a milestone' do
|
||||
page.within(".milestones #milestone_#{active_group_milestone.id}") do
|
||||
click_link(active_group_milestone.title)
|
||||
end
|
||||
|
||||
page.within('.detail-page-header') do
|
||||
find_by_testid('milestone-more-actions-dropdown-toggle').click
|
||||
click_button('Delete')
|
||||
end
|
||||
|
||||
click_button('Delete milestone')
|
||||
|
||||
expect(page).to have_selector('.milestones')
|
||||
expect(page).not_to have_selector(".milestones #milestone_#{active_group_milestone.id}")
|
||||
end
|
||||
|
||||
it 'renders milestones' do
|
||||
expect(page).to have_content('v1.0')
|
||||
expect(page).to have_content('v1.1')
|
||||
|
|
|
|||
|
|
@ -105,18 +105,18 @@ RSpec.describe 'Milestone', feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Deleting a milestone' do
|
||||
describe 'Deleting a milestone', :js do
|
||||
it "the delete milestone button does not show for unauthorized users" do
|
||||
create(:milestone, project: project, title: 8.7)
|
||||
sign_out(user)
|
||||
|
||||
visit group_milestones_path(group)
|
||||
|
||||
expect(page).to have_selector('.js-delete-milestone-button', count: 0)
|
||||
expect(page).to have_selector('[data-testid="milestone-delete-item"]', count: 0)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'reopen closed milestones' do
|
||||
describe 'reopen closed milestones', :js do
|
||||
before do
|
||||
create(:milestone, :closed, project: project)
|
||||
end
|
||||
|
|
@ -125,6 +125,7 @@ RSpec.describe 'Milestone', feature_category: :team_planning do
|
|||
it 'reopens the milestone' do
|
||||
visit group_milestones_path(group, { state: 'closed' })
|
||||
|
||||
find_by_testid('milestone-more-actions-dropdown-toggle').click
|
||||
click_link 'Reopen'
|
||||
|
||||
expect(page).not_to have_selector('.badge-danger')
|
||||
|
|
@ -132,10 +133,11 @@ RSpec.describe 'Milestone', feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'project milestones page' do
|
||||
describe 'project milestones page', :js do
|
||||
it 'reopens the milestone' do
|
||||
visit project_milestones_path(project, { state: 'closed' })
|
||||
|
||||
find_by_testid('milestone-more-actions-dropdown-toggle').click
|
||||
click_link 'Reopen'
|
||||
|
||||
expect(page).not_to have_selector('.badge-danger')
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ RSpec.describe 'User promotes milestone', feature_category: :team_planning do
|
|||
visit(project_milestones_path(project))
|
||||
end
|
||||
|
||||
it "shows milestone promote button" do
|
||||
expect(page).to have_selector('.js-promote-project-milestone-button')
|
||||
it "shows milestone promote button", :js do
|
||||
find_by_testid('milestone-more-actions-dropdown-toggle').click
|
||||
|
||||
expect(page).to have_selector('[data-testid="milestone-promote-item"]')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -27,8 +29,10 @@ RSpec.describe 'User promotes milestone', feature_category: :team_planning do
|
|||
visit(project_milestones_path(project))
|
||||
end
|
||||
|
||||
it "does not show milestone promote button" do
|
||||
expect(page).not_to have_selector('.js-promote-project-milestone-button')
|
||||
it "does not show milestone promote button", :js do
|
||||
find_by_testid('milestone-more-actions-dropdown-toggle').click
|
||||
|
||||
expect(page).not_to have_selector('[data-testid="milestone-promote-item"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import VueRouter from 'vue-router';
|
|||
import { update, cloneDeep } from 'lodash';
|
||||
import { GlAvatar, GlBadge, GlSprintf, GlTruncate } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
|
||||
import { createRouter } from '~/ci/catalog/router/index';
|
||||
import CiResourcesListItem from '~/ci/catalog/components/list/ci_resources_list_item.vue';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
|
|
@ -75,7 +74,7 @@ describe('CiResourcesListItem', () => {
|
|||
|
||||
it('renders the resource name and link', () => {
|
||||
expect(findResourceName().exists()).toBe(true);
|
||||
expect(findResourceName().attributes().href).toBe(defaultProps.resource.webPath);
|
||||
expect(findResourceName().attributes().href).toBe(`/${defaultProps.resource.fullPath}`);
|
||||
});
|
||||
|
||||
it('renders the resource version badge', () => {
|
||||
|
|
@ -229,7 +228,7 @@ describe('CiResourcesListItem', () => {
|
|||
await findResourceName().vm.$emit('click', defaultEvent);
|
||||
|
||||
expect(routerPush).toHaveBeenCalledWith({
|
||||
path: cleanLeadingSeparator(resource.webPath),
|
||||
path: resource.fullPath,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -258,7 +257,7 @@ describe('CiResourcesListItem', () => {
|
|||
});
|
||||
|
||||
it('navigates to the details page', () => {
|
||||
expect(routerPush).toHaveBeenCalledWith({ path: cleanLeadingSeparator(resource.webPath) });
|
||||
expect(routerPush).toHaveBeenCalledWith({ path: resource.fullPath });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-42',
|
||||
fullPath: 'namespace/frontend-fixtures/project-42',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -117,6 +118,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-41',
|
||||
fullPath: 'namespace/frontend-fixtures/project-41',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -132,6 +134,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-42',
|
||||
fullPath: 'namespace/frontend-fixtures/project-42',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -147,6 +150,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-39',
|
||||
fullPath: 'namespace/frontend-fixtures/project-39',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -162,6 +166,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-38',
|
||||
fullPath: 'namespace/frontend-fixtures/project-38',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -177,6 +182,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-37',
|
||||
fullPath: 'namespace/frontend-fixtures/project-37',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -192,6 +198,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-36',
|
||||
fullPath: 'namespace/frontend-fixtures/project-36',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -207,6 +214,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-35',
|
||||
fullPath: 'namespace/frontend-fixtures/project-35',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -222,6 +230,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-34',
|
||||
fullPath: 'namespace/frontend-fixtures/project-34',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -237,6 +246,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-33',
|
||||
fullPath: 'namespace/frontend-fixtures/project-33',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -252,6 +262,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-32',
|
||||
fullPath: 'namespace/frontend-fixtures/project-32',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -267,6 +278,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-31',
|
||||
fullPath: 'namespace/frontend-fixtures/project-31',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -282,6 +294,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-30',
|
||||
fullPath: 'namespace/frontend-fixtures/project-30',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -297,6 +310,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-29',
|
||||
fullPath: 'namespace/frontend-fixtures/project-29',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -312,6 +326,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-28',
|
||||
fullPath: 'namespace/frontend-fixtures/project-28',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -327,6 +342,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-27',
|
||||
fullPath: 'namespace/frontend-fixtures/project-27',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -342,6 +358,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-26',
|
||||
fullPath: 'namespace/frontend-fixtures/project-26',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -357,6 +374,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-25',
|
||||
fullPath: 'namespace/frontend-fixtures/project-25',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -372,6 +390,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-24',
|
||||
fullPath: 'namespace/frontend-fixtures/project-24',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -387,6 +406,7 @@ export const catalogResponseBody = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-23',
|
||||
fullPath: 'namespace/frontend-fixtures/project-23',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
],
|
||||
|
|
@ -437,6 +457,7 @@ export const catalogSinglePageResponse = {
|
|||
],
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-45',
|
||||
fullPath: 'namespace/frontend-fixtures/project-45',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -451,6 +472,7 @@ export const catalogSinglePageResponse = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-44',
|
||||
fullPath: 'namespace/frontend-fixtures/project-44',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
{
|
||||
|
|
@ -465,6 +487,7 @@ export const catalogSinglePageResponse = {
|
|||
__typename: 'CiCatalogResourceVersionConnection',
|
||||
},
|
||||
webPath: '/frontend-fixtures/project-43',
|
||||
fullPath: 'namespace/frontend-fixtures/project-43',
|
||||
__typename: 'CiCatalogResource',
|
||||
},
|
||||
],
|
||||
|
|
@ -505,6 +528,7 @@ export const catalogSharedDataMock = {
|
|||
],
|
||||
},
|
||||
webPath: '/path/to/project',
|
||||
fullPath: 'namespace/path/to/project',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -554,6 +578,7 @@ const generateResourcesNodes = (count = 20, startId = 0) => {
|
|||
],
|
||||
},
|
||||
webPath: 'path/to/project',
|
||||
fullPath: 'namespace/path/to/project',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { initAllowRunnerRegistrationTokenToggle } from '~/group_settings/allow_runner_registration_token_toggle';
|
||||
|
||||
import { GlToggle } from '@gitlab/ui';
|
||||
import { createWrapper } from '@vue/test-utils';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
|
||||
import { initAllowRunnerRegistrationTokenToggle } from '~/group_settings/allow_runner_registration_token_toggle';
|
||||
|
||||
describe('initAllowRunnerRegistrationTokenToggle', () => {
|
||||
let form;
|
||||
let wrapper;
|
||||
let requestSubmitMock;
|
||||
|
||||
const setFormFixture = ({
|
||||
|
|
@ -20,14 +24,16 @@ describe('initAllowRunnerRegistrationTokenToggle', () => {
|
|||
</form>
|
||||
`);
|
||||
|
||||
initAllowRunnerRegistrationTokenToggle();
|
||||
const toggle = initAllowRunnerRegistrationTokenToggle();
|
||||
|
||||
form = document.querySelector('form');
|
||||
wrapper = createWrapper(toggle);
|
||||
|
||||
requestSubmitMock = jest.spyOn(form, 'requestSubmit').mockImplementation(() => {});
|
||||
};
|
||||
|
||||
const findInput = () => form.querySelector('[name="group[allow_runner_registration_token]"]');
|
||||
const findToggle = () => form.querySelector('[data-testid="toggle-wrapper"] button');
|
||||
const findToggle = () => wrapper.findComponent(GlToggle);
|
||||
|
||||
afterEach(() => {
|
||||
resetHTMLFixture();
|
||||
|
|
@ -38,7 +44,7 @@ describe('initAllowRunnerRegistrationTokenToggle', () => {
|
|||
|
||||
expect(form.textContent).toContain('Toggle Label');
|
||||
|
||||
expect(findToggle()).toBeDefined();
|
||||
expect(findToggle().exists()).toBeDefined();
|
||||
expect(findInput()).toBeDefined();
|
||||
});
|
||||
|
||||
|
|
@ -48,30 +54,38 @@ describe('initAllowRunnerRegistrationTokenToggle', () => {
|
|||
});
|
||||
|
||||
it('shows an "on" toggle', () => {
|
||||
expect(findToggle().props('value')).toBe(true);
|
||||
expect(findInput().value).toBe('true');
|
||||
expect(findToggle().getAttribute('aria-checked')).toBe('true');
|
||||
});
|
||||
|
||||
it('when clicked, toggles the setting', () => {
|
||||
findToggle().click();
|
||||
it('when clicked, toggles the setting', async () => {
|
||||
findToggle().vm.$emit('change', false);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findToggle().props('isLoading')).toBe(true);
|
||||
expect(findInput().value).toBe('false');
|
||||
|
||||
expect(requestSubmitMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setting is disabled', () => {
|
||||
beforeEach(() => {
|
||||
setFormFixture({ hiddenInputValue: 'false', toggleIsChecked: 'false' });
|
||||
});
|
||||
|
||||
it('shows an "off toggle"', () => {
|
||||
expect(findToggle().props('value')).toBe(false);
|
||||
expect(findInput().value).toBe('false');
|
||||
expect(findToggle().getAttribute('aria-checked')).toBe('false');
|
||||
});
|
||||
|
||||
it('when clicked, toggles the setting', () => {
|
||||
findToggle().click();
|
||||
it('when clicked, toggles the setting', async () => {
|
||||
findToggle().vm.$emit('change', true);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findToggle().props('isLoading')).toBe(true);
|
||||
expect(findInput().value).toBe('true');
|
||||
expect(requestSubmitMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,293 @@
|
|||
import { GlDisclosureDropdownItem, GlDisclosureDropdown } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import moreActionsDropdown from '~/milestones/components/more_actions_dropdown.vue';
|
||||
import DeleteMilestoneModal from '~/milestones/components/delete_milestone_modal.vue';
|
||||
import PromoteMilestoneModal from '~/milestones/components/promote_milestone_modal.vue';
|
||||
|
||||
describe('moreActionsDropdown', () => {
|
||||
let wrapper;
|
||||
const defaultProvide = {
|
||||
id: 1,
|
||||
title: 'Milestone 1',
|
||||
isActive: true,
|
||||
showDelete: true,
|
||||
canReadMilestone: true,
|
||||
milestoneUrl: '/milestone-url',
|
||||
editUrl: '/edit-url',
|
||||
closeUrl: '/close-url',
|
||||
reopenUrl: '/reopen-url',
|
||||
promoteUrl: '/promote-url',
|
||||
groupName: 'test-group',
|
||||
issueCount: 1,
|
||||
mergeRequestCount: 2,
|
||||
isDetailPage: false,
|
||||
};
|
||||
|
||||
const createComponent = ({ provideData = {}, propsData = {} } = {}) => {
|
||||
wrapper = shallowMountExtended(moreActionsDropdown, {
|
||||
directives: {
|
||||
GlTooltip: createMockDirective('gl-tooltip'),
|
||||
},
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
...provideData,
|
||||
},
|
||||
propsData,
|
||||
stubs: {
|
||||
GlDisclosureDropdownItem,
|
||||
DeleteMilestoneModal,
|
||||
PromoteMilestoneModal,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
|
||||
const showDropdown = () => {
|
||||
findDropdown().vm.$emit('show');
|
||||
};
|
||||
const findDropdownTooltip = () => getBinding(findDropdown().element, 'gl-tooltip');
|
||||
const findEditItem = () => wrapper.findByTestId('milestone-edit-item');
|
||||
const findPromoteItem = () => wrapper.findByTestId('milestone-promote-item');
|
||||
const findPromoteMilestoneModal = () => wrapper.findComponent(PromoteMilestoneModal);
|
||||
const findCloseItem = () => wrapper.findByTestId('milestone-close-item');
|
||||
const findReopenItem = () => wrapper.findByTestId('milestone-reopen-item');
|
||||
const findDeleteItem = () => wrapper.findByTestId('milestone-delete-item');
|
||||
const findMilestoneIdItem = () => wrapper.findByTestId('copy-milestone-id');
|
||||
const findDeleteMilestoneModal = () => wrapper.findComponent(DeleteMilestoneModal);
|
||||
|
||||
describe('dropdown group', () => {
|
||||
it('renders tooltip', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findDropdownTooltip().value).toBe('Milestone actions');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit item', () => {
|
||||
it('renders with correct value if `editUrl` is set', () => {
|
||||
const provideData = {
|
||||
editUrl: '/my-edit-url',
|
||||
};
|
||||
|
||||
createComponent({
|
||||
provideData,
|
||||
});
|
||||
|
||||
expect(findEditItem().attributes('href')).toBe(provideData.editUrl);
|
||||
});
|
||||
|
||||
it('does not render if `editUrl` is false', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
editUrl: '',
|
||||
},
|
||||
});
|
||||
|
||||
expect(findEditItem().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('promote item', () => {
|
||||
const provideData = {
|
||||
promoteUrl: '/my-promote-url',
|
||||
groupName: 'promote-group',
|
||||
title: 'Milestone to promote',
|
||||
};
|
||||
|
||||
it('renders with correct values if `promoteUrl` is set', () => {
|
||||
createComponent({
|
||||
provideData,
|
||||
});
|
||||
|
||||
expect(findPromoteItem().exists()).toBe(true);
|
||||
expect(findPromoteMilestoneModal().props()).toMatchObject({
|
||||
visible: false,
|
||||
milestoneTitle: provideData.title,
|
||||
promoteUrl: provideData.promoteUrl,
|
||||
groupName: provideData.groupName,
|
||||
});
|
||||
});
|
||||
|
||||
it('click on promote opens confirm modal with correct props', async () => {
|
||||
createComponent({
|
||||
provideData,
|
||||
});
|
||||
|
||||
expect(findPromoteMilestoneModal().props('visible')).toBe(false);
|
||||
|
||||
findPromoteItem().trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect(findPromoteMilestoneModal().props()).toMatchObject({
|
||||
visible: true,
|
||||
milestoneTitle: provideData.title,
|
||||
promoteUrl: provideData.promoteUrl,
|
||||
groupName: provideData.groupName,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render if `promoteUrl` is false', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
promoteUrl: '',
|
||||
},
|
||||
});
|
||||
|
||||
expect(findPromoteItem().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('close item', () => {
|
||||
it('renders with correct values if `isActive` is set', () => {
|
||||
const provideData = {
|
||||
isActive: true,
|
||||
closeUrl: '/my-close-url',
|
||||
};
|
||||
|
||||
createComponent({
|
||||
provideData,
|
||||
});
|
||||
|
||||
expect(findCloseItem().exists()).toBe(true);
|
||||
expect(findReopenItem().exists()).toBe(false);
|
||||
expect(findCloseItem().attributes('href')).toBe(provideData.closeUrl);
|
||||
});
|
||||
|
||||
it('does not render if `isActive` is false', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findCloseItem().exists()).toBe(false);
|
||||
expect(findReopenItem().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('has correct class if `isDetailPage` is true', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
isDetailPage: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findCloseItem().attributes('class')).toContain('gl-sm-display-none!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('reopen item', () => {
|
||||
it('renders with correct values if `isActive` is set', () => {
|
||||
const provideData = {
|
||||
isActive: false,
|
||||
reopenUrl: '/my-reopen-url',
|
||||
};
|
||||
|
||||
createComponent({
|
||||
provideData,
|
||||
});
|
||||
|
||||
expect(findReopenItem().exists()).toBe(true);
|
||||
expect(findCloseItem().exists()).toBe(false);
|
||||
expect(findReopenItem().attributes('href')).toBe(provideData.reopenUrl);
|
||||
});
|
||||
|
||||
it('does not render if `isActive` is false', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findReopenItem().exists()).toBe(false);
|
||||
expect(findCloseItem().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('has correct class if `isDetailPage` is true', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
isActive: false,
|
||||
isDetailPage: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findReopenItem().attributes('class')).toContain('gl-sm-display-none!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete item', () => {
|
||||
const provideData = {
|
||||
issueCount: 1,
|
||||
mergeRequestCount: 2,
|
||||
milestoneId: 1,
|
||||
milestoneTitle: 'Milestone 1',
|
||||
milestoneUrl: '/milestone-url',
|
||||
};
|
||||
|
||||
it('renders with correct values', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findDeleteItem().exists()).toBe(true);
|
||||
expect(findDeleteMilestoneModal().props()).toMatchObject({
|
||||
visible: false,
|
||||
issueCount: provideData.issueCount,
|
||||
mergeRequestCount: provideData.mergeRequestCount,
|
||||
milestoneId: provideData.milestoneId,
|
||||
milestoneTitle: provideData.milestoneTitle,
|
||||
milestoneUrl: provideData.milestoneUrl,
|
||||
});
|
||||
});
|
||||
|
||||
it('click on delete opens confirm modal with correct props', async () => {
|
||||
createComponent();
|
||||
|
||||
expect(findDeleteMilestoneModal().props('visible')).toBe(false);
|
||||
|
||||
findDeleteItem().trigger('click');
|
||||
await nextTick();
|
||||
|
||||
expect(findDeleteMilestoneModal().props()).toMatchObject({
|
||||
visible: true,
|
||||
issueCount: provideData.issueCount,
|
||||
mergeRequestCount: provideData.mergeRequestCount,
|
||||
milestoneId: provideData.milestoneId,
|
||||
milestoneTitle: provideData.milestoneTitle,
|
||||
milestoneUrl: provideData.milestoneUrl,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('copy milestone id item', () => {
|
||||
it('renders copy milestone id with correct id', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
id: 22,
|
||||
},
|
||||
});
|
||||
|
||||
showDropdown();
|
||||
|
||||
expect(findMilestoneIdItem().text()).toBe('Copy milestone ID: 22');
|
||||
});
|
||||
|
||||
it('renders if `canReadMilestone` is true', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
canReadMilestone: true,
|
||||
},
|
||||
});
|
||||
expect(findMilestoneIdItem().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render if `canReadMilestone` is false', () => {
|
||||
createComponent({
|
||||
provideData: {
|
||||
canReadMilestone: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findMilestoneIdItem().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { GlModal } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { setHTMLFixture } from 'helpers/fixtures';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/alert';
|
||||
|
|
@ -16,44 +15,29 @@ describe('Promote milestone modal', () => {
|
|||
let wrapper;
|
||||
const milestoneMockData = {
|
||||
milestoneTitle: 'v1.0',
|
||||
url: `${TEST_HOST}/dummy/promote/milestones`,
|
||||
promoteUrl: `${TEST_HOST}/dummy/promote/milestones`,
|
||||
groupName: 'group',
|
||||
};
|
||||
|
||||
const promoteButton = () => document.querySelector('.js-promote-project-milestone-button');
|
||||
|
||||
beforeEach(() => {
|
||||
setHTMLFixture(`<button
|
||||
class="js-promote-project-milestone-button"
|
||||
data-group-name="${milestoneMockData.groupName}"
|
||||
data-milestone-title="${milestoneMockData.milestoneTitle}"
|
||||
data-url="${milestoneMockData.url}">
|
||||
Promote
|
||||
</button>`);
|
||||
wrapper = shallowMount(PromoteMilestoneModal);
|
||||
});
|
||||
|
||||
describe('Modal opener button', () => {
|
||||
it('button gets disabled when the modal opens', () => {
|
||||
expect(promoteButton().disabled).toBe(false);
|
||||
|
||||
promoteButton().click();
|
||||
|
||||
expect(promoteButton().disabled).toBe(true);
|
||||
const createComponent = ({ propsData = {} } = {}) => {
|
||||
wrapper = shallowMount(PromoteMilestoneModal, {
|
||||
propsData,
|
||||
stubs: {
|
||||
PromoteMilestoneModal,
|
||||
},
|
||||
});
|
||||
|
||||
it('button gets enabled when the modal closes', () => {
|
||||
promoteButton().click();
|
||||
|
||||
wrapper.findComponent(GlModal).vm.$emit('hide');
|
||||
|
||||
expect(promoteButton().disabled).toBe(false);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('Modal title and description', () => {
|
||||
beforeEach(() => {
|
||||
promoteButton().click();
|
||||
createComponent({
|
||||
propsData: {
|
||||
visible: true,
|
||||
milestoneTitle: milestoneMockData.milestoneTitle,
|
||||
promoteUrl: milestoneMockData.promoteUrl,
|
||||
groupName: milestoneMockData.groupName,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('contains the proper description', () => {
|
||||
|
|
@ -63,19 +47,28 @@ describe('Promote milestone modal', () => {
|
|||
});
|
||||
|
||||
it('contains the correct title', () => {
|
||||
expect(wrapper.vm.title).toBe('Promote v1.0 to group milestone?');
|
||||
expect(wrapper.vm.title).toBe(
|
||||
`Promote ${milestoneMockData.milestoneTitle} to group milestone?`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When requesting a milestone promotion', () => {
|
||||
beforeEach(() => {
|
||||
promoteButton().click();
|
||||
createComponent({
|
||||
propsData: {
|
||||
visible: true,
|
||||
milestoneTitle: milestoneMockData.milestoneTitle,
|
||||
promoteUrl: milestoneMockData.promoteUrl,
|
||||
groupName: milestoneMockData.groupName,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects when a milestone is promoted', async () => {
|
||||
const responseURL = `${TEST_HOST}/dummy/endpoint`;
|
||||
jest.spyOn(axios, 'post').mockImplementation((url) => {
|
||||
expect(url).toBe(milestoneMockData.url);
|
||||
expect(url).toBe(milestoneMockData.promoteUrl);
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
url: responseURL,
|
||||
|
|
@ -93,7 +86,7 @@ describe('Promote milestone modal', () => {
|
|||
const dummyError = new Error('promoting milestone failed');
|
||||
dummyError.response = { status: HTTP_STATUS_INTERNAL_SERVER_ERROR };
|
||||
jest.spyOn(axios, 'post').mockImplementation((url) => {
|
||||
expect(url).toBe(milestoneMockData.url);
|
||||
expect(url).toBe(milestoneMockData.promoteUrl);
|
||||
return Promise.reject(dummyError);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -397,6 +397,7 @@ describe('Package Files', () => {
|
|||
packageFilesQuery({
|
||||
files: [file],
|
||||
extendPagination: {
|
||||
hasPreviousPage: false,
|
||||
hasNextPage: false,
|
||||
},
|
||||
}),
|
||||
|
|
@ -422,6 +423,7 @@ describe('Package Files', () => {
|
|||
resolver: jest.fn().mockResolvedValue(
|
||||
packageFilesQuery({
|
||||
extendPagination: {
|
||||
hasPreviousPage: false,
|
||||
hasNextPage: false,
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -15,48 +15,5 @@ RSpec.describe Members::MemberApproval, feature_category: :groups_and_projects d
|
|||
it { is_expected.to validate_presence_of(:new_access_level) }
|
||||
it { is_expected.to validate_presence_of(:user) }
|
||||
it { is_expected.to validate_presence_of(:member_namespace) }
|
||||
|
||||
context 'when uniqness is enforced' do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:group) { create(:group) }
|
||||
let!(:member_approval) { create(:member_approval, user: user, member_namespace: group) }
|
||||
|
||||
context 'with same user, namespace, and access level and pending status' do
|
||||
let(:message) { 'A pending approval for the same user, namespace, and access level already exists.' }
|
||||
|
||||
it 'disallows on create' do
|
||||
duplicate_approval = build(:member_approval, user: user, member_namespace: group)
|
||||
|
||||
expect(duplicate_approval).not_to be_valid
|
||||
expect(duplicate_approval.errors[:base]).to include(message)
|
||||
end
|
||||
|
||||
it 'disallows on update' do
|
||||
duplicate_approval = create(:member_approval, user: user, member_namespace: group, status: :approved)
|
||||
expect(duplicate_approval).to be_valid
|
||||
|
||||
duplicate_approval.status = ::Members::MemberApproval.statuses[:pending]
|
||||
expect(duplicate_approval).not_to be_valid
|
||||
expect(duplicate_approval.errors[:base]).to include(message)
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows duplicate member approvals with different statuses' do
|
||||
member_approval.update!(status: ::Members::MemberApproval.statuses[:approved])
|
||||
|
||||
pending_approval = build(:member_approval, user: user, member_namespace: group)
|
||||
|
||||
expect(pending_approval).to be_valid
|
||||
end
|
||||
|
||||
it 'allows duplicate member approvals with different access levels' do
|
||||
different_approval = build(:member_approval,
|
||||
user: user,
|
||||
member_namespace: group,
|
||||
new_access_level: ::Gitlab::Access::MAINTAINER)
|
||||
|
||||
expect(different_approval).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -243,18 +243,6 @@ RSpec.describe Deployments::LinkMergeRequestsService, feature_category: :continu
|
|||
|
||||
expect(deploy.merge_requests).to match_array([merge_request_1, merge_request_2])
|
||||
end
|
||||
|
||||
context 'when :link_fast_forward_merge_requests_to_deployment FF is disabled' do
|
||||
before do
|
||||
stub_feature_flags(link_fast_forward_merge_requests_to_deployment: false)
|
||||
end
|
||||
|
||||
it 'does not link merge requests' do
|
||||
link_merge_requests_for_range
|
||||
|
||||
expect(deploy.merge_requests).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -574,16 +574,14 @@ RSpec.shared_examples 'work items iteration' do
|
|||
expect(page).to be_axe_clean.within(work_item_iteration_selector)
|
||||
end
|
||||
|
||||
# TODO, add test for automated accessibility after it is fixed in GlCollapsibleListBox
|
||||
# Invalid ARIA attribute value: aria-owns="listbox-##" when searchable
|
||||
# it 'passes axe automated accessibility testing in open state' do
|
||||
# within(work_item_iteration) do
|
||||
# click_button _('Edit')
|
||||
# wait_for_requests
|
||||
it 'passes axe automated accessibility testing in open state' do
|
||||
within(work_item_iteration_selector) do
|
||||
click_button _('Edit')
|
||||
wait_for_requests
|
||||
|
||||
# expect(page).to be_axe_clean.within(work_item_iteration)
|
||||
# end
|
||||
# end
|
||||
expect(page).to be_axe_clean.within(work_item_iteration_selector)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when edit is clicked' do
|
||||
|
|
|
|||
Loading…
Reference in New Issue