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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-06-10 00:10:34 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-06-10 00:10:34 +0300
commitf820d18e56f2bd63dd0f91b076ace59345a036a1 (patch)
tree93faf6cea37b703dc3cd01ef81b4b8d63f855582
parentd40d684afaec767bb05efdbeaa4ce3620cd337bb (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/branches/components/delete_branch_modal.vue19
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue6
-rw-r--r--app/assets/javascripts/security_configuration/components/redesigned_app.vue26
-rw-r--r--app/assets/javascripts/security_configuration/index.js11
-rw-r--r--app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue82
-rw-r--r--app/models/environment.rb1
-rw-r--r--doc/ci/variables/predefined_variables.md1
-rw-r--r--doc/user/application_security/dependency_scanning/analyzers.md2
-rw-r--r--doc/user/project/merge_requests/index.md29
-rw-r--r--locale/gitlab.pot3
-rw-r--r--package.json2
-rw-r--r--spec/frontend/branches/components/__snapshots__/delete_branch_modal_spec.js.snap67
-rw-r--r--spec/frontend/branches/components/delete_branch_button_spec.js30
-rw-r--r--spec/frontend/branches/components/delete_branch_modal_spec.js153
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js6
-rw-r--r--spec/frontend/security_configuration/components/redesigned_app_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js108
-rw-r--r--spec/models/ci/build_spec.rb6
-rw-r--r--yarn.lock8
19 files changed, 447 insertions, 139 deletions
diff --git a/app/assets/javascripts/branches/components/delete_branch_modal.vue b/app/assets/javascripts/branches/components/delete_branch_modal.vue
index 040346e56e3..14c2badeb3f 100644
--- a/app/assets/javascripts/branches/components/delete_branch_modal.vue
+++ b/app/assets/javascripts/branches/components/delete_branch_modal.vue
@@ -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
>
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
index 3f746731e34..b3c5af5418f 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
@@ -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">
diff --git a/app/assets/javascripts/security_configuration/components/redesigned_app.vue b/app/assets/javascripts/security_configuration/components/redesigned_app.vue
index 1d51fe14901..c2d57e8f0c8 100644
--- a/app/assets/javascripts/security_configuration/components/redesigned_app.vue
+++ b/app/assets/javascripts/security_configuration/components/redesigned_app.vue
@@ -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
diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js
index 6c4a01ea76b..e1dc6f24737 100644
--- a/app/assets/javascripts/security_configuration/index.js
+++ b/app/assets/javascripts/security_configuration/index.js
@@ -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']),
},
});
},
diff --git a/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue b/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue
new file mode 100644
index 00000000000..8fdc5ca78db
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue
@@ -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>
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 53ee90a1b67..558963c98c4 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -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)
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 595f843907c..fe168461a68 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -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. |
diff --git a/doc/user/application_security/dependency_scanning/analyzers.md b/doc/user/application_security/dependency_scanning/analyzers.md
index 0faa33e0123..fae0f457a20 100644
--- a/doc/user/application_security/dependency_scanning/analyzers.md
+++ b/doc/user/application_security/dependency_scanning/analyzers.md
@@ -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).
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 813c3d88538..b5c51c42ae9 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -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:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c3f328bde31..78a85532525 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -38033,9 +38033,6 @@ msgstr ""
msgid "cannot merge"
msgstr ""
-msgid "child-pipeline"
-msgstr ""
-
msgid "ciReport|%{degradedNum} degraded"
msgstr ""
diff --git a/package.json b/package.json
index 15a29b7bd36..d5de6a60924 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/spec/frontend/branches/components/__snapshots__/delete_branch_modal_spec.js.snap b/spec/frontend/branches/components/__snapshots__/delete_branch_modal_spec.js.snap
deleted file mode 100644
index 187ba4ba7f9..00000000000
--- a/spec/frontend/branches/components/__snapshots__/delete_branch_modal_spec.js.snap
+++ /dev/null
@@ -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>"
-`;
diff --git a/spec/frontend/branches/components/delete_branch_button_spec.js b/spec/frontend/branches/components/delete_branch_button_spec.js
index 515c8f0ec6f..acbc83a9bdc 100644
--- a/spec/frontend/branches/components/delete_branch_button_spec.js
+++ b/spec/frontend/branches/components/delete_branch_button_spec.js
@@ -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',
+ });
});
});
});
diff --git a/spec/frontend/branches/components/delete_branch_modal_spec.js b/spec/frontend/branches/components/delete_branch_modal_spec.js
index 62110af7e84..0c6111bda9e 100644
--- a/spec/frontend/branches/components/delete_branch_modal_spec.js
+++ b/spec/frontend/branches/components/delete_branch_modal_spec.js
@@ -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(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');
- expect(submitFormSpy).toHaveBeenCalled();
+ 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);
+
+ 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 });
- 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();
- });
+ expect(findModalMessage().html()).not.toContain(expectedUnmergedWarning);
});
});
});
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index 96f2cd1e371..c7d95526a0c 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -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);
diff --git a/spec/frontend/security_configuration/components/redesigned_app_spec.js b/spec/frontend/security_configuration/components/redesigned_app_spec.js
index a1da7e8584c..d55d8d183ce 100644
--- a/spec/frontend/security_configuration/components/redesigned_app_spec.js
+++ b/spec/frontend/security_configuration/components/redesigned_app_spec.js
@@ -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');
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js
new file mode 100644
index 00000000000..d58c87d66cb
--- /dev/null
+++ b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js
@@ -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([]);
+ });
+ });
+});
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 352badf4133..ed8487cac88 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -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
diff --git a/yarn.lock b/yarn.lock
index 2a1e03422c2..2100dad5b9a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"