Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-09 21:10:34 +00:00
parent d40d684afa
commit f820d18e56
19 changed files with 448 additions and 140 deletions

View file

@ -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
>

View file

@ -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">

View file

@ -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

View file

@ -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']),
},
});
},

View file

@ -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>

View file

@ -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)

View file

@ -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. |

View file

@ -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).

View file

@ -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:

View file

@ -38033,9 +38033,6 @@ msgstr ""
msgid "cannot merge"
msgstr ""
msgid "child-pipeline"
msgstr ""
msgid "ciReport|%{degradedNum} degraded"
msgstr ""

View file

@ -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",

View file

@ -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 hasnt 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 hasnt 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>"
`;

View file

@ -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',
});
});
});
});

View file

@ -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 hasnt 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);
});
});
});

View file

@ -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);

View file

@ -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');
});
});
});

View file

@ -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([]);
});
});
});

View file

@ -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

View file

@ -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"