Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-06 12:08:32 +00:00
parent 900a0a5a4a
commit 2e31a394c5
48 changed files with 325 additions and 657 deletions

View file

@ -829,7 +829,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/app/assets/javascripts/runner/components/registration/registration_token.vue @gitlab-org/manage/authentication-and-authorization
/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue @gitlab-org/manage/authentication-and-authorization
/app/assets/javascripts/runner/components/search_tokens/ @gitlab-org/manage/authentication-and-authorization
/app/assets/javascripts/static_site_editor/rich_content_editor/services/renderers/build_uneditable_token.js @gitlab-org/manage/authentication-and-authorization
/app/assets/javascripts/token_access/components/ @gitlab-org/manage/authentication-and-authorization
/app/assets/javascripts/token_access/index.js @gitlab-org/manage/authentication-and-authorization
/app/assets/stylesheets/page_bundles/profile_two_factor_auth.scss @gitlab-org/manage/authentication-and-authorization

View file

@ -1383,7 +1383,6 @@ RSpec/ContextWording:
- 'spec/controllers/projects/settings/repository_controller_spec.rb'
- 'spec/controllers/projects/snippets_controller_spec.rb'
- 'spec/controllers/projects/starrers_controller_spec.rb'
- 'spec/controllers/projects/static_site_editor_controller_spec.rb'
- 'spec/controllers/projects/tags_controller_spec.rb'
- 'spec/controllers/projects/todos_controller_spec.rb'
- 'spec/controllers/projects/tree_controller_spec.rb'
@ -2511,7 +2510,6 @@ RSpec/ContextWording:
- 'spec/lib/gitlab/spamcheck/client_spec.rb'
- 'spec/lib/gitlab/sql/pattern_spec.rb'
- 'spec/lib/gitlab/ssh_public_key_spec.rb'
- 'spec/lib/gitlab/static_site_editor/config/file_config/entry/mount_spec.rb'
- 'spec/lib/gitlab/submodule_links_spec.rb'
- 'spec/lib/gitlab/subscription_portal_spec.rb'
- 'spec/lib/gitlab/suggestions/commit_message_spec.rb'
@ -3513,7 +3511,6 @@ RSpec/ContextWording:
- 'spec/services/spam/ham_service_spec.rb'
- 'spec/services/spam/spam_action_service_spec.rb'
- 'spec/services/spam/spam_verdict_service_spec.rb'
- 'spec/services/static_site_editor/config_service_spec.rb'
- 'spec/services/submodules/update_service_spec.rb'
- 'spec/services/suggestions/apply_service_spec.rb'
- 'spec/services/suggestions/create_service_spec.rb'

View file

@ -286,7 +286,6 @@ RSpec/ReturnFromStub:
- 'spec/services/projects/in_product_marketing_campaign_emails_service_spec.rb'
- 'spec/services/projects/update_remote_mirror_service_spec.rb'
- 'spec/services/projects/update_service_spec.rb'
- 'spec/services/static_site_editor/config_service_spec.rb'
- 'spec/services/suggestions/apply_service_spec.rb'
- 'spec/services/suggestions/create_service_spec.rb'
- 'spec/services/verify_pages_domain_service_spec.rb'

View file

@ -225,7 +225,6 @@ Style/ClassAndModuleChildren:
- 'app/controllers/projects/snippets/blobs_controller.rb'
- 'app/controllers/projects/snippets_controller.rb'
- 'app/controllers/projects/starrers_controller.rb'
- 'app/controllers/projects/static_site_editor_controller.rb'
- 'app/controllers/projects/tags/releases_controller.rb'
- 'app/controllers/projects/tags_controller.rb'
- 'app/controllers/projects/templates_controller.rb'

View file

@ -14,7 +14,7 @@ export default {
type: Object,
required: true,
},
noteIsConfidential: {
isInternalNote: {
type: Boolean,
required: false,
default: false,
@ -44,7 +44,7 @@ export default {
return this.noteableData.issue_email_participants?.map(({ email }) => email) || [];
},
showEmailParticipantsWarning() {
return this.emailParticipants.length && !this.noteIsConfidential;
return this.emailParticipants.length && !this.isInternalNote;
},
},
};

View file

@ -60,7 +60,7 @@ export default {
note: '',
noteType: constants.COMMENT,
errors: [],
noteIsConfidential: false,
noteIsInternal: false,
isSubmitting: false,
};
},
@ -91,13 +91,13 @@ export default {
},
commentButtonTitle() {
const { comment, internalComment, startThread, startInternalThread } = this.$options.i18n;
if (this.noteIsConfidential) {
if (this.noteIsInternal) {
return this.noteType === constants.COMMENT ? internalComment : startInternalThread;
}
return this.noteType === constants.COMMENT ? comment : startThread;
},
textareaPlaceholder() {
return this.noteIsConfidential
return this.noteIsInternal
? this.$options.i18n.bodyPlaceholderInternal
: this.$options.i18n.bodyPlaceholder;
},
@ -110,7 +110,7 @@ export default {
canCreateNote() {
return this.getNoteableData.current_user.can_create_note;
},
canSetConfidential() {
canSetInternalNote() {
return this.getNoteableData.current_user.can_update && (this.isIssue || this.isEpic);
},
issueActionButtonTitle() {
@ -172,7 +172,7 @@ export default {
trackingLabel() {
return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
},
confidentialNotesEnabled() {
internalNotesEnabled() {
return Boolean(this.glFeatures.confidentialNotes);
},
disableSubmitButton() {
@ -217,7 +217,11 @@ export default {
note: {
noteable_type: this.noteableType,
noteable_id: this.getNoteableData.id,
confidential: this.noteIsConfidential,
// Internal notes were identified as `confidential`
// before we decided to treat them as _internal_
// so now until API is updated we need to use `confidential`
// in request payload.
confidential: this.noteIsInternal,
note: this.note,
},
merge_request_diff_head_sha: this.getNoteableData.diff_head_sha,
@ -292,7 +296,7 @@ export default {
if (shouldClear) {
this.note = '';
this.noteIsConfidential = false;
this.noteIsInternal = false;
this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false;
}
@ -356,7 +360,7 @@ export default {
<comment-field-layout
:with-alert-container="true"
:noteable-data="getNoteableData"
:note-is-confidential="noteIsConfidential"
:is-internal-note="noteIsInternal"
:noteable-type="noteableType"
>
<markdown-field
@ -410,17 +414,17 @@ export default {
</template>
<template v-else>
<gl-form-checkbox
v-if="confidentialNotesEnabled && canSetConfidential"
v-model="noteIsConfidential"
v-if="internalNotesEnabled && canSetInternalNote"
v-model="noteIsInternal"
class="gl-mb-6"
data-testid="confidential-note-checkbox"
data-testid="internal-note-checkbox"
>
{{ $options.i18n.confidential }}
{{ $options.i18n.internal }}
<gl-icon
v-gl-tooltip:tooltipcontainer.bottom
name="question"
:size="16"
:title="$options.i18n.confidentialVisibility"
:title="$options.i18n.internalVisibility"
class="gl-text-gray-500"
/>
</gl-form-checkbox>
@ -429,7 +433,7 @@ export default {
class="gl-mr-3"
:disabled="disableSubmitButton"
:tracking-label="trackingLabel"
:is-internal-note="noteIsConfidential"
:is-internal-note="noteIsInternal"
:noteable-display-name="noteableDisplayName"
:discussions-require-resolution="discussionsRequireResolution"
@click="handleSave"

View file

@ -8,7 +8,7 @@ import { __ } from '~/locale';
import '~/behaviors/markdown/render_gfm';
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
import autosave from '../mixins/autosave';
import { CONFIDENTIAL_CLASSES } from '../constants';
import { INTERNAL_NOTE_CLASSES } from '../constants';
import noteAttachment from './note_attachment.vue';
import noteAwardsList from './note_awards_list.vue';
import noteEditedText from './note_edited_text.vue';
@ -55,7 +55,7 @@ export default {
required: false,
default: '',
},
isConfidential: {
isInternalNote: {
type: Boolean,
required: false,
default: false,
@ -101,9 +101,9 @@ export default {
return escape(suggestion);
},
confidentialContainerClasses() {
if (this.isConfidential && !this.isEditing) {
return CONFIDENTIAL_CLASSES;
internalNoteContainerClasses() {
if (this.isInternalNote && !this.isEditing) {
return INTERNAL_NOTE_CLASSES;
}
return '';
},
@ -179,7 +179,7 @@ export default {
}"
class="note-body"
>
<div :class="confidentialContainerClasses" data-testid="note-confidential-container">
<div :class="internalNoteContainerClasses" data-testid="note-internal-container">
<suggestions
v-if="hasSuggestion && !isEditing"
:suggestions="note.suggestions"

View file

@ -329,7 +329,7 @@ export default {
<form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form">
<comment-field-layout
:noteable-data="getNoteableData"
:note-is-confidential="discussion.confidential"
:is-internal-note="discussion.confidential"
>
<markdown-field
:markdown-preview-path="markdownPreviewPath"

View file

@ -67,7 +67,7 @@ export default {
required: false,
default: true,
},
isConfidential: {
isInternalNote: {
type: Boolean,
required: false,
default: false,
@ -110,7 +110,7 @@ export default {
authorName() {
return this.author.name;
},
noteConfidentialityTooltip() {
internalNoteTooltip() {
return s__('Notes|This internal note will always remain confidential');
},
},
@ -231,13 +231,13 @@ export default {
<time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" />
</template>
<gl-badge
v-if="isConfidential"
v-if="isInternalNote"
v-gl-tooltip:tooltipcontainer.bottom
data-testid="internalNoteIndicator"
variant="warning"
size="sm"
class="gl-mb-3 gl-ml-2"
:title="noteConfidentialityTooltip"
:title="internalNoteTooltip"
>
{{ __('Internal note') }}
</gl-badge>

View file

@ -447,7 +447,7 @@ export default {
:author="author"
:created-at="note.created_at"
:note-id="note.id"
:is-confidential="note.confidential"
:is-internal-note="note.confidential"
:noteable-type="noteableType"
>
<template #note-header-info>
@ -494,7 +494,7 @@ export default {
ref="noteBody"
:note="note"
:can-edit="note.current_user.can_edit"
:is-confidential="note.confidential"
:is-internal-note="note.confidential"
:line="line"
:file="diffFile"
:is-editing="isEditing"

View file

@ -52,4 +52,4 @@ export const toggleStateErrorMessage = {
},
};
export const CONFIDENTIAL_CLASSES = ['gl-bg-orange-50', 'gl-px-4', 'gl-py-2'];
export const INTERNAL_NOTE_CLASSES = ['gl-bg-orange-50', 'gl-px-4', 'gl-py-2'];

View file

@ -14,8 +14,8 @@ export const COMMENT_FORM = {
epic: __('epic'),
bodyPlaceholder: __('Write a comment or drag your files here…'),
bodyPlaceholderInternal: __('Write an internal note or drag your files here…'),
confidential: s__('Notes|Make this an internal note'),
confidentialVisibility: s__(
internal: s__('Notes|Make this an internal note'),
internalVisibility: s__(
'Notes|Internal notes are only visible to the author, assignees, and members with the role of Reporter or higher',
),
discussionThatNeedsResolution: __(

View file

@ -1,102 +0,0 @@
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import getPipelineWarnings from '../../graphql/queries/get_pipeline_warnings.query.graphql';
export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
expectedMessage: 'will be removed in',
i18n: {
title: __('Found warning in your .gitlab-ci.yml'),
rootTypesWarning: __(
'%{codeStart}types%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stages%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}',
),
typeWarning: __(
'%{codeStart}type%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stage%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}',
),
},
components: {
GlAlert,
GlLink,
GlSprintf,
},
inject: ['deprecatedKeywordsDocPath', 'fullPath', 'pipelineIid'],
apollo: {
warnings: {
query: getPipelineWarnings,
variables() {
return {
fullPath: this.fullPath,
iid: this.pipelineIid,
};
},
update(data) {
return data?.project?.pipeline?.warningMessages || [];
},
error() {
this.hasError = true;
},
},
},
data() {
return {
warnings: [],
hasError: false,
};
},
computed: {
deprecationWarnings() {
return this.warnings.filter(({ content }) => {
return content.includes(this.$options.expectedMessage);
});
},
formattedWarnings() {
// The API doesn't have a mechanism currently to return a
// type instead of just the error message. To work around this,
// we check if the deprecation message is found within the warnings
// and show a FE version of that message with the link to the documentation
// and translated. We can have only 2 types of warnings: root types and individual
// type. If the word `root` is present, then we know it's the root type deprecation
// and if not, it's the normal type. This has to be deleted in 15.0.
// Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/350810
return this.deprecationWarnings.map(({ content }) => {
if (content.includes('root')) {
return this.$options.i18n.rootTypesWarning;
}
return this.$options.i18n.typeWarning;
});
},
hasDeprecationWarning() {
return this.formattedWarnings.length > 0;
},
showWarning() {
return (
!this.$apollo.queries.warnings?.loading && !this.hasError && this.hasDeprecationWarning
);
},
},
};
</script>
<template>
<div>
<gl-alert
v-if="showWarning"
:title="$options.i18n.title"
variant="warning"
:dismissible="false"
>
<ul class="gl-mb-0">
<li v-for="warning in formattedWarnings" :key="warning">
<gl-sprintf :message="warning">
<template #code="{ content }">
<code> {{ content }}</code>
</template>
<template #link="{ content }">
<gl-link :href="deprecatedKeywordsDocPath" target="_blank"> {{ content }}</gl-link>
</template>
</gl-sprintf>
</li>
</ul>
</gl-alert>
</div>
</template>

View file

@ -19,7 +19,10 @@ export default {
},
testCase: {
type: Object,
required: true,
required: false,
default: () => {
return {};
},
},
},
computed: {
@ -49,6 +52,7 @@ export default {
},
text: {
name: __('Name'),
file: __('File'),
duration: __('Execution time'),
history: __('History'),
trace: __('System output'),
@ -74,11 +78,24 @@ export default {
</div>
</div>
<div v-if="testCase.file" class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">
<strong class="gl-text-right col-sm-3">{{ $options.text.file }}</strong>
<div class="col-sm-9" data-testid="test-case-file">
<gl-link v-if="testCase.filePath" :href="testCase.filePath">
{{ testCase.file }}
</gl-link>
<span v-else>{{ testCase.file }}</span>
</div>
</div>
<div class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">
<strong class="gl-text-right col-sm-3">{{ $options.text.duration }}</strong>
<div class="col-sm-9" data-testid="test-case-duration">
<div v-if="testCase.formattedTime" class="col-sm-9" data-testid="test-case-duration">
{{ testCase.formattedTime }}
</div>
<div v-else-if="testCase.execution_time" class="col-sm-9" data-testid="test-case-duration">
{{ sprintf('%{value} s', { value: testCase.execution_time }) }}
</div>
</div>
<div v-if="testCase.recent_failures" class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">

View file

@ -1,12 +0,0 @@
query getPipelineWarnings($fullPath: ID!, $iid: ID!) {
project(fullPath: $fullPath) {
id
pipeline(iid: $iid) {
id
warningMessages {
content
id
}
}
}
}

View file

@ -3,7 +3,6 @@ import { __, s__ } from '~/locale';
import createDagApp from './pipeline_details_dag';
import { createPipelinesDetailApp } from './pipeline_details_graph';
import { createPipelineHeaderApp } from './pipeline_details_header';
import { createPipelineNotificationApp } from './pipeline_details_notification';
import { createPipelineJobsApp } from './pipeline_details_jobs';
import { createPipelineFailedJobsApp } from './pipeline_details_failed_jobs';
import { apolloProvider } from './pipeline_shared_client';
@ -13,7 +12,6 @@ const SELECTORS = {
PIPELINE_DETAILS: '.js-pipeline-details-vue',
PIPELINE_GRAPH: '#js-pipeline-graph-vue',
PIPELINE_HEADER: '#js-pipeline-header-vue',
PIPELINE_NOTIFICATION: '#js-pipeline-notification',
PIPELINE_TABS: '#js-pipeline-tabs',
PIPELINE_TESTS: '#js-pipeline-tests-detail',
PIPELINE_JOBS: '#js-pipeline-jobs-vue',
@ -31,14 +29,6 @@ export default async function initPipelineDetailsBundle() {
});
}
try {
createPipelineNotificationApp(SELECTORS.PIPELINE_NOTIFICATION, apolloProvider);
} catch {
createFlash({
message: __('An error occurred while loading a section of this page.'),
});
}
if (gon.features?.pipelineTabsVue) {
const { createAppOptions } = await import('ee_else_ce/pipelines/pipeline_tabs');
const { createPipelineTabs } = await import('./pipeline_tabs');

View file

@ -1,31 +0,0 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import DeprecatedKeywordNotification from './components/notification/deprecated_type_keyword_notification.vue';
Vue.use(VueApollo);
export const createPipelineNotificationApp = (elSelector, apolloProvider) => {
const el = document.querySelector(elSelector);
if (!el) {
return;
}
const { deprecatedKeywordsDocPath, fullPath, pipelineIid } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
DeprecatedKeywordNotification,
},
provide: {
deprecatedKeywordsDocPath,
fullPath,
pipelineIid,
},
apolloProvider,
render(createElement) {
return createElement('deprecated-keyword-notification');
},
});
};

View file

@ -159,11 +159,7 @@ export default {
},
method: 'fetchData',
successCallback: (response) => {
const headers = normalizeHeaders(response.headers);
if (!headers['POLL-INTERVAL']) {
allData.push(response.data);
}
this.headerCheck(response, (data) => allData.push(data));
if (allData.length === requests.length) {
this.setCollapsedData(allData);
@ -183,21 +179,23 @@ export default {
fetchData: () => this.fetchCollapsedData(this),
},
method: 'fetchData',
successCallback: ({ data }) => {
if (Object.keys(data).length > 0) {
poll.stop();
this.setCollapsedData(data);
}
successCallback: (response) => {
this.headerCheck(response, (data) => this.setCollapsedData(data));
},
errorCallback: (e) => {
poll.stop();
this.setCollapsedError(e);
},
});
poll.makeRequest();
},
headerCheck(response, callback) {
const headers = normalizeHeaders(response.headers);
if (!headers['POLL-INTERVAL']) {
callback(response.data);
}
},
loadCollapsedData() {
this.loadingState = LOADING_STATES.collapsedLoading;

View file

@ -70,7 +70,7 @@ export default {
<gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
</div>
<div v-if="data.modal">
<gl-link v-gl-modal="modalId" @click="data.modal.onClick">
<gl-link v-gl-modal="modalId" data-testid="modal-link" @click="data.modal.onClick">
{{ data.modal.text }}
</gl-link>
</div>

View file

@ -1,5 +1,6 @@
import { uniqueId } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
import { EXTENSION_ICONS } from '../../constants';
import {
summaryTextBuilder,
@ -7,6 +8,7 @@ import {
reportSubTextBuilder,
countRecentlyFailedTests,
recentFailuresTextBuilder,
formatFilePath,
} from './utils';
import { i18n, TESTS_FAILED_STATUS, ERROR_STATUS } from './constants';
@ -16,6 +18,7 @@ export default {
i18n,
expandEvent: 'i_testing_summary_widget_total',
props: ['testResultsPath', 'headBlobPath', 'pipeline'],
modalComponent: TestCaseDetails,
computed: {
summary(data) {
if (data.parsingInProgress) {
@ -53,8 +56,11 @@ export default {
},
methods: {
fetchCollapsedData() {
return axios.get(this.testResultsPath).then(({ data = {}, status }) => {
return axios.get(this.testResultsPath).then((res) => {
const { data = {}, status } = res;
return {
...res,
data: {
hasSuiteError: data.suites?.some((suite) => suite.status === ERROR_STATUS),
parsingInProgress: status === 204,
@ -94,8 +100,18 @@ export default {
return {
id: uniqueId('test-'),
header: this.testHeader(test, sectionHeader, index),
modal: {
text: test.name,
onClick: () => {
this.modalData = {
testCase: {
filePath: test.file && `${this.headBlobPath}/${formatFilePath(test.file)}`,
...test,
},
};
},
},
icon: { name: iconName },
text: test.name,
};
};
},

View file

@ -82,3 +82,12 @@ export const countRecentlyFailedTests = (subject) => {
})
.reduce((total, count) => total + count, 0);
};
/**
* Removes `./` from the beginning of a file path so it can be appended onto a blob path
* @param {String} file
* @returns {String} - formatted value
*/
export const formatFilePath = (file) => {
return file.replace(/^\.?\/*/, '');
};

View file

@ -74,21 +74,11 @@
> li:not(.empty-message):not(.no-border) {
background-color: $white;
margin-bottom: 5px;
display: flex;
justify-content: space-between;
padding: $gl-padding;
border-radius: $border-radius-default;
border: 1px solid $gray-50;
&:last-child {
margin-bottom: 0;
}
.prioritized-labels:not(.is-not-draggable) & {
box-shadow: 0 1px 2px $issue-boards-card-shadow;
cursor: grab;
border: 0;
&:active {
cursor: grabbing;
@ -111,11 +101,6 @@
width: 109px;
}
.labels-container {
border-radius: $border-radius-default;
padding: $gl-padding $gl-padding-8;
}
.label-actions-list {
list-style: none;
flex-shrink: 0;

View file

@ -13,9 +13,9 @@
maskable_regex: ci_variable_maskable_regex,
protected_by_default: ci_variable_protected_by_default?.to_s,
aws_logo_svg_path: image_path('aws_logo.svg'),
aws_tip_deploy_link: help_page_path('ci/cloud_deployment/index.md', anchor: 'deploy-your-application-to-the-aws-elastic-container-service-ecs'),
aws_tip_commands_link: help_page_path('ci/cloud_deployment/index.md', anchor: 'run-aws-commands-from-gitlab-cicd'),
aws_tip_learn_link: help_page_path('ci/cloud_deployment/index.md', anchor: 'aws'),
aws_tip_deploy_link: help_page_path('ci/cloud_deployment/index.md', anchor: 'deploy-your-application-to-the-amazon-elastic-container-service-ecs'),
aws_tip_commands_link: help_page_path('ci/cloud_deployment/index.md', anchor: 'use-an-image-to-run-aws-commands'),
aws_tip_learn_link: help_page_path('ci/cloud_deployment/index.md'),
contains_variable_reference_link: help_page_path('ci/variables/index', anchor: 'use-variables-in-other-variables'),
protected_environment_variables_link: help_page_path('ci/variables/index', anchor: 'protected-cicd-variables'),
masked_environment_variables_link: help_page_path('ci/variables/index', anchor: 'mask-a-cicd-variable'),

View file

@ -4,6 +4,5 @@
%h1.page-title
= _('Edit Label')
%hr
= render 'shared/labels/form', url: group_label_path(@group, @label), back_path: @previous_labels_path

View file

@ -8,13 +8,13 @@
#js-promote-label-modal
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.gl-mt-2.gl-bg-gray-10.gl-border-solid.gl-border-1.gl-border-gray-100
.labels-container.gl-mt-5
- if @labels.any?
.text-muted
.text-muted.gl-mb-5
= _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence }
.other-labels
%h5= _('Labels')
%ul.content-list.manage-labels-list.js-other-labels
%h4= _('Labels')
%ul.manage-labels-list.js-other-labels
= render partial: 'shared/label', collection: @labels, as: :label, locals: { use_label_priority: false, subject: @group }
= paginate @labels, theme: 'gitlab'
- elsif search.present?

View file

@ -4,6 +4,5 @@
%h1.page-title
= _('New Label')
%hr
= render 'shared/labels/form', url: group_labels_path, back_path: @previous_labels_path

View file

@ -4,5 +4,5 @@
%h1.page-title
= _('Edit Label')
%hr
= render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project)

View file

@ -9,7 +9,7 @@
#js-promote-label-modal
= render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.gl-mt-3.gl-bg-gray-10.gl-border-solid.gl-border-1.gl-border-gray-100
.labels-container.gl-mt-5
- if can_admin_label && search.blank?
%p.text-muted
= _('Labels can be applied to issues and merge requests.')
@ -19,8 +19,8 @@
-# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels.gl-mb-7{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] }
%h5.gl-mt-3= _('Prioritized Labels')
.content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
%h4.gl-mt-3= _('Prioritized Labels')
.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
#js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
= render 'shared/empty_states/priority_labels'
- if @prioritized_labels.any?
@ -31,14 +31,14 @@
- if @labels.any?
.other-labels
%h5{ class: ('hide' if hide) }= _('Other Labels')
.content-list.manage-labels-list.js-other-labels
%h4{ class: ('hide' if hide) }= _('Other Labels')
.manage-labels-list.js-other-labels
= render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project }
= paginate @labels, theme: 'gitlab'
- elsif search.present?
.other-labels
- if @available_labels.any?
%h5
%h4
= _('Other Labels')
.nothing-here-block
= _('No other labels with such name or description')

View file

@ -4,5 +4,5 @@
%h1.page-title
= _('New Label')
%hr
= render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project)

View file

@ -26,7 +26,6 @@
- lint_link_start = '<a href="%{url}" class="gl-text-blue-500!">'.html_safe % { url: lint_link_url }
= s_('You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}').html_safe % { gitlab_ci_yml: '.gitlab-ci.yml', lint_link_start: lint_link_start, lint_link_end: '</a>'.html_safe }
#js-pipeline-notification{ data: { deprecated_keywords_doc_path: help_page_path('ci/yaml/index.md', anchor: 'deprecated-keywords'), full_path: @project.full_path, pipeline_iid: @pipeline.iid } }
- if Feature.enabled?(:pipeline_tabs_vue, @project)
#js-pipeline-tabs{ data: js_pipeline_tabs_data(@project, @pipeline) }
- else

View file

@ -6,7 +6,7 @@
- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user
- tooltip_title = label_status_tooltip(label, status) if status
%li.label-list-item{ id: label_css_id, data: { id: label.id } }
%li.label-list-item{ id: label_css_id, class: "gl-p-5 gl-border-b", data: { id: label.id } }
= render "shared/label_row", label: label, force_priority: force_priority
%ul.label-actions-list
- if can?(current_user, :admin_label, @project)

View file

@ -2,21 +2,18 @@
= form_errors(@label)
.form-group.row
.col-sm-2.col-form-label
.col-12
= f.label :title
.col-sm-10
= f.text_field :title, class: "gl-form-input form-control js-label-title qa-label-title", required: true, autofocus: true
= render_if_exists 'shared/labels/create_label_help_text'
.form-group.row
.col-sm-2.col-form-label
.col-12
= f.label :description
.col-sm-10
= f.text_field :description, class: "gl-form-input form-control js-quick-submit qa-label-description"
.form-group.row
.col-sm-2.col-form-label
.col-12
= f.label :color, _("Background color")
.col-sm-10
.input-group
.input-group-prepend
.input-group-text.label-color-preview &nbsp;
@ -26,13 +23,13 @@
%br
= _("Or you can choose one of the suggested colors below")
= render_suggested_colors
.gl-display-flex.gl-justify-content-space-between.gl-p-5.gl-bg-gray-10.gl-border-t-solid.gl-border-t-gray-100.gl-border-t-1
.gl-display-flex.gl-justify-content-space-between
%div
- if @label.persisted?
= f.submit _('Save changes'), class: 'btn gl-button btn-confirm js-save-button'
= f.submit _('Save changes'), class: 'btn gl-button btn-confirm js-save-button gl-mr-2'
- else
= f.submit _('Create label'), class: 'btn gl-button btn-confirm js-save-button qa-label-create-button'
= link_to _('Cancel'), back_path, class: 'btn gl-button btn-default btn-cancel'
= f.submit _('Create label'), class: 'btn gl-button btn-confirm js-save-button qa-label-create-button gl-mr-2'
= link_to _('Cancel'), back_path, class: 'btn gl-button btn-default btn-cancel gl-mr-2'
- if @label.persisted?
- presented_label = @label.present
%button.btn.btn-danger.gl-button.btn-danger-secondary.js-delete-label-modal-button{ type: 'button', data: { label_name: presented_label.name, subject_name: presented_label.subject_name, destroy_path: presented_label.destroy_path } }

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/359740
milestone: '15.0'
type: development
group: group::pipeline authoring
default_enabled: false
default_enabled: true

View file

@ -4,11 +4,10 @@ group: Release
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Getting started with Continuous Deployment to AWS Elastic Container Service **(FREE)**
# Deploy to Amazon Elastic Container Service **(FREE)**
This step-by-step guide helps you use [Continuous Deployment to ECS](../index.md#deploy-your-application-to-the-aws-elastic-container-service-ecs)
that deploys a project hosted on GitLab.com to [Elastic Container Service](https://aws.amazon.com/ecs/)
(ECS) on AWS.
This step-by-step guide helps you deploy a project hosted on GitLab.com to
the Amazon [Elastic Container Service (ECS)](https://aws.amazon.com/ecs/).
In this guide, you begin by creating an ECS cluster manually using the AWS console. You create and
deploy a simple application that you create from a GitLab template.
@ -68,7 +67,7 @@ and [Container Registry](../../../user/packages/container_registry/index.md).
1. Select **Setup up CI/CD**. It brings you to a `.gitlab-ci.yml`
creation form.
1. Copy and paste the following content into the empty `.gitlab-ci.yml`. This defines
[a pipeline for continuous deployment to ECS](../index.md#deploy-your-application-to-the-aws-elastic-container-service-ecs).
a pipeline for continuous deployment to ECS.
```yaml
include:

View file

@ -5,165 +5,101 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
# Cloud deployment **(FREE)**
# Deploy to AWS from GitLab CI/CD **(FREE)**
Interacting with a major cloud provider may have become a much needed task that's
part of your delivery process. With GitLab you can
[deploy your application anywhere](https://about.gitlab.com/stages-devops-lifecycle/deploy-targets/).
GitLab provides Docker images with the libraries and tools you need to deploy
to AWS. You can reference these images in your CI/CD pipeline.
For some specific deployment targets, GitLab makes this process less painful by providing Docker
images with the needed libraries and tools pre-installed. By referencing them in your
CI/CD pipeline, you can interact with your chosen cloud provider more easily.
If you're using GitLab.com and deploying to the [Amazon Elastic Container Service](https://aws.amazon.com/ecs/) (ECS),
read about [deploying to ECS](ecs/quick_start_guide.md).
## AWS
## Authenticate GitLab with AWS
GitLab provides Docker images that you can use to [run AWS commands from GitLab CI/CD](#run-aws-commands-from-gitlab-cicd), and a template to make
it easier to [deploy to AWS](#deploy-your-application-to-the-aws-elastic-container-service-ecs).
To use GitLab CI/CD to connect to AWS, you must authenticate.
After you set up authentication, you can configure CI/CD to deploy.
### Quick start
1. Sign on to your AWS account.
1. Create [an IAM user](https://console.aws.amazon.com/iam/home#/home).
1. Select your user to access its details. Go to **Security credentials > Create a new access key**.
1. Note the **Access key ID** and **Secret access key**.
1. In your GitLab project, go to **Settings > CI/CD**. Set the following
[CI/CD variables](../variables/index.md):
If you're using GitLab.com, see the [quick start guide](ecs/quick_start_guide.md)
for setting up Continuous Deployment to [AWS Elastic Container Service](https://aws.amazon.com/ecs/) (ECS).
| Environment variable name | Value |
|:-------------------------------|:------------------------|
| `AWS_ACCESS_KEY_ID` | Your Access key ID. |
| `AWS_SECRET_ACCESS_KEY` | Your secret access key. |
| `AWS_DEFAULT_REGION` | Your region code. You might want to confirm that the AWS service you intend to use is [available in the chosen region](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/). |
### Run AWS commands from GitLab CI/CD
1. Variables are [protected by default](../variables/index.md#protected-cicd-variables).
To use GitLab CI/CD with branches or tags that are not protected,
clear the **Protect variable** checkbox.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/31167) in GitLab 12.6.
## Use an image to run AWS commands
The GitLab AWS Docker image provides the [AWS Command Line Interface](https://aws.amazon.com/cli/),
which enables you to run `aws` commands. As part of your deployment strategy, you can run `aws` commands directly from
`.gitlab-ci.yml` by specifying the [GitLab AWS Docker image](https://gitlab.com/gitlab-org/cloud-deploy).
If an image contains the [AWS Command Line Interface](https://aws.amazon.com/cli/),
you can reference the image in your project's `.gitlab-ci.yml` file. Then you can run
`aws` commands in your CI/CD jobs.
Some credentials are required to be able to run `aws` commands:
For example:
1. Sign up for [an AWS account](https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-set-up.html) if you don't have one yet.
1. Log in onto the console and create [a new IAM user](https://console.aws.amazon.com/iam/home#/home).
1. Select your newly created user to access its details. Navigate to **Security credentials > Create a new access key**.
```yaml
deploy:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
script:
- aws s3 ...
- aws create-deployment ...
```
NOTE:
A new **Access key ID** and **Secret access key** are generated. Please take a note of them right away.
GitLab provides a Docker image that includes the AWS CLI:
1. In your GitLab project, go to **Settings > CI/CD**. Set the following as
[CI/CD variables](../variables/index.md)
(see table below):
- Images are hosted in the GitLab Container Registry. The latest image is
`registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest`.
- [Images are stored in a GitLab repository](https://gitlab.com/gitlab-org/cloud-deploy/-/tree/master/aws).
- Access key ID.
- Secret access key.
- Region code. You can check the [list of AWS regional endpoints](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints).
You might want to check if the AWS service you intend to use is
[available in the chosen region](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/).
Alternately, you can use an [Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/) image.
[Learn how to push an image to your ECR repository](https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html).
| Environment variable name | Value |
|:-------------------------------|:-----------------------|
| `AWS_ACCESS_KEY_ID` | Your Access key ID |
| `AWS_SECRET_ACCESS_KEY` | Your Secret access key |
| `AWS_DEFAULT_REGION` | Your region code |
You can also use an image from any third-party registry.
NOTE:
When you create a variable it's set to be [protected by default](../variables/index.md#protected-cicd-variables). If you want to use the `aws` commands on branches or tags that are not protected, make sure to uncheck the **Protect variable** checkbox.
1. You can now use `aws` commands in the `.gitlab-ci.yml` file of this project:
```yaml
deploy:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest # see the note below
script:
- aws s3 ...
- aws create-deployment ...
```
NOTE:
The image used in the example above
(`registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest`) is hosted on the [GitLab
Container Registry](../../user/packages/container_registry/index.md) and is
ready to use. Alternatively, replace the image with one hosted on AWS ECR.
### Use an AWS Elastic Container Registry (ECR) image in your CI/CD
Instead of referencing an image hosted on the GitLab Registry, you can
reference an image hosted on any third-party registry, such as the
[Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/).
To do so, [push your image into your ECR
repository](https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html).
Then reference it in your `.gitlab-ci.yml` file and replace the `image`
path to point to your ECR image.
### Deploy your application to the AWS Elastic Container Service (ECS)
## Deploy your application to the Amazon Elastic Container Service (ECS)
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207962) in GitLab 12.9.
> - The `Deploy-ECS.gitlab-ci.yml` template was [moved](https://gitlab.com/gitlab-org/gitlab/-/issues/220821) to `AWS/Deploy-ECS.gitlab-ci.yml` in GitLab 13.2.
GitLab provides a series of [CI templates that you can include in your project](../yaml/index.md#include).
To automate deployments of your application to your [Amazon Elastic Container Service](https://aws.amazon.com/ecs/) (AWS ECS)
cluster, you can `include` the `AWS/Deploy-ECS.gitlab-ci.yml` template in your `.gitlab-ci.yml` file.
You can automate deployments of your application to your [Amazon ECS](https://aws.amazon.com/ecs/)
cluster.
GitLab also provides [Docker images](https://gitlab.com/gitlab-org/cloud-deploy/-/tree/master/aws) that can be used in your `.gitlab-ci.yml` file to simplify working with AWS:
Prerequisites:
- Use `registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest` to use AWS CLI commands.
- Use `registry.gitlab.com/gitlab-org/cloud-deploy/aws-ecs:latest` to deploy your application to AWS ECS.
- [Authenticate AWS with GitLab](#authenticate-gitlab-with-aws).
- Create a cluster on Amazon ECS.
- Create related components, like an ECS service, a database on Amazon RDS, and so on.
- Create an ECS task definition, where the value for the `containerDefinitions[].name` attribute is
the same as the `Container name` defined in your targeted ECS service. The task definition can be:
- An existing task definition in ECS.
- [In GitLab 13.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/222618),
a JSON file in your GitLab project. Use the
[template in the AWS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/create-task-definition.html#task-definition-template)
and save the file in your project. For example `<project-root>/ci/aws/task-definition.json`.
Before getting started with this process, you need a cluster on AWS ECS, as well as related
components, like an ECS service, ECS task definition, a database on Amazon RDS, and so on.
[Read more about AWS ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html).
To deploy to your ECS cluster:
The ECS task definition can be:
1. In your GitLab project, go to **Settings > CI/CD**. Set the following
[CI/CD variables](../variables/index.md). You can find these names by
selecting the targeted cluster on your [Amazon ECS dashboard](https://console.aws.amazon.com/ecs/home).
- An existing task definition in AWS ECS
- A JSON file containing a task definition. Create the JSON file by using the template provided in
the [AWS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/create-task-definition.html#task-definition-template).
Copy the task definition into a new file in your project, for example `<project-root>/ci/aws/task-definition.json`.
[Available](https://gitlab.com/gitlab-org/gitlab/-/issues/222618) in GitLab 13.3 and later.
After you have these prerequisites ready, follow these steps:
1. Make sure your AWS credentials are set up as CI/CD variables for your
project. You can follow [the steps above](#run-aws-commands-from-gitlab-cicd) to complete this setup.
1. Add these variables to your project's `.gitlab-ci.yml` file, or in the project's
[CI/CD settings](../variables/index.md#custom-cicd-variables):
- `CI_AWS_ECS_CLUSTER`: The name of the AWS ECS cluster that you're targeting for your deployments.
- `CI_AWS_ECS_SERVICE`: The name of the targeted service tied to your AWS ECS cluster.
- `CI_AWS_ECS_TASK_DEFINITION`: The name of an existing task definition in ECS tied
to the service mentioned above.
```yaml
variables:
CI_AWS_ECS_CLUSTER: my-cluster
CI_AWS_ECS_SERVICE: my-service
CI_AWS_ECS_TASK_DEFINITION: my-task-definition
```
You can find these names after selecting the targeted cluster on your [AWS ECS dashboard](https://console.aws.amazon.com/ecs/home):
![AWS ECS dashboard](../img/ecs_dashboard_v12_9.png)
Alternatively, if you want to use a task definition defined in a JSON file, use
`CI_AWS_ECS_TASK_DEFINITION_FILE` instead:
```yaml
variables:
CI_AWS_ECS_CLUSTER: my-cluster
CI_AWS_ECS_SERVICE: my-service
CI_AWS_ECS_TASK_DEFINITION_FILE: ci/aws/my_task_definition.json
```
You can create your `CI_AWS_ECS_TASK_DEFINITION_FILE` variable as a
[file-typed CI/CD variable](../variables/index.md#cicd-variable-types) instead of a
regular CI/CD variable. If you choose to do so, set the variable value to be the full contents of
the JSON task definition. You can then remove the JSON file from your project.
In both cases, make sure that the value for the `containerDefinitions[].name` attribute is
the same as the `Container name` defined in your targeted ECS service.
| Environment variable name | Value |
|:-------------------------------|:------------------------|
| `CI_AWS_ECS_CLUSTER` | The name of the AWS ECS cluster that you're targeting for your deployments. |
| `CI_AWS_ECS_SERVICE` | The name of the targeted service tied to your AWS ECS cluster. |
| `CI_AWS_ECS_TASK_DEFINITION` | If the task definition is in ECS, the name of the task definition tied to the service. |
| `CI_AWS_ECS_TASK_DEFINITION_FILE` | If the task definition is a JSON file in GitLab, the filename, including the path. For example, `ci/aws/my_task_definition.json`. If the name of the task definition in your JSON file is the same name as an existing task definition in ECS, then a new revision is created when CI/CD runs. Otherwise, a brand new task definition is created, starting at revision 1. |
WARNING:
`CI_AWS_ECS_TASK_DEFINITION_FILE` takes precedence over `CI_AWS_ECS_TASK_DEFINITION` if both these
variables are defined within your project.
NOTE:
If the name of the task definition you wrote in your JSON file is the same name
as an existing task definition on AWS, then a new revision is created for it.
Otherwise, a brand new task definition is created, starting at revision 1.
If you define both `CI_AWS_ECS_TASK_DEFINITION_FILE` and `CI_AWS_ECS_TASK_DEFINITION`,
`CI_AWS_ECS_TASK_DEFINITION_FILE` takes precedence.
1. Include this template in `.gitlab-ci.yml`:
@ -172,46 +108,32 @@ After you have these prerequisites ready, follow these steps:
- template: AWS/Deploy-ECS.gitlab-ci.yml
```
The `AWS/Deploy-ECS` template ships with GitLab and is available [on
GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/AWS/Deploy-ECS.gitlab-ci.yml).
The `AWS/Deploy-ECS` template ships with GitLab and is available
[on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/AWS/Deploy-ECS.gitlab-ci.yml).
1. Commit and push your updated `.gitlab-ci.yml` to your project's repository, and you're done!
1. Commit and push your updated `.gitlab-ci.yml` to your project's repository.
Your application Docker image is rebuilt and pushed to the GitLab registry.
If your image is located in a private registry, make sure your task definition is
[configured with a `repositoryCredentials` attribute](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/private-auth.html).
Your application Docker image is rebuilt and pushed to the GitLab Container Registry.
If your image is located in a private registry, make sure your task definition is
[configured with a `repositoryCredentials` attribute](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/private-auth.html).
Then the targeted task definition is updated with the location of the new
Docker image, and a new revision is created in ECS as result.
The targeted task definition is updated with the location of the new
Docker image, and a new revision is created in ECS as result.
Finally, your AWS ECS service is updated with the new revision of the
task definition, making the cluster pull the newest version of your
application.
Finally, your AWS ECS service is updated with the new revision of the
task definition, making the cluster pull the newest version of your
application.
WARNING:
The [`AWS/Deploy-ECS.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/AWS/Deploy-ECS.gitlab-ci.yml)
template includes both the [`Jobs/Build.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml)
and [`Jobs/Deploy/ECS.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml)
"sub-templates". Do not include these "sub-templates" on their own, and only include the main
`AWS/Deploy-ECS.gitlab-ci.yml` template. The "sub-templates" are designed to only be
used along with the main template. They may move or change unexpectedly causing your
pipeline to fail if you didn't include the main template. Also, the job names within
these templates may change. Do not override these jobs names in your own pipeline,
as the override stops working when the name changes.
template includes two templates: [`Jobs/Build.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml)
and [`Jobs/Deploy/ECS.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml). Do not include these templates on their own. Only include the
`AWS/Deploy-ECS.gitlab-ci.yml` template. These other templates are designed to be
used only with the main template. They may move or change unexpectedly. Also, the job names within
these templates may change. Do not override these job names in your own pipeline,
because the override stops working when the name changes.
Alternatively, if you don't wish to use the `AWS/Deploy-ECS.gitlab-ci.yml` template
to deploy to AWS ECS, you can always use our
`aws-base` Docker image to run your own [AWS CLI commands for ECS](https://docs.aws.amazon.com/cli/latest/reference/ecs/index.html#cli-aws-ecs).
```yaml
deploy:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
script:
- aws ecs register-task-definition ...
```
### Provision and deploy to your AWS Elastic Compute Cloud (EC2)
## Deploy your application to Amazon EC2
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201742) in GitLab 13.5.
@ -224,7 +146,7 @@ following actions within the same pipeline:
![CF-Provision-and-Deploy-EC2 diagram](../img/cf_ec2_diagram_v13_5.png)
#### Run the `AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml` template
### Run the `AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml` template
To run the `AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml` template, you must
pass three JSON input objects, based on existing templates:
@ -289,12 +211,12 @@ When running your project pipeline at this point:
on the related JSON object's content. The deployment job finishes whenever the deployment to EC2
is done or has failed.
#### Custom build job for Auto DevOps
### Custom build job for Auto DevOps
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216008) in GitLab 13.6.
To leverage [Auto DevOps](../../topics/autodevops/index.md) for your project when deploying to
AWS EC2, first you must define [your AWS credentials as CI/CD variables](#run-aws-commands-from-gitlab-cicd).
AWS EC2, first you must define [your AWS credentials as CI/CD variables](#authenticate-gitlab-with-aws).
Next, define a job for the `build` stage. To do so, you must reference the
`Auto-DevOps.gitlab-ci.yml` template and include a job named `build_artifact` in your
@ -323,8 +245,4 @@ For a video walkthrough of this configuration process, see [Auto Deploy to EC2](
### Deploy to Amazon EKS
- [How to deploy your application to a GitLab-managed Amazon EKS cluster with Auto DevOps](https://about.gitlab.com/blog/2020/05/05/deploying-application-eks/)
## Deploy to Google Cloud
- [Deploying with GitLab on Google Cloud](https://about.gitlab.com/partners/technology-partners/google-cloud-platform/)
- [Deploy your application to a GitLab-managed Amazon EKS cluster with Auto DevOps](https://about.gitlab.com/blog/2020/05/05/deploying-application-eks/)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View file

@ -191,7 +191,7 @@ To do so, follow these steps:
- `ECS` if you're not enforcing any launch type check when deploying to ECS.
When you trigger a pipeline, if you have Auto DevOps enabled and if you have correctly
[entered AWS credentials as variables](../../ci/cloud_deployment/index.md#deploy-your-application-to-the-aws-elastic-container-service-ecs),
[entered AWS credentials as variables](../../ci/cloud_deployment/index.md#authenticate-gitlab-with-aws),
your application is deployed to AWS ECS.
If you have both a valid `AUTO_DEVOPS_PLATFORM_TARGET` variable and a Kubernetes cluster tied to your project,

View file

@ -475,12 +475,6 @@ msgid_plural "%{bold_start}%{count}%{bold_end} opened merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "%{codeStart}type%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stage%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}"
msgstr ""
msgid "%{codeStart}types%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stages%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}"
msgstr ""
msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements."
msgstr ""
@ -16349,9 +16343,6 @@ msgstr ""
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
msgid "Found warning in your .gitlab-ci.yml"
msgstr ""
msgid "Framework successfully deleted"
msgstr ""

View file

@ -135,14 +135,14 @@ describe('Comment Field Layout Component', () => {
});
});
describe('issue has email participants, but note is confidential', () => {
describe('issue has email participants, but note is internal', () => {
it('does not show EmailParticipantsWarning', () => {
createWrapper({
noteableData: {
...noteableDataMock,
issue_email_participants: [{ email: 'someone@gitlab.com' }],
},
noteIsConfidential: true,
isInternalNote: true,
});
expect(findEmailParticipantsWarning().exists()).toBe(false);

View file

@ -32,7 +32,7 @@ describe('issue_comment_form component', () => {
const findTextArea = () => wrapper.findByTestId('comment-field');
const findAddToReviewButton = () => wrapper.findByTestId('add-to-review-button');
const findAddCommentNowButton = () => wrapper.findByTestId('add-comment-now-button');
const findConfidentialNoteCheckbox = () => wrapper.findByTestId('confidential-note-checkbox');
const findConfidentialNoteCheckbox = () => wrapper.findByTestId('internal-note-checkbox');
const findCommentTypeDropdown = () => wrapper.findComponent(CommentTypeDropdown);
const findCommentButton = () => findCommentTypeDropdown().find('button');
const findErrorAlerts = () => wrapper.findAllComponents(GlAlert).wrappers;
@ -249,15 +249,15 @@ describe('issue_comment_form component', () => {
describe('textarea', () => {
describe('general', () => {
it.each`
noteType | confidential | placeholder
${'comment'} | ${false} | ${'Write a comment or drag your files here…'}
${'internal note'} | ${true} | ${'Write an internal note or drag your files here…'}
noteType | noteIsInternal | placeholder
${'comment'} | ${false} | ${'Write a comment or drag your files here…'}
${'internal note'} | ${true} | ${'Write an internal note or drag your files here…'}
`(
'should render textarea with placeholder for $noteType',
({ confidential, placeholder }) => {
({ noteIsInternal, placeholder }) => {
mountComponent({
mountFunction: mount,
initialData: { noteIsConfidential: confidential },
initialData: { noteIsInternal },
});
expect(findTextArea().attributes('placeholder')).toBe(placeholder);
@ -389,14 +389,14 @@ describe('issue_comment_form component', () => {
});
it.each`
confidential | buttonText
${false} | ${'Comment'}
${true} | ${'Add internal note'}
`('renders comment button with text "$buttonText"', ({ confidential, buttonText }) => {
noteIsInternal | buttonText
${false} | ${'Comment'}
${true} | ${'Add internal note'}
`('renders comment button with text "$buttonText"', ({ noteIsInternal, buttonText }) => {
mountComponent({
mountFunction: mount,
noteableData: createNotableDataMock({ confidential }),
initialData: { noteIsConfidential: confidential },
noteableData: createNotableDataMock({ confidential: noteIsInternal }),
initialData: { noteIsInternal },
});
expect(findCommentButton().text()).toBe(buttonText);

View file

@ -7,7 +7,7 @@ import NoteAwardsList from '~/notes/components/note_awards_list.vue';
import NoteForm from '~/notes/components/note_form.vue';
import createStore from '~/notes/stores';
import notes from '~/notes/stores/modules/index';
import { CONFIDENTIAL_CLASSES } from '~/notes/constants';
import { INTERNAL_NOTE_CLASSES } from '~/notes/constants';
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
@ -59,20 +59,20 @@ describe('issue_note_body component', () => {
expect(wrapper.findComponent(NoteAwardsList).exists()).toBe(true);
});
it('should not have confidential classes', () => {
expect(wrapper.findByTestId('note-confidential-container').classes()).not.toEqual(
CONFIDENTIAL_CLASSES,
it('should not have internal note classes', () => {
expect(wrapper.findByTestId('note-internal-container').classes()).not.toEqual(
INTERNAL_NOTE_CLASSES,
);
});
describe('isConfidential', () => {
describe('isInternalNote', () => {
beforeEach(() => {
wrapper = createComponent({ props: { isConfidential: true } });
wrapper = createComponent({ props: { isInternalNote: true } });
});
it('should have confidential classes', () => {
expect(wrapper.findByTestId('note-confidential-container').classes()).toEqual(
CONFIDENTIAL_CLASSES,
it('should have internal note classes', () => {
expect(wrapper.findByTestId('note-internal-container').classes()).toEqual(
INTERNAL_NOTE_CLASSES,
);
});
});
@ -106,14 +106,14 @@ describe('issue_note_body component', () => {
expect(wrapper.vm.autosave.key).toEqual(autosaveKey);
});
describe('isConfidential', () => {
describe('isInternalNote', () => {
beforeEach(() => {
wrapper.setProps({ isConfidential: true });
wrapper.setProps({ isInternalNote: true });
});
it('should not have confidential classes', () => {
expect(wrapper.findByTestId('note-confidential-container').classes()).not.toEqual(
CONFIDENTIAL_CLASSES,
it('should not have internal note classes', () => {
expect(wrapper.findByTestId('note-internal-container').classes()).not.toEqual(
INTERNAL_NOTE_CLASSES,
);
});
});

View file

@ -21,7 +21,7 @@ describe('NoteHeader component', () => {
const findActionText = () => wrapper.find({ ref: 'actionText' });
const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' });
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
const findConfidentialIndicator = () => wrapper.findByTestId('internalNoteIndicator');
const findInternalNoteIndicator = () => wrapper.findByTestId('internalNoteIndicator');
const findSpinner = () => wrapper.find({ ref: 'spinner' });
const findAuthorStatus = () => wrapper.find({ ref: 'authorStatus' });
@ -283,20 +283,20 @@ describe('NoteHeader component', () => {
});
});
describe('with confidentiality indicator', () => {
describe('with internal note badge', () => {
it.each`
status | condition
${true} | ${'shows'}
${false} | ${'hides'}
`('$condition icon indicator when isConfidential is $status', ({ status }) => {
createComponent({ isConfidential: status });
expect(findConfidentialIndicator().exists()).toBe(status);
`('$condition badge when isInternalNote is $status', ({ status }) => {
createComponent({ isInternalNote: status });
expect(findInternalNoteIndicator().exists()).toBe(status);
});
it('shows confidential indicator tooltip for project context', () => {
createComponent({ isConfidential: true, noteableType: 'issue' });
it('shows internal note badge tooltip for project context', () => {
createComponent({ isInternalNote: true, noteableType: 'issue' });
expect(findConfidentialIndicator().attributes('title')).toBe(
expect(findInternalNoteIndicator().attributes('title')).toBe(
'This internal note will always remain confidential',
);
});

View file

@ -1,146 +0,0 @@
import VueApollo from 'vue-apollo';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlAlert, GlSprintf } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import DeprecatedTypeKeywordNotification from '~/pipelines/components/notification/deprecated_type_keyword_notification.vue';
import getPipelineWarnings from '~/pipelines/graphql/queries/get_pipeline_warnings.query.graphql';
import {
mockWarningsWithoutDeprecation,
mockWarningsRootType,
mockWarningsType,
mockWarningsTypesAll,
} from './mock_data';
const defaultProvide = {
deprecatedKeywordsDocPath: '/help/ci/yaml/index.md#deprecated-keywords',
fullPath: '/namespace/my-project',
pipelineIid: 4,
};
let wrapper;
const mockWarnings = jest.fn();
const createComponent = ({ isLoading = false, options = {} } = {}) => {
return shallowMount(DeprecatedTypeKeywordNotification, {
stubs: {
GlSprintf,
},
provide: {
...defaultProvide,
},
mocks: {
$apollo: {
queries: {
warnings: {
loading: isLoading,
},
},
},
},
...options,
});
};
const createComponentWithApollo = () => {
const localVue = createLocalVue();
localVue.use(VueApollo);
const handlers = [[getPipelineWarnings, mockWarnings]];
const mockApollo = createMockApollo(handlers);
return createComponent({
options: {
localVue,
apolloProvider: mockApollo,
mocks: {},
},
});
};
const findAlert = () => wrapper.findComponent(GlAlert);
const findAlertItems = () => findAlert().findAll('li');
afterEach(() => {
wrapper.destroy();
});
describe('Deprecated keyword notification', () => {
describe('while loading the pipeline warnings', () => {
beforeEach(() => {
wrapper = createComponent({ isLoading: true });
});
it('does not display the notification', () => {
expect(findAlert().exists()).toBe(false);
});
});
describe('if there is an error in the query', () => {
beforeEach(async () => {
mockWarnings.mockResolvedValue({ errors: ['It didnt work'] });
wrapper = createComponentWithApollo();
await waitForPromises();
});
it('does not display the notification', () => {
expect(findAlert().exists()).toBe(false);
});
});
describe('with a valid query result', () => {
describe('if there are no deprecation warnings', () => {
beforeEach(async () => {
mockWarnings.mockResolvedValue(mockWarningsWithoutDeprecation);
wrapper = createComponentWithApollo();
await waitForPromises();
});
it('does not show the notification', () => {
expect(findAlert().exists()).toBe(false);
});
});
describe('with a root type deprecation message', () => {
beforeEach(async () => {
mockWarnings.mockResolvedValue(mockWarningsRootType);
wrapper = createComponentWithApollo();
await waitForPromises();
});
it('shows the notification with one item', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlertItems()).toHaveLength(1);
expect(findAlertItems().at(0).text()).toContain('types');
});
});
describe('with a job type deprecation message', () => {
beforeEach(async () => {
mockWarnings.mockResolvedValue(mockWarningsType);
wrapper = createComponentWithApollo();
await waitForPromises();
});
it('shows the notification with one item', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlertItems()).toHaveLength(1);
expect(findAlertItems().at(0).text()).toContain('type');
expect(findAlertItems().at(0).text()).not.toContain('types');
});
});
describe('with both the root types and job type deprecation message', () => {
beforeEach(async () => {
mockWarnings.mockResolvedValue(mockWarningsTypesAll);
wrapper = createComponentWithApollo();
await waitForPromises();
});
it('shows the notification with two items', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlertItems()).toHaveLength(2);
expect(findAlertItems().at(0).text()).toContain('types');
expect(findAlertItems().at(1).text()).toContain('type');
expect(findAlertItems().at(1).text()).not.toContain('types');
});
});
});
});

View file

@ -1,4 +1,4 @@
import { GlModal } from '@gitlab/ui';
import { GlModal, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
@ -9,6 +9,8 @@ describe('Test case details', () => {
const defaultTestCase = {
classname: 'spec.test_spec',
name: 'Test#something cool',
file: '~/index.js',
filePath: '/src/javascripts/index.js',
formattedTime: '10.04ms',
recent_failures: {
count: 2,
@ -19,6 +21,8 @@ describe('Test case details', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findName = () => wrapper.findByTestId('test-case-name');
const findFile = () => wrapper.findByTestId('test-case-file');
const findFileLink = () => wrapper.findComponent(GlLink);
const findDuration = () => wrapper.findByTestId('test-case-duration');
const findRecentFailures = () => wrapper.findByTestId('test-case-recent-failures');
const findAttachmentUrl = () => wrapper.findByTestId('test-case-attachment-url');
@ -57,11 +61,26 @@ describe('Test case details', () => {
expect(findName().text()).toBe(defaultTestCase.name);
});
it('renders the test case file', () => {
expect(findFile().text()).toBe(defaultTestCase.file);
expect(findFileLink().attributes('href')).toBe(defaultTestCase.filePath);
});
it('renders the test case duration', () => {
expect(findDuration().text()).toBe(defaultTestCase.formattedTime);
});
});
describe('when test case has execution time instead of formatted time', () => {
beforeEach(() => {
createComponent({ ...defaultTestCase, formattedTime: null, execution_time: 17 });
});
it('renders the test case duration', () => {
expect(findDuration().text()).toBe('17 s');
});
});
describe('when test case has recent failures', () => {
describe('has only 1 recent failure', () => {
it('renders the recent failure', () => {

View file

@ -9,6 +9,7 @@ import axios from '~/lib/utils/axios_utils';
import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container';
import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
import httpStatusCodes from '~/lib/utils/http_status';
import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
import { failedReport } from 'jest/reports/mock_data/mock_data';
import mixedResultsTestReports from 'jest/reports/mock_data/new_and_fixed_failures_report.json';
@ -39,6 +40,7 @@ describe('Test report extension', () => {
const findToggleCollapsedButton = () => wrapper.findByTestId('toggle-button');
const findTertiaryButton = () => wrapper.find(GlButton);
const findAllExtensionListItems = () => wrapper.findAllByTestId('extension-list-item');
const findModal = () => wrapper.find(TestCaseDetails);
const createComponent = () => {
wrapper = mountExtended(extensionsContainer, {
@ -190,4 +192,19 @@ describe('Test report extension', () => {
);
});
});
describe('modal link', () => {
beforeEach(async () => {
await createExpandedWidgetWithData();
wrapper.findByTestId('modal-link').trigger('click');
});
it('opens a modal to display test case details', () => {
expect(findModal().exists()).toBe(true);
expect(findModal().props('testCase')).toMatchObject(
mixedResultsTestReports.suites[0].new_failures[0],
);
});
});
});

View file

@ -134,16 +134,13 @@ describe('Terraform extension', () => {
describe('polling', () => {
let pollRequest;
let pollStop;
beforeEach(() => {
pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
pollStop = jest.spyOn(Poll.prototype, 'stop');
});
afterEach(() => {
pollRequest.mockRestore();
pollStop.mockRestore();
});
describe('successful poll', () => {
@ -155,7 +152,6 @@ describe('Terraform extension', () => {
it('does not make additional requests after poll is successful', () => {
expect(pollRequest).toHaveBeenCalledTimes(1);
expect(pollStop).toHaveBeenCalledTimes(1);
});
});
@ -171,7 +167,6 @@ describe('Terraform extension', () => {
it('does not make additional requests after poll is unsuccessful', () => {
expect(pollRequest).toHaveBeenCalledTimes(1);
expect(pollStop).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -983,35 +983,30 @@ describe('MrWidgetOptions', () => {
describe('mock polling extension', () => {
let pollRequest;
let pollStop;
const findWidgetTestExtension = () => wrapper.find('[data-testid="widget-extension"]');
beforeEach(() => {
pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
pollStop = jest.spyOn(Poll.prototype, 'stop');
registeredExtensions.extensions = [];
});
afterEach(() => {
pollRequest.mockRestore();
pollStop.mockRestore();
registeredExtensions.extensions = [];
// Clear all left-over timeouts that may be registered in the poll class
let id = window.setTimeout(() => {}, 0);
while (id > 0) {
window.clearTimeout(id);
id -= 1;
}
});
describe('success - multi polling', () => {
const findWidgetTestExtension = () => wrapper.find('[data-testid="widget-extension"]');
// Clear all left-over timeouts that may be registered in the poll class
afterEach(() => {
let id = window.setTimeout(() => {}, 0);
while (id > 0) {
window.clearTimeout(id);
id -= 1;
}
});
it('sets data when polling is complete', async () => {
registerExtension(
multiPollingExtension([
@ -1052,21 +1047,34 @@ describe('MrWidgetOptions', () => {
);
await createComponent();
expect(findWidgetTestExtension().html()).toContain('Loading...');
expect(findWidgetTestExtension().html()).toContain('Test extension loading...');
});
});
describe('success', () => {
beforeEach(() => {
it('does not make additional requests after poll is successful', async () => {
registerExtension(pollingExtension);
createComponent();
});
it('does not make additional requests after poll is successful', () => {
await createComponent();
// called two times due to parent component polling (mount) and extension polling
expect(pollRequest).toHaveBeenCalledTimes(2);
expect(pollStop).toHaveBeenCalledTimes(1);
});
it('keeps polling when poll-interval header is provided', async () => {
registerExtension({
...pollingExtension,
methods: {
...pollingExtension.methods,
fetchCollapsedData() {
return Promise.resolve({
data: {},
headers: { 'poll-interval': 1 },
status: 204,
});
},
},
});
await createComponent();
expect(findWidgetTestExtension().html()).toContain('Test extension loading...');
});
});
@ -1084,7 +1092,6 @@ describe('MrWidgetOptions', () => {
it('does not make additional requests after poll has failed', () => {
// called two times due to parent component polling (mount) and extension polling
expect(pollRequest).toHaveBeenCalledTimes(2);
expect(pollStop).toHaveBeenCalledTimes(1);
});
it('captures sentry error and displays error when poll has failed', () => {

View file

@ -4,11 +4,14 @@ export const workingExtension = (shouldCollapse = true) => ({
name: 'WidgetTestExtension',
props: ['targetProjectFullPath'],
expandEvent: 'test_expand_event',
i18n: {
loading: 'Test extension loading...',
},
computed: {
summary({ count, targetProjectFullPath }) {
summary({ count, targetProjectFullPath } = {}) {
return `Test extension summary count: ${count} & ${targetProjectFullPath}`;
},
statusIcon({ count }) {
statusIcon({ count } = {}) {
return count > 0 ? EXTENSION_ICONS.warning : EXTENSION_ICONS.success;
},
shouldCollapse() {
@ -108,7 +111,10 @@ export const pollingExtension = {
export const multiPollingExtension = (endpointsToBePolled) => ({
name: 'WidgetTestMultiPollingExtension',
props: ['targetProjectFullPath'],
props: [],
i18n: {
loading: 'Test extension loading...',
},
computed: {
summary(data) {
return `Multi polling test extension reports: ${data?.[0]?.reports}, count: ${data.length}`;