Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d40d684afa
commit
f820d18e56
19 changed files with 448 additions and 140 deletions
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { GlButton, GlFormInput, GlModal, GlSprintf, GlAlert } from '@gitlab/ui';
|
||||
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
|
@ -16,7 +15,6 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
isProtectedBranch: false,
|
||||
branchName: '',
|
||||
defaultBranchName: '',
|
||||
|
@ -69,9 +67,10 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
eventHub.$on('openModal', (options) => {
|
||||
this.openModal(options);
|
||||
});
|
||||
eventHub.$on('openModal', this.openModal);
|
||||
},
|
||||
destroyed() {
|
||||
eventHub.$off('openModal', this.openModal);
|
||||
},
|
||||
methods: {
|
||||
openModal({ isProtectedBranch, branchName, defaultBranchName, deletePath, merged }) {
|
||||
|
@ -81,13 +80,13 @@ export default {
|
|||
this.deletePath = deletePath;
|
||||
this.merged = merged;
|
||||
|
||||
this.$root.$emit(BV_SHOW_MODAL, this.modalId);
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.form.submit();
|
||||
},
|
||||
closeModal() {
|
||||
this.$root.$emit(BV_HIDE_MODAL, this.modalId);
|
||||
this.$refs.modal.hide();
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
|
@ -117,7 +116,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-modal :visible="visible" size="sm" :modal-id="modalId" :title="title">
|
||||
<gl-modal ref="modal" size="sm" :modal-id="modalId" :title="title">
|
||||
<gl-alert class="gl-mb-5" variant="danger" :dismissible="false">
|
||||
<div data-testid="modal-message">
|
||||
<gl-sprintf :message="message">
|
||||
|
@ -175,7 +174,7 @@ export default {
|
|||
|
||||
<template #modal-footer>
|
||||
<div class="gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0">
|
||||
<gl-button @click="closeModal">
|
||||
<gl-button data-testid="delete-branch-cancel-button" @click="closeModal">
|
||||
{{ $options.i18n.cancelButtonText }}
|
||||
</gl-button>
|
||||
<div class="gl-mr-3"></div>
|
||||
|
@ -184,7 +183,7 @@ export default {
|
|||
:disabled="deleteButtonDisabled"
|
||||
variant="danger"
|
||||
data-qa-selector="delete_branch_confirmation_button"
|
||||
data-testid="delete_branch_confirmation_button"
|
||||
data-testid="delete-branch-confirmation-button"
|
||||
@click="submitForm"
|
||||
>{{ buttonText }}</gl-button
|
||||
>
|
||||
|
|
|
@ -58,7 +58,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
tooltipText() {
|
||||
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label}
|
||||
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label} -
|
||||
${this.sourceJobInfo}`;
|
||||
},
|
||||
buttonId() {
|
||||
|
@ -71,7 +71,7 @@ export default {
|
|||
return this.pipeline.project.name;
|
||||
},
|
||||
downstreamTitle() {
|
||||
return this.childPipeline ? __('child-pipeline') : this.pipeline.project.name;
|
||||
return this.childPipeline ? this.sourceJobName : this.pipeline.project.name;
|
||||
},
|
||||
parentPipeline() {
|
||||
return this.isUpstream && this.isSameProject;
|
||||
|
@ -163,7 +163,7 @@ export default {
|
|||
/>
|
||||
<div v-else class="gl-pr-2"><gl-loading-icon inline /></div>
|
||||
<div class="gl-display-flex gl-flex-direction-column gl-w-13">
|
||||
<span class="gl-text-truncate">
|
||||
<span class="gl-text-truncate" data-testid="downstream-title">
|
||||
{{ downstreamTitle }}
|
||||
</span>
|
||||
<div class="gl-text-truncate">
|
||||
|
|
|
@ -35,13 +35,27 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
gitlabCiPresent: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
gitlabCiHistoryPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
latestPipelinePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
canViewCiHistory() {
|
||||
return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -66,6 +80,11 @@ export default {
|
|||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
<p v-if="canViewCiHistory">
|
||||
<gl-link data-testid="security-view-history-link" :href="gitlabCiHistoryPath">{{
|
||||
$options.i18n.configurationHistory
|
||||
}}</gl-link>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template #features>
|
||||
|
@ -92,6 +111,11 @@ export default {
|
|||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
<p v-if="canViewCiHistory">
|
||||
<gl-link data-testid="compliance-view-history-link" :href="gitlabCiHistoryPath">{{
|
||||
$options.i18n.configurationHistory
|
||||
}}</gl-link>
|
||||
</p>
|
||||
</template>
|
||||
<template #features>
|
||||
<feature-card
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils';
|
||||
import SecurityConfigurationApp from './components/app.vue';
|
||||
import { securityFeatures, complianceFeatures } from './components/constants';
|
||||
import RedesignedSecurityConfigurationApp from './components/redesigned_app.vue';
|
||||
|
@ -17,7 +18,13 @@ export const initStaticSecurityConfiguration = (el) => {
|
|||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
const { projectPath, upgradePath, features, latestPipelinePath } = el.dataset;
|
||||
const {
|
||||
projectPath,
|
||||
upgradePath,
|
||||
features,
|
||||
latestPipelinePath,
|
||||
gitlabCiHistoryPath,
|
||||
} = el.dataset;
|
||||
|
||||
if (gon.features.securityConfigurationRedesign) {
|
||||
const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures(
|
||||
|
@ -39,6 +46,8 @@ export const initStaticSecurityConfiguration = (el) => {
|
|||
augmentedComplianceFeatures,
|
||||
augmentedSecurityFeatures,
|
||||
latestPipelinePath,
|
||||
gitlabCiHistoryPath,
|
||||
...parseBooleanDataAttributes(el, ['gitlabCiPresent']),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<script>
|
||||
import { reportTypeToSecurityReportTypeEnum } from 'ee_else_ce/vue_shared/security_reports/constants';
|
||||
import createFlash from '~/flash';
|
||||
import { s__ } from '~/locale';
|
||||
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
|
||||
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql';
|
||||
import { extractSecurityReportArtifactsFromMergeRequest } from '~/vue_shared/security_reports/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SecurityReportDownloadDropdown,
|
||||
},
|
||||
props: {
|
||||
reportTypes: {
|
||||
type: Array,
|
||||
required: true,
|
||||
validator: (reportType) => {
|
||||
return reportType.every((report) => reportTypeToSecurityReportTypeEnum[report]);
|
||||
},
|
||||
},
|
||||
targetProjectFullPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
mrIid: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reportArtifacts: [],
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
reportArtifacts: {
|
||||
query: securityReportMergeRequestDownloadPathsQuery,
|
||||
variables() {
|
||||
return {
|
||||
projectPath: this.targetProjectFullPath,
|
||||
iid: String(this.mrIid),
|
||||
reportTypes: this.reportTypes.map(
|
||||
(reportType) => reportTypeToSecurityReportTypeEnum[reportType],
|
||||
),
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return extractSecurityReportArtifactsFromMergeRequest(this.reportTypes, data);
|
||||
},
|
||||
error(error) {
|
||||
this.showError(error);
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isLoadingReportArtifacts() {
|
||||
return this.$apollo.queries.reportArtifacts.loading;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showError(error) {
|
||||
createFlash({
|
||||
message: this.$options.i18n.apiError,
|
||||
captureError: true,
|
||||
error,
|
||||
});
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
apiError: s__(
|
||||
'SecurityReports|Failed to get security report information. Please reload the page or try again later.',
|
||||
),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<security-report-download-dropdown
|
||||
:artifacts="reportArtifacts"
|
||||
:loading="isLoadingReportArtifacts"
|
||||
/>
|
||||
</template>
|
|
@ -223,6 +223,7 @@ class Environment < ApplicationRecord
|
|||
Gitlab::Ci::Variables::Collection.new
|
||||
.append(key: 'CI_ENVIRONMENT_NAME', value: name)
|
||||
.append(key: 'CI_ENVIRONMENT_SLUG', value: slug)
|
||||
.append(key: 'CI_ENVIRONMENT_TIER', value: tier)
|
||||
end
|
||||
|
||||
def recently_updated_on_branch?(ref)
|
||||
|
|
|
@ -53,6 +53,7 @@ There are also [Kubernetes-specific deployment variables](../../user/project/clu
|
|||
| `CI_ENVIRONMENT_SLUG` | 8.15 | all | The simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, and so on. Available if [`environment:name`](../yaml/README.md#environmentname) is set. The slug is [truncated to 24 characters](https://gitlab.com/gitlab-org/gitlab/-/issues/20941). |
|
||||
| `CI_ENVIRONMENT_URL` | 9.3 | all | The URL of the environment for this job. Available if [`environment:url`](../yaml/README.md#environmenturl) is set. |
|
||||
| `CI_ENVIRONMENT_ACTION` | 13.11 | all | The action annotation specified for this job's environment. Available if [`environment:action`](../yaml/README.md#environmentaction) is set. Can be `start`, `prepare`, or `stop`. |
|
||||
| `CI_ENVIRONMENT_TIER` | 14.0 | all | The [deployment tier of the environment](../environments/index.md#deployment-tier-of-environments) for this job. |
|
||||
| `CI_HAS_OPEN_REQUIREMENTS` | 13.1 | all | Only available if the pipeline's project has an open [requirement](../../user/project/requirements/index.md). `true` when available. |
|
||||
| `CI_JOB_ID` | 9.0 | all | The internal ID of the job, unique across all jobs in the GitLab instance. |
|
||||
| `CI_JOB_IMAGE` | 12.9 | 12.9 | The name of the Docker image running the job. |
|
||||
|
|
|
@ -80,7 +80,7 @@ include:
|
|||
template: Dependency-Scanning.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
DS_EXCLUDED_ANALYZERS: "gemnasium, gemansium-maven, gemnasium-python, bundler-audit, retire.js"
|
||||
DS_EXCLUDED_ANALYZERS: "gemnasium, gemnasium-maven, gemnasium-python, bundler-audit, retire.js"
|
||||
```
|
||||
|
||||
This is used when one totally relies on [custom analyzers](#custom-analyzers).
|
||||
|
|
|
@ -76,6 +76,35 @@ can assign, categorize, and track progress on a merge request:
|
|||
- [**Notifications**](../../profile/notifications.md): A toggle to select whether
|
||||
or not to receive notifications for updates to a merge request.
|
||||
|
||||
## Close a merge request
|
||||
|
||||
If you decide to permanently stop work on a merge request,
|
||||
GitLab recommends you close the merge request rather than
|
||||
[delete it](#delete-a-merge-request). Users with
|
||||
Developer, Maintainer, or Owner [roles](../../permissions.md) in a project
|
||||
can close merge requests in the project:
|
||||
|
||||
1. Go to the merge request you want to close.
|
||||
1. Scroll to the comment box at the bottom of the page.
|
||||
1. Following the comment box, select **Close merge request**.
|
||||
|
||||
GitLab closes the merge request, but preserves records of the merge request,
|
||||
its comments, and any associated pipelines.
|
||||
|
||||
### Delete a merge request
|
||||
|
||||
GitLab recommends you close, rather than delete, merge requests.
|
||||
|
||||
WARNING:
|
||||
You cannot undo the deletion of a merge request.
|
||||
|
||||
To delete a merge request:
|
||||
|
||||
1. Sign in to GitLab as a user with the project Owner [role](../../permissions.md).
|
||||
Only users with this role can delete merge requests in a project.
|
||||
1. Go to the merge request you want to delete, and select **Edit**.
|
||||
1. Scroll to the bottom of the page, and select **Delete merge request**.
|
||||
|
||||
## Merge request workflows
|
||||
|
||||
For a software developer working in a team:
|
||||
|
|
|
@ -38033,9 +38033,6 @@ msgstr ""
|
|||
msgid "cannot merge"
|
||||
msgstr ""
|
||||
|
||||
msgid "child-pipeline"
|
||||
msgstr ""
|
||||
|
||||
msgid "ciReport|%{degradedNum} degraded"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "1.199.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "29.33.0",
|
||||
"@gitlab/ui": "29.34.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "6.1.3-2",
|
||||
"@rails/ujs": "6.1.3-2",
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Delete branch modal Deleting a protected branch (for owner or maintainer) renders the modal correctly 1`] = `
|
||||
"<div visible=\\"visible\\">
|
||||
<gl-alert-stub title=\\"\\" dismisslabel=\\"Dismiss\\" variant=\\"danger\\" primarybuttonlink=\\"\\" primarybuttontext=\\"\\" secondarybuttonlink=\\"\\" secondarybuttontext=\\"\\" class=\\"gl-mb-5\\">
|
||||
<div data-testid=\\"modal-message\\">
|
||||
<gl-sprintf-stub message=\\"You're about to permanently delete the protected branch %{strongStart}test_modal.%{strongEnd}\\"></gl-sprintf-stub>
|
||||
<p class=\\"gl-mb-0 gl-mt-4\\">
|
||||
This branch hasn’t been merged into default. To avoid data loss, consider merging this branch before deleting it.
|
||||
</p>
|
||||
</div>
|
||||
</gl-alert-stub>
|
||||
<form action=\\"/path/to/branch\\" method=\\"post\\">
|
||||
<div class=\\"gl-mt-4\\">
|
||||
<p>
|
||||
<gl-sprintf-stub message=\\"Once you confirm and press %{strongStart}Yes, delete protected branch,%{strongEnd} it cannot be undone or recovered.\\"></gl-sprintf-stub>
|
||||
</p>
|
||||
<p>
|
||||
<gl-sprintf-stub message=\\"Please type the following to confirm:\\"></gl-sprintf-stub> <code class=\\"gl-white-space-pre-wrap\\"> test_modal </code>
|
||||
<b-form-input-stub name=\\"delete_branch_input\\" value=\\"\\" autocomplete=\\"off\\" debounce=\\"0\\" type=\\"text\\" aria-labelledby=\\"input-label\\" class=\\"gl-form-input gl-mt-4\\"></b-form-input-stub>
|
||||
</p>
|
||||
</div> <input type=\\"hidden\\" name=\\"_method\\" value=\\"delete\\"> <input type=\\"hidden\\" name=\\"authenticity_token\\">
|
||||
</form>
|
||||
<div class=\\"gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0\\">
|
||||
<b-button-stub size=\\"md\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" class=\\"gl-button\\">
|
||||
<!---->
|
||||
<!----> <span class=\\"gl-button-text\\">
|
||||
Cancel, keep branch
|
||||
</span></b-button-stub>
|
||||
<div class=\\"gl-mr-3\\"></div>
|
||||
<b-button-stub disabled=\\"true\\" size=\\"md\\" variant=\\"danger\\" type=\\"button\\" tag=\\"button\\" data-qa-selector=\\"delete_branch_confirmation_button\\" data-testid=\\"delete_branch_confirmation_button\\" class=\\"gl-button\\">
|
||||
<!---->
|
||||
<!----> <span class=\\"gl-button-text\\">Yes, delete protected branch</span></b-button-stub>
|
||||
</div>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`Delete branch modal Deleting a regular branch renders the modal correctly 1`] = `
|
||||
"<div visible=\\"visible\\">
|
||||
<gl-alert-stub title=\\"\\" dismisslabel=\\"Dismiss\\" variant=\\"danger\\" primarybuttonlink=\\"\\" primarybuttontext=\\"\\" secondarybuttonlink=\\"\\" secondarybuttontext=\\"\\" class=\\"gl-mb-5\\">
|
||||
<div data-testid=\\"modal-message\\">
|
||||
<gl-sprintf-stub message=\\"You're about to permanently delete the branch %{strongStart}test_modal.%{strongEnd}\\"></gl-sprintf-stub>
|
||||
<p class=\\"gl-mb-0 gl-mt-4\\">
|
||||
This branch hasn’t been merged into default. To avoid data loss, consider merging this branch before deleting it.
|
||||
</p>
|
||||
</div>
|
||||
</gl-alert-stub>
|
||||
<form action=\\"/path/to/branch\\" method=\\"post\\">
|
||||
<div>
|
||||
<p class=\\"gl-mt-4\\">
|
||||
<gl-sprintf-stub message=\\"Deleting the %{strongStart}test_modal%{strongEnd} branch cannot be undone. Are you sure?\\"></gl-sprintf-stub>
|
||||
</p>
|
||||
</div> <input type=\\"hidden\\" name=\\"_method\\" value=\\"delete\\"> <input type=\\"hidden\\" name=\\"authenticity_token\\">
|
||||
</form>
|
||||
<div class=\\"gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0\\">
|
||||
<b-button-stub size=\\"md\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" class=\\"gl-button\\">
|
||||
<!---->
|
||||
<!----> <span class=\\"gl-button-text\\">
|
||||
Cancel, keep branch
|
||||
</span></b-button-stub>
|
||||
<div class=\\"gl-mr-3\\"></div>
|
||||
<b-button-stub size=\\"md\\" variant=\\"danger\\" type=\\"button\\" tag=\\"button\\" data-qa-selector=\\"delete_branch_confirmation_button\\" data-testid=\\"delete_branch_confirmation_button\\" class=\\"gl-button\\">
|
||||
<!---->
|
||||
<!----> <span class=\\"gl-button-text\\">Yes, delete branch</span></b-button-stub>
|
||||
</div>
|
||||
</div>"
|
||||
`;
|
|
@ -29,7 +29,7 @@ describe('Delete branch button', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders the button with correct tooltip, style, and icon', () => {
|
||||
it('renders the button with default tooltip, style, and icon', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findDeleteButton().attributes()).toMatchObject({
|
||||
|
@ -42,7 +42,20 @@ describe('Delete branch button', () => {
|
|||
it('renders a different tooltip for a protected branch', () => {
|
||||
createComponent({ isProtectedBranch: true });
|
||||
|
||||
expect(findDeleteButton().attributes('title')).toBe('Delete protected branch');
|
||||
expect(findDeleteButton().attributes()).toMatchObject({
|
||||
title: 'Delete protected branch',
|
||||
variant: 'danger',
|
||||
icon: 'remove',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a different protected tooltip when it is both protected and disabled', () => {
|
||||
createComponent({ isProtectedBranch: true, disabled: true });
|
||||
|
||||
expect(findDeleteButton().attributes()).toMatchObject({
|
||||
title: 'Only a project maintainer or owner can delete a protected branch',
|
||||
variant: 'default',
|
||||
});
|
||||
});
|
||||
|
||||
it('emits the data to eventHub when button is clicked', () => {
|
||||
|
@ -63,14 +76,21 @@ describe('Delete branch button', () => {
|
|||
it('does not disable the button by default when mounted', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findDeleteButton().attributes('disabled')).not.toBe('true');
|
||||
expect(findDeleteButton().attributes()).toMatchObject({
|
||||
title: 'Delete branch',
|
||||
variant: 'danger',
|
||||
});
|
||||
});
|
||||
|
||||
// Used for unallowed users and for the default branch.
|
||||
it('disables the button when mounted for a disabled modal', () => {
|
||||
createComponent({ disabled: true });
|
||||
createComponent({ disabled: true, tooltip: 'The default branch cannot be deleted' });
|
||||
|
||||
expect(findDeleteButton().attributes('disabled')).toBe('true');
|
||||
expect(findDeleteButton().attributes()).toMatchObject({
|
||||
title: 'The default branch cannot be deleted',
|
||||
disabled: 'true',
|
||||
variant: 'default',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,86 +1,157 @@
|
|||
import { GlButton, GlModal, GlFormInput } from '@gitlab/ui';
|
||||
import { GlButton, GlModal, GlFormInput, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import DeleteBranchModal from '~/branches/components/delete_branch_modal.vue';
|
||||
import eventHub from '~/branches/event_hub';
|
||||
|
||||
let wrapper;
|
||||
|
||||
const branchName = 'test_modal';
|
||||
const defaultBranchName = 'default';
|
||||
const deletePath = '/path/to/branch';
|
||||
const merged = false;
|
||||
const isProtectedBranch = false;
|
||||
|
||||
const createComponent = (data = {}) => {
|
||||
wrapper = shallowMount(DeleteBranchModal, {
|
||||
data() {
|
||||
return {
|
||||
branchName,
|
||||
deletePath: '/path/to/branch',
|
||||
defaultBranchName: 'default',
|
||||
...data,
|
||||
};
|
||||
},
|
||||
attrs: {
|
||||
visible: true,
|
||||
},
|
||||
stubs: {
|
||||
GlModal: stubComponent(GlModal, {
|
||||
template:
|
||||
'<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
|
||||
}),
|
||||
GlButton,
|
||||
GlFormInput,
|
||||
},
|
||||
});
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(DeleteBranchModal, {
|
||||
data() {
|
||||
return {
|
||||
branchName,
|
||||
deletePath,
|
||||
defaultBranchName,
|
||||
merged,
|
||||
isProtectedBranch,
|
||||
...data,
|
||||
};
|
||||
},
|
||||
stubs: {
|
||||
GlModal: stubComponent(GlModal, {
|
||||
template:
|
||||
'<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
|
||||
}),
|
||||
GlButton,
|
||||
GlFormInput,
|
||||
GlSprintf,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const findDeleteButton = () => wrapper.find('[data-testid="delete_branch_confirmation_button"]');
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findModalMessage = () => wrapper.findByTestId('modal-message');
|
||||
const findDeleteButton = () => wrapper.findByTestId('delete-branch-confirmation-button');
|
||||
const findCancelButton = () => wrapper.findByTestId('delete-branch-cancel-button');
|
||||
const findFormInput = () => wrapper.findComponent(GlFormInput);
|
||||
const findForm = () => wrapper.find('form');
|
||||
|
||||
describe('Delete branch modal', () => {
|
||||
const expectedUnmergedWarning =
|
||||
'This branch hasn’t been merged into default. To avoid data loss, consider merging this branch before deleting it.';
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('Deleting a regular branch', () => {
|
||||
const expectedTitle = 'Delete branch. Are you ABSOLUTELY SURE?';
|
||||
const expectedWarning = "You're about to permanently delete the branch test_modal.";
|
||||
const expectedMessage = `${expectedWarning} ${expectedUnmergedWarning}`;
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders the modal correctly', () => {
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
expect(findModal().props('title')).toBe(expectedTitle);
|
||||
expect(findModalMessage().text()).toMatchInterpolatedText(expectedMessage);
|
||||
expect(findCancelButton().text()).toBe('Cancel, keep branch');
|
||||
expect(findDeleteButton().text()).toBe('Yes, delete branch');
|
||||
expect(findForm().attributes('action')).toBe(deletePath);
|
||||
});
|
||||
|
||||
it('submits the form when clicked', () => {
|
||||
it('submits the form when the delete button is clicked', () => {
|
||||
const submitFormSpy = jest.spyOn(wrapper.vm.$refs.form, 'submit');
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
findDeleteButton().trigger('click');
|
||||
findDeleteButton().trigger('click');
|
||||
|
||||
expect(submitFormSpy).toHaveBeenCalled();
|
||||
expect(findForm().attributes('action')).toBe(deletePath);
|
||||
expect(submitFormSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls show on the modal when a `openModal` event is received through the event hub', async () => {
|
||||
const showSpy = jest.spyOn(wrapper.vm.$refs.modal, 'show');
|
||||
|
||||
eventHub.$emit('openModal', {
|
||||
isProtectedBranch,
|
||||
branchName,
|
||||
defaultBranchName,
|
||||
deletePath,
|
||||
merged,
|
||||
});
|
||||
|
||||
expect(showSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls hide on the modal when cancel button is clicked', () => {
|
||||
const closeModalSpy = jest.spyOn(wrapper.vm.$refs.modal, 'hide');
|
||||
|
||||
findCancelButton().trigger('click');
|
||||
|
||||
expect(closeModalSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Deleting a protected branch (for owner or maintainer)', () => {
|
||||
const expectedTitleProtected = 'Delete protected branch. Are you ABSOLUTELY SURE?';
|
||||
const expectedWarningProtected =
|
||||
"You're about to permanently delete the protected branch test_modal.";
|
||||
const expectedMessageProtected = `${expectedWarningProtected} ${expectedUnmergedWarning}`;
|
||||
const expectedConfirmationText =
|
||||
'Once you confirm and press Yes, delete protected branch, it cannot be undone or recovered. Please type the following to confirm: test_modal';
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({ isProtectedBranch: true });
|
||||
});
|
||||
|
||||
it('renders the modal correctly', () => {
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
describe('rendering the modal correctly for a protected branch', () => {
|
||||
it('sets the modal title for a protected branch', () => {
|
||||
expect(findModal().props('title')).toBe(expectedTitleProtected);
|
||||
});
|
||||
|
||||
it('renders the correct text in the modal message', () => {
|
||||
expect(findModalMessage().text()).toMatchInterpolatedText(expectedMessageProtected);
|
||||
});
|
||||
|
||||
it('renders the protected branch name confirmation form with expected text and action', () => {
|
||||
expect(findForm().text()).toMatchInterpolatedText(expectedConfirmationText);
|
||||
expect(findForm().attributes('action')).toBe(deletePath);
|
||||
});
|
||||
|
||||
it('renders the buttons with the correct button text', () => {
|
||||
expect(findCancelButton().text()).toBe('Cancel, keep branch');
|
||||
expect(findDeleteButton().text()).toBe('Yes, delete protected branch');
|
||||
});
|
||||
});
|
||||
|
||||
it('disables the delete button when branch name input is unconfirmed', () => {
|
||||
expect(findDeleteButton().attributes('disabled')).toBe('true');
|
||||
});
|
||||
it('opens with the delete button disabled and enables it when branch name is confirmed', async () => {
|
||||
expect(findDeleteButton().props('disabled')).toBe(true);
|
||||
|
||||
it('enables the delete button when branch name input is confirmed', () => {
|
||||
return wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
findFormInput().vm.$emit('input', branchName);
|
||||
})
|
||||
.then(() => {
|
||||
expect(findDeleteButton()).not.toBeDisabled();
|
||||
});
|
||||
findFormInput().vm.$emit('input', branchName);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findDeleteButton().props('disabled')).not.toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Deleting a merged branch', () => {
|
||||
it('does not include the unmerged branch warning when merged is true', () => {
|
||||
createComponent({ merged: true });
|
||||
|
||||
expect(findModalMessage().html()).not.toContain(expectedUnmergedWarning);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ describe('Linked pipeline', () => {
|
|||
let wrapper;
|
||||
|
||||
const findButton = () => wrapper.find(GlButton);
|
||||
const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]');
|
||||
const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
|
||||
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
|
||||
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
||||
|
@ -119,6 +120,11 @@ describe('Linked pipeline', () => {
|
|||
expect(findPipelineLabel().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have the name of the trigger job on the card when it is a child pipeline', () => {
|
||||
createWrapper(downstreamProps);
|
||||
expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.source_job.name);
|
||||
});
|
||||
|
||||
it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
|
||||
createWrapper(upstreamProps);
|
||||
expect(findPipelineLabel().exists()).toBe(true);
|
||||
|
|
|
@ -36,6 +36,8 @@ describe('redesigned App component', () => {
|
|||
const findTabs = () => wrapper.findAllComponents(GlTab);
|
||||
const findByTestId = (id) => wrapper.findByTestId(id);
|
||||
const findFeatureCards = () => wrapper.findAllComponents(FeatureCard);
|
||||
const findComplianceViewHistoryLink = () => findByTestId('compliance-view-history-link');
|
||||
const findSecurityViewHistoryLink = () => findByTestId('security-view-history-link');
|
||||
|
||||
const securityFeaturesMock = [
|
||||
{
|
||||
|
@ -103,6 +105,11 @@ describe('redesigned App component', () => {
|
|||
it('should not show latest pipeline link when latestPipelinePath is not defined', () => {
|
||||
expect(findByTestId('latest-pipeline-info').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not show configuration History Link when gitlabCiPresent & gitlabCiHistoryPath are not defined', () => {
|
||||
expect(findComplianceViewHistoryLink().exists()).toBe(false);
|
||||
expect(findSecurityViewHistoryLink().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when given latestPipelinePath props', () => {
|
||||
|
@ -134,4 +141,23 @@ describe('redesigned App component', () => {
|
|||
expect(latestPipelineInfoCompliance.find('a').attributes('href')).toBe('test/path');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given gitlabCiPresent & gitlabCiHistoryPath props', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
augmentedSecurityFeatures: securityFeaturesMock,
|
||||
augmentedComplianceFeatures: complianceFeaturesMock,
|
||||
gitlabCiPresent: true,
|
||||
gitlabCiHistoryPath: 'test/historyPath',
|
||||
});
|
||||
});
|
||||
|
||||
it('should show configuration History Link', () => {
|
||||
expect(findComplianceViewHistoryLink().exists()).toBe(true);
|
||||
expect(findSecurityViewHistoryLink().exists()).toBe(true);
|
||||
|
||||
expect(findComplianceViewHistoryLink().attributes('href')).toBe('test/historyPath');
|
||||
expect(findSecurityViewHistoryLink().attributes('href')).toBe('test/historyPath');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import {
|
||||
expectedDownloadDropdownProps,
|
||||
securityReportMergeRequestDownloadPathsQueryResponse,
|
||||
} from 'jest/vue_shared/security_reports/mock_data';
|
||||
import createFlash from '~/flash';
|
||||
import Component from '~/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue';
|
||||
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
|
||||
import {
|
||||
REPORT_TYPE_SAST,
|
||||
REPORT_TYPE_SECRET_DETECTION,
|
||||
} from '~/vue_shared/security_reports/constants';
|
||||
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
describe('Merge request artifact Download', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
reportTypes: [REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION],
|
||||
targetProjectFullPath: '/path',
|
||||
mrIid: 123,
|
||||
};
|
||||
|
||||
const createWrapper = ({ propsData, options }) => {
|
||||
wrapper = shallowMount(Component, {
|
||||
stubs: {
|
||||
SecurityReportDownloadDropdown,
|
||||
},
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...propsData,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
const pendingHandler = () => new Promise(() => {});
|
||||
const successHandler = () =>
|
||||
Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryResponse });
|
||||
const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] });
|
||||
const createMockApolloProvider = (handler) => {
|
||||
Vue.use(VueApollo);
|
||||
const requestHandlers = [[securityReportMergeRequestDownloadPathsQuery, handler]];
|
||||
|
||||
return createMockApollo(requestHandlers);
|
||||
};
|
||||
|
||||
const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown);
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('given the query is loading', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
options: {
|
||||
apolloProvider: createMockApolloProvider(pendingHandler),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('loading is true', () => {
|
||||
expect(findDownloadDropdown().props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the query loads successfully', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
options: {
|
||||
apolloProvider: createMockApolloProvider(successHandler),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the download dropdown', () => {
|
||||
expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps);
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the query fails', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
options: {
|
||||
apolloProvider: createMockApolloProvider(failureHandler),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createFlash correctly', () => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: Component.i18n.apiError,
|
||||
captureError: true,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
});
|
||||
|
||||
it('renders nothing', () => {
|
||||
expect(findDownloadDropdown().props('artifacts')).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2720,7 +2720,7 @@ RSpec.describe Ci::Build do
|
|||
let(:expected_variables) do
|
||||
predefined_variables.map { |variable| variable.fetch(:key) } +
|
||||
%w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG
|
||||
CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL]
|
||||
CI_ENVIRONMENT_TIER CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL]
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -2820,7 +2820,8 @@ RSpec.describe Ci::Build do
|
|||
let(:environment_variables) do
|
||||
[
|
||||
{ key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false },
|
||||
{ key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false }
|
||||
{ key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false },
|
||||
{ key: 'CI_ENVIRONMENT_TIER', value: 'production', public: true, masked: false }
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -2829,6 +2830,7 @@ RSpec.describe Ci::Build do
|
|||
project: build.project,
|
||||
name: 'production',
|
||||
slug: 'prod-slug',
|
||||
tier: 'production',
|
||||
external_url: '')
|
||||
end
|
||||
|
||||
|
|
|
@ -908,10 +908,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
||||
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
||||
|
||||
"@gitlab/ui@29.33.0":
|
||||
version "29.33.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.33.0.tgz#2fb06dfe95f86dff6840bcb097ab1a2fd640a0ce"
|
||||
integrity sha512-WhmJu3vaacBzIPOtKS0uD8htp8L6gjKXfqgWedu/Ukncs02OvlhAGy9CC4SHSTMhjYSMkQIGHrFvBPIW2DPEOg==
|
||||
"@gitlab/ui@29.34.0":
|
||||
version "29.34.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-29.34.0.tgz#c8e9d7411f98537d3153d99b6c614583d4d1285d"
|
||||
integrity sha512-ukEHnvd+4f9M+K4b5EArVygUDbS+kUcsP94f6I7Rvd95TR/LlJXwf6vtFdgdBNbk8W98AzW9GwdlT4hmgWfRdw==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
|
|
Loading…
Reference in a new issue