Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a331169e6e
commit
2c06e006d8
44 changed files with 1028 additions and 396 deletions
|
@ -1 +1 @@
|
|||
3772b182b5b714770956de4d76f3ce09a220fb85
|
||||
57ac403e3ac18dbfa7e1e72bfb3eb8ab01cea626
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<script>
|
||||
import confetti from 'canvas-confetti';
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
confetti.create(this.$refs.canvas, {
|
||||
resize: true,
|
||||
useWorker: true,
|
||||
disableForReducedMotion: true,
|
||||
});
|
||||
|
||||
this.basicCannon();
|
||||
},
|
||||
methods: {
|
||||
basicCannon() {
|
||||
confetti({
|
||||
particleCount: 100,
|
||||
spread: 70,
|
||||
origin: { y: 0.2 },
|
||||
scalar: 2,
|
||||
shapes: ['square'],
|
||||
colors: ['#FC6D26', '#6B4FBB', '#FDB997'],
|
||||
zIndex: 1045,
|
||||
gravity: 1.5,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas ref="canvas" width="0" height="0"></canvas>
|
||||
</template>
|
|
@ -18,19 +18,21 @@ import ExperimentTracking from '~/experimentation/experiment_tracking';
|
|||
import { sanitize } from '~/lib/dompurify';
|
||||
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
|
||||
import { getParameterValues } from '~/lib/utils/url_utility';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import { sprintf } from '~/locale';
|
||||
import {
|
||||
INVITE_MEMBERS_IN_COMMENT,
|
||||
GROUP_FILTERS,
|
||||
USERS_FILTER_ALL,
|
||||
MEMBER_AREAS_OF_FOCUS,
|
||||
INVITE_MEMBERS_FOR_TASK,
|
||||
MODAL_LABELS,
|
||||
} from '../constants';
|
||||
import eventHub from '../event_hub';
|
||||
import {
|
||||
responseMessageFromError,
|
||||
responseMessageFromSuccess,
|
||||
} from '../utils/response_message_parser';
|
||||
import ModalConfetti from './confetti.vue';
|
||||
import GroupSelect from './group_select.vue';
|
||||
import MembersTokenSelect from './members_token_select.vue';
|
||||
|
||||
|
@ -50,6 +52,7 @@ export default {
|
|||
GlFormCheckboxGroup,
|
||||
MembersTokenSelect,
|
||||
GroupSelect,
|
||||
ModalConfetti,
|
||||
},
|
||||
inject: ['newProjectPath'],
|
||||
props: {
|
||||
|
@ -129,22 +132,30 @@ export default {
|
|||
source: 'unknown',
|
||||
invalidFeedbackMessage: '',
|
||||
isLoading: false,
|
||||
mode: 'default',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isCelebration() {
|
||||
return this.mode === 'celebrate';
|
||||
},
|
||||
validationState() {
|
||||
return this.invalidFeedbackMessage === '' ? null : false;
|
||||
},
|
||||
isInviteGroup() {
|
||||
return this.inviteeType === 'group';
|
||||
},
|
||||
modalTitle() {
|
||||
return this.$options.labels[this.inviteeType].modal[this.mode].title;
|
||||
},
|
||||
introText() {
|
||||
const inviteTo = this.isProject ? 'toProject' : 'toGroup';
|
||||
|
||||
return sprintf(this.$options.labels[this.inviteeType][inviteTo].introText, {
|
||||
return sprintf(this.$options.labels[this.inviteeType][this.inviteTo][this.mode].introText, {
|
||||
name: this.name,
|
||||
});
|
||||
},
|
||||
inviteTo() {
|
||||
return this.isProject ? 'toProject' : 'toGroup';
|
||||
},
|
||||
toastOptions() {
|
||||
return {
|
||||
onComplete: () => {
|
||||
|
@ -234,7 +245,8 @@ export default {
|
|||
usersToAddById.map((user) => user.id).join(','),
|
||||
];
|
||||
},
|
||||
openModal({ inviteeType, source }) {
|
||||
openModal({ mode = 'default', inviteeType, source }) {
|
||||
this.mode = mode;
|
||||
this.inviteeType = inviteeType;
|
||||
this.source = source;
|
||||
|
||||
|
@ -381,60 +393,7 @@ export default {
|
|||
return unescape(sanitize(message, { ALLOWED_TAGS: [] }));
|
||||
},
|
||||
},
|
||||
labels: {
|
||||
members: {
|
||||
modalTitle: s__('InviteMembersModal|Invite members'),
|
||||
searchField: s__('InviteMembersModal|GitLab member or email address'),
|
||||
placeHolder: s__('InviteMembersModal|Select members or type email addresses'),
|
||||
toGroup: {
|
||||
introText: s__(
|
||||
"InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} group.",
|
||||
),
|
||||
},
|
||||
toProject: {
|
||||
introText: s__(
|
||||
"InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} project.",
|
||||
),
|
||||
},
|
||||
tasksToBeDone: {
|
||||
title: s__(
|
||||
'InviteMembersModal|Create issues for your new team member to work on (optional)',
|
||||
),
|
||||
noProjects: s__(
|
||||
'InviteMembersModal|To assign issues to a new team member, you need a project for the issues. %{linkStart}Create a project to get started.%{linkEnd}',
|
||||
),
|
||||
},
|
||||
tasksProject: {
|
||||
title: s__('InviteMembersModal|Choose a project for the issues'),
|
||||
},
|
||||
},
|
||||
group: {
|
||||
modalTitle: s__('InviteMembersModal|Invite a group'),
|
||||
searchField: s__('InviteMembersModal|Select a group to invite'),
|
||||
placeHolder: s__('InviteMembersModal|Search for a group to invite'),
|
||||
toGroup: {
|
||||
introText: s__(
|
||||
"InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} group.",
|
||||
),
|
||||
},
|
||||
toProject: {
|
||||
introText: s__(
|
||||
"InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} project.",
|
||||
),
|
||||
},
|
||||
},
|
||||
accessLevel: s__('InviteMembersModal|Select a role'),
|
||||
accessExpireDate: s__('InviteMembersModal|Access expiration date (optional)'),
|
||||
toastMessageSuccessful: s__('InviteMembersModal|Members were successfully added'),
|
||||
invalidFeedbackMessageDefault: s__('InviteMembersModal|Something went wrong'),
|
||||
readMoreText: s__(`InviteMembersModal|%{linkStart}Read more%{linkEnd} about role permissions`),
|
||||
inviteButtonText: s__('InviteMembersModal|Invite'),
|
||||
cancelButtonText: s__('InviteMembersModal|Cancel'),
|
||||
headerCloseLabel: s__('InviteMembersModal|Close invite team members'),
|
||||
areasOfFocusLabel: s__(
|
||||
'InviteMembersModal|What would you like new member(s) to focus on? (optional)',
|
||||
),
|
||||
},
|
||||
labels: MODAL_LABELS,
|
||||
membersTokenSelectLabelId: 'invite-members-input',
|
||||
};
|
||||
</script>
|
||||
|
@ -445,12 +404,15 @@ export default {
|
|||
size="sm"
|
||||
data-qa-selector="invite_members_modal_content"
|
||||
data-testid="invite-members-modal"
|
||||
:title="$options.labels[inviteeType].modalTitle"
|
||||
:title="modalTitle"
|
||||
:header-close-label="$options.labels.headerCloseLabel"
|
||||
@hidden="resetFields"
|
||||
@close="resetFields"
|
||||
@hide="resetFields"
|
||||
>
|
||||
<div>
|
||||
<div class="gl-display-flex">
|
||||
<div v-if="isCelebration" class="gl-p-4 gl-font-size-h1"><gl-emoji data-name="tada" /></div>
|
||||
<div>
|
||||
<p ref="introText">
|
||||
<gl-sprintf :message="introText">
|
||||
|
@ -458,7 +420,12 @@ export default {
|
|||
<strong>{{ content }}</strong>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<br />
|
||||
<span v-if="isCelebration">{{ $options.labels.members.modal.celebrate.intro }} </span>
|
||||
<modal-confetti v-if="isCelebration" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<gl-form-group
|
||||
:invalid-feedback="invalidFeedbackMessage"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { __ } from '~/locale';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
||||
export const SEARCH_DELAY = 200;
|
||||
|
||||
|
@ -27,3 +27,120 @@ export const USERS_FILTER_ALL = 'all';
|
|||
export const USERS_FILTER_SAML_PROVIDER_ID = 'saml_provider_id';
|
||||
export const TRIGGER_ELEMENT_BUTTON = 'button';
|
||||
export const TRIGGER_ELEMENT_SIDE_NAV = 'side-nav';
|
||||
export const MEMBERS_MODAL_DEFAULT_TITLE = s__('InviteMembersModal|Invite members');
|
||||
export const MEMBERS_MODAL_CELEBRATE_TITLE = s__(
|
||||
'InviteMembersModal|GitLab is better with colleagues!',
|
||||
);
|
||||
export const MEMBERS_MODAL_CELEBRATE_INTRO = s__(
|
||||
'InviteMembersModal|How about inviting a colleague or two to join you?',
|
||||
);
|
||||
export const MEMBERS_TO_GROUP_DEFAULT_INTRO_TEXT = s__(
|
||||
"InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} group.",
|
||||
);
|
||||
|
||||
export const MEMBERS_TO_PROJECT_DEFAULT_INTRO_TEXT = s__(
|
||||
"InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} project.",
|
||||
);
|
||||
export const MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT = s__(
|
||||
"InviteMembersModal|Congratulations on creating your project, you're almost there!",
|
||||
);
|
||||
export const MEMBERS_SEARCH_FIELD = s__('InviteMembersModal|GitLab member or email address');
|
||||
export const MEMBERS_PLACEHOLDER = s__('InviteMembersModal|Select members or type email addresses');
|
||||
export const MEMBERS_TASKS_TO_BE_DONE_TITLE = s__(
|
||||
'InviteMembersModal|Create issues for your new team member to work on (optional)',
|
||||
);
|
||||
export const MEMBERS_TASKS_TO_BE_DONE_NO_PROJECTS = s__(
|
||||
'InviteMembersModal|To assign issues to a new team member, you need a project for the issues. %{linkStart}Create a project to get started.%{linkEnd}',
|
||||
);
|
||||
export const MEMBERS_TASKS_PROJECTS_TITLE = s__(
|
||||
'InviteMembersModal|Choose a project for the issues',
|
||||
);
|
||||
|
||||
export const GROUP_MODAL_DEFAULT_TITLE = s__('InviteMembersModal|Invite a group');
|
||||
export const GROUP_MODAL_TO_GROUP_DEFAULT_INTRO_TEXT = s__(
|
||||
"InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} group.",
|
||||
);
|
||||
export const GROUP_MODAL_TO_PROJECT_DEFAULT_INTRO_TEXT = s__(
|
||||
"InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} project.",
|
||||
);
|
||||
|
||||
export const GROUP_SEARCH_FIELD = s__('InviteMembersModal|Select a group to invite');
|
||||
export const GROUP_PLACEHOLDER = s__('InviteMembersModal|Search for a group to invite');
|
||||
|
||||
export const ACCESS_LEVEL = s__('InviteMembersModal|Select a role');
|
||||
export const ACCESS_EXPIRE_DATE = s__('InviteMembersModal|Access expiration date (optional)');
|
||||
export const TOAST_MESSAGE_SUCCESSFUL = s__('InviteMembersModal|Members were successfully added');
|
||||
export const INVALID_FEEDBACK_MESSAGE_DEFAULT = s__('InviteMembersModal|Something went wrong');
|
||||
export const READ_MORE_TEXT = s__(
|
||||
`InviteMembersModal|%{linkStart}Read more%{linkEnd} about role permissions`,
|
||||
);
|
||||
export const INVITE_BUTTON_TEXT = s__('InviteMembersModal|Invite');
|
||||
export const CANCEL_BUTTON_TEXT = s__('InviteMembersModal|Cancel');
|
||||
export const HEADER_CLOSE_LABEL = s__('InviteMembersModal|Close invite team members');
|
||||
export const AREAS_OF_FOCUS_LABEL = s__(
|
||||
'InviteMembersModal|What would you like new member(s) to focus on? (optional)',
|
||||
);
|
||||
|
||||
export const MODAL_LABELS = {
|
||||
members: {
|
||||
modal: {
|
||||
default: {
|
||||
title: MEMBERS_MODAL_DEFAULT_TITLE,
|
||||
},
|
||||
celebrate: {
|
||||
title: MEMBERS_MODAL_CELEBRATE_TITLE,
|
||||
intro: MEMBERS_MODAL_CELEBRATE_INTRO,
|
||||
},
|
||||
},
|
||||
toGroup: {
|
||||
default: {
|
||||
introText: MEMBERS_TO_GROUP_DEFAULT_INTRO_TEXT,
|
||||
},
|
||||
},
|
||||
toProject: {
|
||||
default: {
|
||||
introText: MEMBERS_TO_PROJECT_DEFAULT_INTRO_TEXT,
|
||||
},
|
||||
celebrate: {
|
||||
introText: MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT,
|
||||
},
|
||||
},
|
||||
searchField: MEMBERS_SEARCH_FIELD,
|
||||
placeHolder: MEMBERS_PLACEHOLDER,
|
||||
tasksToBeDone: {
|
||||
title: MEMBERS_TASKS_TO_BE_DONE_TITLE,
|
||||
noProjects: MEMBERS_TASKS_TO_BE_DONE_NO_PROJECTS,
|
||||
},
|
||||
tasksProject: {
|
||||
title: MEMBERS_TASKS_PROJECTS_TITLE,
|
||||
},
|
||||
},
|
||||
group: {
|
||||
modal: {
|
||||
default: {
|
||||
title: GROUP_MODAL_DEFAULT_TITLE,
|
||||
},
|
||||
},
|
||||
toGroup: {
|
||||
default: {
|
||||
introText: GROUP_MODAL_TO_GROUP_DEFAULT_INTRO_TEXT,
|
||||
},
|
||||
},
|
||||
toProject: {
|
||||
default: {
|
||||
introText: GROUP_MODAL_TO_PROJECT_DEFAULT_INTRO_TEXT,
|
||||
},
|
||||
},
|
||||
searchField: GROUP_SEARCH_FIELD,
|
||||
placeHolder: GROUP_PLACEHOLDER,
|
||||
},
|
||||
accessLevel: ACCESS_LEVEL,
|
||||
accessExpireDate: ACCESS_EXPIRE_DATE,
|
||||
toastMessageSuccessful: TOAST_MESSAGE_SUCCESSFUL,
|
||||
invalidFeedbackMessageDefault: INVALID_FEEDBACK_MESSAGE_DEFAULT,
|
||||
readMoreText: READ_MORE_TEXT,
|
||||
inviteButtonText: INVITE_BUTTON_TEXT,
|
||||
cancelButtonText: CANCEL_BUTTON_TEXT,
|
||||
headerCloseLabel: HEADER_CLOSE_LABEL,
|
||||
areasOfFocusLabel: AREAS_OF_FOCUS_LABEL,
|
||||
};
|
||||
|
|
|
@ -46,7 +46,7 @@ export default {
|
|||
return sprintf(
|
||||
s__(`AdminProjects|
|
||||
You’re about to permanently delete the project %{projectName}, its repository,
|
||||
and all related resources, including issues and merge requests. Once you confirm and press
|
||||
and all related resources, including issues and merge requests. After you confirm and press
|
||||
%{strong_start}Delete project%{strong_end}, it cannot be undone or recovered.`),
|
||||
{
|
||||
projectName: `<strong>${escape(this.projectName)}</strong>`,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlProgressBar, GlSprintf } from '@gitlab/ui';
|
||||
import eventHub from '~/invite_members/event_hub';
|
||||
import { s__ } from '~/locale';
|
||||
import { ACTION_LABELS, ACTION_SECTIONS } from '../constants';
|
||||
import LearnGitlabSectionCard from './learn_gitlab_section_card.vue';
|
||||
|
@ -22,6 +23,11 @@ export default {
|
|||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
inviteMembersOpen: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
maxValue: Object.keys(ACTION_LABELS).length,
|
||||
actionSections: Object.keys(ACTION_SECTIONS),
|
||||
|
@ -33,7 +39,15 @@ export default {
|
|||
return Math.round((this.progressValue / this.$options.maxValue) * 100);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.inviteMembersOpen) {
|
||||
this.openInviteMembersModal('celebrate');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openInviteMembersModal(mode) {
|
||||
eventHub.$emit('openModal', { mode, inviteeType: 'members', source: 'learn-gitlab' });
|
||||
},
|
||||
actionsFor(section) {
|
||||
const actions = Object.fromEntries(
|
||||
Object.entries(this.actions).filter(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import LearnGitlab from '../components/learn_gitlab.vue';
|
||||
|
||||
|
@ -11,15 +12,17 @@ function initLearnGitlab() {
|
|||
|
||||
const actions = convertObjectPropsToCamelCase(JSON.parse(el.dataset.actions));
|
||||
const sections = convertObjectPropsToCamelCase(JSON.parse(el.dataset.sections));
|
||||
const { inviteMembersOpen } = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render(createElement) {
|
||||
return createElement(LearnGitlab, {
|
||||
props: { actions, sections },
|
||||
props: { actions, sections, inviteMembersOpen },
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
initInviteMembersModal();
|
||||
initLearnGitlab();
|
||||
|
|
|
@ -42,7 +42,7 @@ export default {
|
|||
strings: {
|
||||
alertTitle: __('You are about to permanently delete this project'),
|
||||
alertBody: __(
|
||||
'Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc.',
|
||||
'After a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc.',
|
||||
),
|
||||
isNotForkMessage: __(
|
||||
'This project is %{strongStart}NOT%{strongEnd} a fork, and has the following:',
|
||||
|
|
|
@ -7,6 +7,20 @@ module LearnGitlabHelper
|
|||
learn_gitlab_onboarding_available?(project)
|
||||
end
|
||||
|
||||
def learn_gitlab_data(project)
|
||||
{
|
||||
actions: onboarding_actions_data(project).to_json,
|
||||
sections: onboarding_sections_data.to_json
|
||||
}
|
||||
end
|
||||
|
||||
def learn_gitlab_onboarding_available?(project)
|
||||
OnboardingProgress.onboarding?(project.namespace) &&
|
||||
LearnGitlab::Project.new(current_user).available?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def onboarding_actions_data(project)
|
||||
attributes = onboarding_progress(project).attributes.symbolize_keys
|
||||
|
||||
|
@ -42,13 +56,6 @@ module LearnGitlabHelper
|
|||
}
|
||||
end
|
||||
|
||||
def learn_gitlab_onboarding_available?(project)
|
||||
OnboardingProgress.onboarding?(project.namespace) &&
|
||||
LearnGitlab::Project.new(current_user).available?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def action_urls
|
||||
LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) }
|
||||
.merge(LearnGitlab::Onboarding::ACTION_DOC_URLS)
|
||||
|
|
|
@ -12,6 +12,8 @@ class Deployment < ApplicationRecord
|
|||
StatusUpdateError = Class.new(StandardError)
|
||||
StatusSyncError = Class.new(StandardError)
|
||||
|
||||
ARCHIVABLE_OFFSET = 50_000
|
||||
|
||||
belongs_to :project, required: true
|
||||
belongs_to :environment, required: true
|
||||
belongs_to :cluster, class_name: 'Clusters::Cluster', optional: true
|
||||
|
@ -100,6 +102,10 @@ class Deployment < ApplicationRecord
|
|||
deployment.run_after_commit do
|
||||
Deployments::UpdateEnvironmentWorker.perform_async(id)
|
||||
Deployments::LinkMergeRequestWorker.perform_async(id)
|
||||
|
||||
if ::Feature.enabled?(:deployments_archive, deployment.project, default_enabled: :yaml)
|
||||
Deployments::ArchiveInProjectWorker.perform_async(deployment.project_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -133,6 +139,14 @@ class Deployment < ApplicationRecord
|
|||
skipped: 5
|
||||
}
|
||||
|
||||
def self.archivables_in(project, limit:)
|
||||
start_iid = project.deployments.order(iid: :desc).limit(1)
|
||||
.select("(iid - #{ARCHIVABLE_OFFSET}) AS start_iid")
|
||||
|
||||
project.deployments.preload(:environment).where('iid <= (?)', start_iid)
|
||||
.where(archived: false).limit(limit)
|
||||
end
|
||||
|
||||
def self.last_for_environment(environment)
|
||||
ids = self
|
||||
.for_environment(environment)
|
||||
|
|
27
app/services/deployments/archive_in_project_service.rb
Normal file
27
app/services/deployments/archive_in_project_service.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Deployments
|
||||
# This service archives old deploymets and deletes deployment refs for
|
||||
# keeping the project repository performant.
|
||||
class ArchiveInProjectService < ::BaseService
|
||||
BATCH_SIZE = 100
|
||||
|
||||
def execute
|
||||
unless ::Feature.enabled?(:deployments_archive, project, default_enabled: :yaml)
|
||||
return error('Feature flag is not enabled')
|
||||
end
|
||||
|
||||
deployments = Deployment.archivables_in(project, limit: BATCH_SIZE)
|
||||
|
||||
return success(result: :empty) if deployments.empty?
|
||||
|
||||
ids = deployments.map(&:id)
|
||||
ref_paths = deployments.map(&:ref_path)
|
||||
|
||||
project.repository.delete_refs(*ref_paths)
|
||||
project.deployments.id_in(ids).update_all(archived: true)
|
||||
|
||||
success(result: :archived, count: ids.count)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,12 @@
|
|||
- breadcrumb_title _("Learn GitLab")
|
||||
- page_title _("Learn GitLab")
|
||||
- add_page_specific_style 'page_bundles/learn_gitlab'
|
||||
- data = learn_gitlab_data(@project)
|
||||
- invite_members_open = session.delete(:confetti_post_signup)
|
||||
|
||||
#js-learn-gitlab-app{ data: { actions: onboarding_actions_data(@project).to_json, sections: onboarding_sections_data.to_json } }
|
||||
- experiment(:confetti_post_signup, actor: current_user) do |e|
|
||||
- e.control do
|
||||
#js-learn-gitlab-app{ data: data }
|
||||
- e.candidate do
|
||||
= render 'projects/invite_members_modal', project: @project
|
||||
#js-learn-gitlab-app{ data: data.merge(invite_members_open: invite_members_open) }
|
||||
|
|
|
@ -723,6 +723,15 @@
|
|||
:weight: 1
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: deployment:deployments_archive_in_project
|
||||
:worker_name: Deployments::ArchiveInProjectWorker
|
||||
:feature_category: :continuous_delivery
|
||||
:has_external_dependencies:
|
||||
:urgency: :low
|
||||
:resource_boundary: :unknown
|
||||
:weight: 3
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: deployment:deployments_drop_older_deployments
|
||||
:worker_name: Deployments::DropOlderDeploymentsWorker
|
||||
:feature_category: :continuous_delivery
|
||||
|
|
19
app/workers/deployments/archive_in_project_worker.rb
Normal file
19
app/workers/deployments/archive_in_project_worker.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Deployments
|
||||
class ArchiveInProjectWorker
|
||||
include ApplicationWorker
|
||||
|
||||
queue_namespace :deployment
|
||||
feature_category :continuous_delivery
|
||||
idempotent!
|
||||
deduplicate :until_executed, including_scheduled: true
|
||||
data_consistency :delayed
|
||||
|
||||
def perform(project_id)
|
||||
Project.find_by_id(project_id).try do |project|
|
||||
Deployments::ArchiveInProjectService.new(project, nil).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
8
config/feature_flags/development/deployments_archive.yml
Normal file
8
config/feature_flags/development/deployments_archive.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: deployments_archive
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73628
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345027
|
||||
milestone: '14.5'
|
||||
type: development
|
||||
group: group::release
|
||||
default_enabled: false
|
8
config/feature_flags/experiment/confetti_post_signup.yml
Normal file
8
config/feature_flags/experiment/confetti_post_signup.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: confetti_post_signup
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70011
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339890
|
||||
milestone: '14.5'
|
||||
type: experiment
|
||||
group: group::expansion
|
||||
default_enabled: false
|
|
@ -1,9 +1,9 @@
|
|||
- name: "Certificate-based integration with Kubernetes"
|
||||
announcement_milestone: "14.5" # MIGHT CHANGE IF WE RELEASE THE ANNOUNCEMENT BLOG POST BEFORE THE 22ND
|
||||
announcement_milestone: "14.5"
|
||||
announcement_date: "2021-11-15"
|
||||
removal_milestone: "15.0"
|
||||
body: |
|
||||
We are deprecating the certificate-based integration with Kubernetes and the features that rely on it.
|
||||
[We are deprecating the certificate-based integration with Kubernetes](https://about.gitlab.com/blog/2021/11/15/deprecating-the-cert-based-kubernetes-integration/).
|
||||
The timeline of removal of the integration from the product is not yet planned and we will communicate
|
||||
more details as they emerge. The certificate-based integration will continue to receive security and
|
||||
critical fixes, and features built on the integration will continue to work with supported Kubernetes
|
||||
|
|
9
data/deprecations/14-5-deprecate-opensuse-15-2.yml
Normal file
9
data/deprecations/14-5-deprecate-opensuse-15-2.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
- name: "openSUSE Leap 15.2 packages" # The name of the feature to be deprecated
|
||||
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
|
||||
announcement_date: "2021-11-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
|
||||
removal_milestone: "14.8" # The milestone when this feature is planned to be removed
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
Distribution support and security updates for openSUSE Leap 15.2 are [ending December 2021](https://en.opensuse.org/Lifetime#openSUSE_Leap).
|
||||
|
||||
Starting in 14.5 we are providing packages for openSUSE Leap 15.3, and will stop providing packages for openSUSE Leap 15.2 in the 14.8 milestone.
|
||||
issue_url: https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6427
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddArchivedColumnToDeployments < Gitlab::Database::Migration[1.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :deployments, :archived, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexOnUnarchivedDeployments < Gitlab::Database::Migration[1.0]
|
||||
INDEX_NAME = 'index_deployments_on_archived_project_id_iid'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :deployments, %i[archived project_id iid], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :deployments, INDEX_NAME
|
||||
end
|
||||
end
|
1
db/schema_migrations/20211105010101
Normal file
1
db/schema_migrations/20211105010101
Normal file
|
@ -0,0 +1 @@
|
|||
26c534cdae8630e3f28ad2b61a1049aaab5c5b7a1b761f0961831b621e148ed3
|
1
db/schema_migrations/20211110010101
Normal file
1
db/schema_migrations/20211110010101
Normal file
|
@ -0,0 +1 @@
|
|||
63495b9f9ca2d4fa121b75eea36f2923942a6e11f27bef2c51414e00ccd48973
|
|
@ -13375,7 +13375,8 @@ CREATE TABLE deployments (
|
|||
status smallint NOT NULL,
|
||||
finished_at timestamp with time zone,
|
||||
cluster_id integer,
|
||||
deployable_id bigint
|
||||
deployable_id bigint,
|
||||
archived boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE deployments_id_seq
|
||||
|
@ -25625,6 +25626,8 @@ CREATE UNIQUE INDEX index_deployment_clusters_on_cluster_id_and_deployment_id ON
|
|||
|
||||
CREATE INDEX index_deployment_merge_requests_on_merge_request_id ON deployment_merge_requests USING btree (merge_request_id);
|
||||
|
||||
CREATE INDEX index_deployments_on_archived_project_id_iid ON deployments USING btree (archived, project_id, iid);
|
||||
|
||||
CREATE INDEX index_deployments_on_cluster_id_and_status ON deployments USING btree (cluster_id, status);
|
||||
|
||||
CREATE INDEX index_deployments_on_created_at ON deployments USING btree (created_at);
|
||||
|
|
|
@ -7,22 +7,18 @@ type: howto
|
|||
|
||||
# GitLab CI/CD instance configuration **(FREE SELF)**
|
||||
|
||||
GitLab CI/CD is enabled by default in all new projects on an instance. You can configure
|
||||
the instance to have [GitLab CI/CD disabled by default](#disable-gitlab-cicd-in-new-projects)
|
||||
in new projects.
|
||||
|
||||
You can still choose to [enable GitLab CI/CD in individual projects](../ci/enable_or_disable_ci.md#enable-cicd-in-a-project)
|
||||
at any time.
|
||||
GitLab administrators can manage the GitLab CI/CD configuration for their instance.
|
||||
|
||||
## Disable GitLab CI/CD in new projects
|
||||
|
||||
You can set GitLab CI/CD to be disabled by default in all new projects by modifying the settings in:
|
||||
GitLab CI/CD is enabled by default in all new projects on an instance. You can set
|
||||
CI/CD to be disabled by default in new projects by modifying the settings in:
|
||||
|
||||
- `gitlab.yml` for source installations.
|
||||
- `gitlab.rb` for Omnibus GitLab installations.
|
||||
|
||||
Existing projects that already had CI/CD enabled are unchanged. Also, this setting only changes
|
||||
the project default, so project owners can still enable CI/CD in the project settings.
|
||||
the project default, so project owners [can still enable CI/CD in the project settings](../ci/enable_or_disable_ci.md#enable-cicd-in-a-project).
|
||||
|
||||
For installations from source:
|
||||
|
||||
|
@ -62,6 +58,19 @@ For Omnibus GitLab installations:
|
|||
sudo gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
## Set the `needs:` job limit **(FREE SELF)**
|
||||
|
||||
The maximum number of jobs that can be defined in `needs:` defaults to 50.
|
||||
|
||||
A GitLab administrator with [access to the GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session)
|
||||
can choose a custom limit. For example, to set the limit to `100`:
|
||||
|
||||
```ruby
|
||||
Plan.default.actual_limits.update!(ci_needs_size_limit: 100)
|
||||
```
|
||||
|
||||
To disable directed acyclic graphs (DAG), set the limit to `0`.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
|
|
|
@ -589,6 +589,43 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
|
|||
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/:id/members/:user_id"
|
||||
```
|
||||
|
||||
## Approve a member for a group
|
||||
|
||||
Approves a pending user for a group and its subgroups and projects.
|
||||
|
||||
```plaintext
|
||||
PUT /groups/:id/members/:user_id/approve
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the root group](index.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `user_id` | integer | yes | The user ID of the member |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id/approve"
|
||||
```
|
||||
|
||||
## Approve all pending members for a group
|
||||
|
||||
Approves all pending users for a group and its subgroups and projects.
|
||||
|
||||
```plaintext
|
||||
POST /groups/:id/members/approve_all
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the root group](index.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/approve_all"
|
||||
```
|
||||
|
||||
## Give a group access to a project
|
||||
|
||||
See [share project with group](projects.md#share-project-with-group)
|
||||
|
|
|
@ -74,9 +74,9 @@ giving you powerful options for parallelization within your pipeline.
|
|||
## Limitations
|
||||
|
||||
A directed acyclic graph is a complicated feature, and as of the initial MVC there
|
||||
are certain use cases that you may need to work around. For more information:
|
||||
are certain use cases that you may need to work around. For more information, check the:
|
||||
|
||||
- [`needs` requirements and limitations](../yaml/index.md#requirements-and-limitations).
|
||||
- [`needs` additional details](../yaml/index.md#needs).
|
||||
- Related epic [tracking planned improvements](https://gitlab.com/groups/gitlab-org/-/epics/1716).
|
||||
|
||||
## Needs Visualization
|
||||
|
|
|
@ -714,6 +714,29 @@ fetch line:
|
|||
fetch = +refs/environments/*:refs/remotes/origin/environments/*
|
||||
```
|
||||
|
||||
### Archive Old Deployments
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73628) in GitLab 14.5.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `deployments_archive`. On GitLab.com, this feature will be rolled out gradually.
|
||||
|
||||
When a new deployment happens in your project,
|
||||
GitLab creates [a special Git-ref to the deployment](#check-out-deployments-locally).
|
||||
Since these Git-refs are populated from the remote GitLab repository,
|
||||
you could find that some Git operations, such as `git-fetch` and `git-pull`,
|
||||
become slower as the number of deployments in your project increases.
|
||||
|
||||
To maintain the efficiency of your Git operations, GitLab keeps
|
||||
only recent deployment refs (up to 50,000) and deletes the rest of the old deployment refs.
|
||||
Archived deployments are still available, in the UI or by using the API, for auditing purposes.
|
||||
Also, you can still fetch the deployed commit from the repository
|
||||
with specifying the commit SHA (for example, `git checkout <deployment-sha>`), even after archive.
|
||||
|
||||
NOTE:
|
||||
GitLab preserves all commits as [`keep-around` refs](../../user/project/repository/reducing_the_repo_size_using_git.md)
|
||||
so that deployed commits are not garbage collected, even if it's not referenced by the deployment refs.
|
||||
|
||||
### Scope environments with specs
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2112) in GitLab Premium 9.4.
|
||||
|
@ -911,3 +934,19 @@ To fix this, use one of the following solutions:
|
|||
script: deploy review app
|
||||
environment: review/$CI_COMMIT_REF_SLUG
|
||||
```
|
||||
|
||||
### Deployment refs are not found
|
||||
|
||||
Starting from GitLab 14.5, GitLab [deletes old deployment refs](#archive-old-deployments)
|
||||
to keep your Git repository performant.
|
||||
|
||||
If you have to restore archived Git-refs, please ask an administrator of your self-managed GitLab instance
|
||||
to execute the following command on Rails console:
|
||||
|
||||
```ruby
|
||||
Project.find_by_full_path(<your-project-full-path>).deployments.where(archived: true).each(&:create_ref)
|
||||
```
|
||||
|
||||
Please note that GitLab could drop this support in the future for the performance concern.
|
||||
You can open an issue in [GitLab Issue Tracker](https://gitlab.com/gitlab-org/gitlab/-/issues/new)
|
||||
to discuss the behavior of this feature.
|
||||
|
|
|
@ -88,7 +88,7 @@ The keywords available for use in trigger jobs are:
|
|||
- [`only` and `except`](../yaml/index.md#only--except)
|
||||
- [`when`](../yaml/index.md#when) (only with a value of `on_success`, `on_failure`, or `always`)
|
||||
- [`extends`](../yaml/index.md#extends)
|
||||
- [`needs`](../yaml/index.md#needs), but not [cross project artifact downloads with `needs`](../yaml/index.md#cross-project-artifact-downloads-with-needs)
|
||||
- [`needs`](../yaml/index.md#needs), but not [`needs:project`](../yaml/index.md#needsproject)
|
||||
|
||||
#### Specify a downstream pipeline branch
|
||||
|
||||
|
@ -190,7 +190,7 @@ the ones defined in the upstream project take precedence.
|
|||
|
||||
#### Pass CI/CD variables to a downstream pipeline by using variable inheritance
|
||||
|
||||
You can pass variables to a downstream pipeline with [`dotenv` variable inheritance](../variables/index.md#pass-an-environment-variable-to-another-job) and [cross project artifact downloads](../yaml/index.md#cross-project-artifact-downloads-with-needs).
|
||||
You can pass variables to a downstream pipeline with [`dotenv` variable inheritance](../variables/index.md#pass-an-environment-variable-to-another-job) and [`needs:project`](../yaml/index.md#needsproject).
|
||||
|
||||
In the upstream pipeline:
|
||||
|
||||
|
@ -254,21 +254,6 @@ trigger_job:
|
|||
strategy: depend
|
||||
```
|
||||
|
||||
#### Mirror status from upstream pipeline
|
||||
|
||||
You can mirror the pipeline status from an upstream pipeline to a bridge job by
|
||||
using the `needs:pipeline` keyword. The latest pipeline status from the default branch is
|
||||
replicated to the bridge job.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
upstream_bridge:
|
||||
stage: test
|
||||
needs:
|
||||
pipeline: other/project
|
||||
```
|
||||
|
||||
### Create multi-project pipelines by using the API
|
||||
|
||||
> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/31573) to GitLab Free in 12.4.
|
||||
|
|
|
@ -12,11 +12,14 @@ Autoscaling means reduced queue times to spin up CI/CD jobs, and isolated VMs fo
|
|||
|
||||
GitLab offers Ultimate tier capabilities and included CI/CD minutes per group per month for our [Open Source](https://about.gitlab.com/solutions/open-source/join/), [Education](https://about.gitlab.com/solutions/education/), and [Startups](https://about.gitlab.com/solutions/startups/) programs. For private projects, GitLab offers various [plans](https://about.gitlab.com/pricing/), starting with a Free tier.
|
||||
|
||||
All your CI/CD jobs run on [n1-standard-1 instances](https://cloud.google.com/compute/docs/machine-types) with 3.75GB of RAM, CoreOS and the latest Docker Engine
|
||||
All your CI/CD jobs run on [n1-standard-1 instances](https://cloud.google.com/compute/docs/machine-types) with 3.75GB of RAM, Google COS and the latest Docker Engine
|
||||
installed. Instances provide 1 vCPU and 25GB of HDD disk space. The default
|
||||
region of the VMs is US East1.
|
||||
Each instance is used only for one job. This ensures that any sensitive data left on the system can't be accessed by other people's CI/CD jobs.
|
||||
|
||||
NOTE:
|
||||
The final disk space your jobs can use will be less than 25GB. Some disk space allocated to the instance will be occupied by the operating system, the Docker image, and a copy of your cloned repository.
|
||||
|
||||
The `gitlab-shared-runners-manager-X.gitlab.com` fleet of runners are dedicated for GitLab projects as well as community forks of them. They use a slightly larger machine type (n1-standard-2) and have a bigger SSD disk size. They don't run untagged jobs and unlike the general fleet of shared runners, the instances are re-used up to 40 times.
|
||||
|
||||
Jobs handled by the shared runners on GitLab.com (`shared-runners-manager-X.gitlab.com`),
|
||||
|
|
|
@ -557,7 +557,7 @@ they can be used in job scripts.
|
|||
1. Save the `.env` file as an [`artifacts:reports:dotenv`](../yaml/index.md#artifactsreportsdotenv)
|
||||
artifact.
|
||||
1. Set a job in a later stage to receive the artifact by using the [`dependencies`](../yaml/index.md#dependencies)
|
||||
or the [`needs`](../yaml/index.md#artifact-downloads-with-needs) keywords.
|
||||
or the [`needs`](../yaml/index.md#needs) keywords.
|
||||
1. The later job can then [use the variable in scripts](#use-cicd-variables-in-job-scripts).
|
||||
|
||||
For example, with the [`dependencies`](../yaml/index.md#dependencies) keyword:
|
||||
|
@ -579,7 +579,7 @@ deploy:
|
|||
- build
|
||||
```
|
||||
|
||||
For example, with the [`needs`](../yaml/index.md#artifact-downloads-with-needs) keyword:
|
||||
For example, with the [`needs:artifacts`](../yaml/index.md#needsartifacts) keyword:
|
||||
|
||||
```yaml
|
||||
build:
|
||||
|
|
|
@ -1364,16 +1364,14 @@ that use `needs` can be visualized as a [directed acyclic graph](../directed_acy
|
|||
You can ignore stage ordering and run some jobs without waiting for others to complete.
|
||||
Jobs in multiple stages can run concurrently.
|
||||
|
||||
The following example creates four paths of execution:
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job.
|
||||
|
||||
- Linter: the `lint` job runs immediately without waiting for the `build` stage
|
||||
to complete because it has no needs (`needs: []`).
|
||||
- Linux path: the `linux:rspec` and `linux:rubocop` jobs runs as soon as the `linux:build`
|
||||
job finishes without waiting for `mac:build` to finish.
|
||||
- macOS path: the `mac:rspec` and `mac:rubocop` jobs runs as soon as the `mac:build`
|
||||
job finishes, without waiting for `linux:build` to finish.
|
||||
- The `production` job runs as soon as all previous jobs finish; in this case:
|
||||
`linux:build`, `linux:rspec`, `linux:rubocop`, `mac:build`, `mac:rspec`, `mac:rubocop`.
|
||||
**Possible inputs**:
|
||||
|
||||
- An array of jobs.
|
||||
- An empty array (`[]`), to set the job to start as soon as the pipeline is created.
|
||||
|
||||
**Example of `needs`**:
|
||||
|
||||
```yaml
|
||||
linux:build:
|
||||
|
@ -1394,32 +1392,33 @@ linux:rspec:
|
|||
needs: ["linux:build"]
|
||||
script: echo "Running rspec on linux..."
|
||||
|
||||
linux:rubocop:
|
||||
stage: test
|
||||
needs: ["linux:build"]
|
||||
script: echo "Running rubocop on linux..."
|
||||
|
||||
mac:rspec:
|
||||
stage: test
|
||||
needs: ["mac:build"]
|
||||
script: echo "Running rspec on mac..."
|
||||
|
||||
mac:rubocop:
|
||||
stage: test
|
||||
needs: ["mac:build"]
|
||||
script: echo "Running rubocop on mac..."
|
||||
|
||||
production:
|
||||
stage: deploy
|
||||
script: echo "Running production..."
|
||||
```
|
||||
|
||||
#### Requirements and limitations
|
||||
This example creates four paths of execution:
|
||||
|
||||
- The maximum number of jobs that a single job can need in the `needs:` array is limited:
|
||||
- Linter: The `lint` job runs immediately without waiting for the `build` stage
|
||||
to complete because it has no needs (`needs: []`).
|
||||
- Linux path: The `linux:rspec` job runs as soon as the `linux:build`
|
||||
job finishes, without waiting for `mac:build` to finish.
|
||||
- macOS path: The `mac:rspec` jobs runs as soon as the `mac:build`
|
||||
job finishes, without waiting for `linux:build` to finish.
|
||||
- The `production` job runs as soon as all previous jobs finish:
|
||||
`linux:build`, `linux:rspec`, `mac:build`, `mac:rspec`.
|
||||
|
||||
**Additional details**:
|
||||
|
||||
- The maximum number of jobs that a single job can have in the `needs:` array is limited:
|
||||
- For GitLab.com, the limit is 50. For more information, see our
|
||||
[infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/7541).
|
||||
- For self-managed instances, the default limit is 50. This limit [can be changed](#changing-the-needs-job-limit).
|
||||
- For self-managed instances, the default limit is 50. This limit [can be changed](../../administration/cicd.md#set-the-needs-job-limit).
|
||||
- If `needs:` refers to a job that uses the [`parallel`](#parallel) keyword,
|
||||
it depends on all jobs created in parallel, not just one job. It also downloads
|
||||
artifacts from all the parallel jobs by default. If the artifacts have the same
|
||||
|
@ -1434,20 +1433,7 @@ production:
|
|||
- In GitLab 13.9 and older, if `needs:` refers to a job that might not be added to
|
||||
a pipeline because of `only`, `except`, or `rules`, the pipeline might fail to create.
|
||||
|
||||
##### Changing the `needs:` job limit **(FREE SELF)**
|
||||
|
||||
The maximum number of jobs that can be defined in `needs:` defaults to 50.
|
||||
|
||||
A GitLab administrator with [access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can choose a custom limit. For example, to set the limit to 100:
|
||||
|
||||
```ruby
|
||||
Plan.default.actual_limits.update!(ci_needs_size_limit: 100)
|
||||
```
|
||||
|
||||
To disable directed acyclic graphs (DAG), set the limit to `0`.
|
||||
|
||||
#### Artifact downloads with `needs`
|
||||
#### `needs:artifacts`
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14311) in GitLab 12.6.
|
||||
|
||||
|
@ -1458,55 +1444,72 @@ by default, because jobs with `needs` can start before earlier stages complete.
|
|||
Use `artifacts: true` (default) or `artifacts: false` to control when artifacts are
|
||||
downloaded in jobs that use `needs`.
|
||||
|
||||
In the following example, the `rspec` job downloads the `build_job` artifacts, but the
|
||||
`rubocop` job does not:
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job. Must be used with `needs:job`.
|
||||
|
||||
**Possible inputs**:
|
||||
|
||||
- `true` (default) or `false`.
|
||||
|
||||
**Example of `needs:artifacts`**:
|
||||
|
||||
```yaml
|
||||
build_job:
|
||||
stage: build
|
||||
artifacts:
|
||||
paths:
|
||||
- binaries/
|
||||
|
||||
rspec:
|
||||
test-job1:
|
||||
stage: test
|
||||
needs:
|
||||
- job: build_job
|
||||
- job: build_job1
|
||||
artifacts: true
|
||||
|
||||
rubocop:
|
||||
test-job2:
|
||||
stage: test
|
||||
needs:
|
||||
- job: build_job
|
||||
- job: build_job2
|
||||
artifacts: false
|
||||
```
|
||||
|
||||
In the following example, the `rspec` job downloads the artifacts from all three `build_jobs`.
|
||||
`artifacts` is:
|
||||
|
||||
- Set to true for `build_job_1`.
|
||||
- Defaults to true for both `build_job_2` and `build_job_3`.
|
||||
|
||||
```yaml
|
||||
rspec:
|
||||
test-job3:
|
||||
needs:
|
||||
- job: build_job_1
|
||||
- job: build_job1
|
||||
artifacts: true
|
||||
- job: build_job_2
|
||||
- build_job_3
|
||||
- job: build_job2
|
||||
- build_job3
|
||||
```
|
||||
|
||||
In GitLab 12.6 and later, you can't combine the [`dependencies`](#dependencies) keyword
|
||||
In this example:
|
||||
|
||||
- The `test-job1` job downloads the `build_job1` artifacts
|
||||
- The `test-job2` job does not download the `build_job2` artifacts.
|
||||
- The `test-job3` job downloads the artifacts from all three `build_jobs`, because
|
||||
`artifacts:` is `true`, or defaults to `true`, for all three needed jobs.
|
||||
|
||||
**Additional details**:
|
||||
|
||||
- In GitLab 12.6 and later, you can't combine the [`dependencies`](#dependencies) keyword
|
||||
with `needs`.
|
||||
|
||||
#### Cross project artifact downloads with `needs` **(PREMIUM)**
|
||||
#### `needs:project` **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14311) in GitLab 12.7.
|
||||
|
||||
Use `needs` to download artifacts from up to five jobs in pipelines:
|
||||
Use `needs:project` to download artifacts from up to five jobs in other pipelines.
|
||||
The artifacts are downloaded from the latest successful pipeline for the specified ref.
|
||||
|
||||
- [On other refs in the same project](#artifact-downloads-between-pipelines-in-the-same-project).
|
||||
- In different projects, groups and namespaces.
|
||||
If there is a pipeline running for the specified ref, a job with `needs:project`
|
||||
does not wait for the pipeline to complete. Instead, the job downloads the artifact
|
||||
from the latest pipeline that completed successfully.
|
||||
|
||||
`needs:project` must be used with `job:`, `ref:`, and `artifacts:`.
|
||||
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job.
|
||||
|
||||
**Possible inputs**:
|
||||
|
||||
- `needs:project`: A full project path, including namespace and group. If the
|
||||
project is in the same group or namespace, you can omit them from the `project:`
|
||||
keyword. For example: `project: group/project-name` or `project: project-name`.
|
||||
- `job`: The job to download artifacts from.
|
||||
- `ref`: The ref to download artifacts from.
|
||||
- `artifacts`: Must be `true` to download artifacts.
|
||||
|
||||
**Examples of `needs:project`**:
|
||||
|
||||
```yaml
|
||||
build_job:
|
||||
|
@ -1520,39 +1523,11 @@ build_job:
|
|||
artifacts: true
|
||||
```
|
||||
|
||||
`build_job` downloads the artifacts from the latest successful `build-1` job
|
||||
on the `main` branch in the `group/project-name` project. If the project is in the
|
||||
same group or namespace, you can omit them from the `project:` keyword. For example,
|
||||
`project: group/project-name` or `project: project-name`.
|
||||
In this example, `build_job` downloads the artifacts from the latest successful `build-1` job
|
||||
on the `main` branch in the `group/project-name` project.
|
||||
|
||||
The user running the pipeline must have at least `reporter` access to the group or project, or the group/project must have public visibility.
|
||||
|
||||
You cannot use cross project artifact downloads in the same job as [`trigger`](#trigger).
|
||||
|
||||
##### Artifact downloads between pipelines in the same project
|
||||
|
||||
Use `needs` to download artifacts from different pipelines in the current project.
|
||||
Set the `project` keyword as the current project's name, and specify a ref.
|
||||
|
||||
In the following example, `build_job` downloads the artifacts for the latest successful
|
||||
`build-1` job with the `other-ref` ref:
|
||||
|
||||
```yaml
|
||||
build_job:
|
||||
stage: build
|
||||
script:
|
||||
- ls -lhR
|
||||
needs:
|
||||
- project: group/same-project-name
|
||||
job: build-1
|
||||
ref: other-ref
|
||||
artifacts: true
|
||||
```
|
||||
|
||||
CI/CD variable support for `project:`, `job:`, and `ref` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202093)
|
||||
in GitLab 13.3. [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235761) in GitLab 13.4.
|
||||
|
||||
For example:
|
||||
In GitLab 13.3 and later, you can use [CI/CD variables](../variables/index.md) in `needs:project`,
|
||||
for example:
|
||||
|
||||
```yaml
|
||||
build_job:
|
||||
|
@ -1566,27 +1541,45 @@ build_job:
|
|||
artifacts: true
|
||||
```
|
||||
|
||||
You can't download artifacts from jobs that run in [`parallel:`](#parallel).
|
||||
**Additional details**:
|
||||
|
||||
When using `needs` to download artifacts from another pipeline, the job does not wait for
|
||||
- To download artifacts from a different pipeline in the current project, set `project:`
|
||||
to be the same as the current project, but use a different ref than the current pipeline.
|
||||
Concurrent pipelines running on the same ref could override the artifacts.
|
||||
- The user running the pipeline must have at least the Reporter role for the group or project,
|
||||
or the group/project must have public visibility.
|
||||
- You can't use `needs:project` in the same job as [`trigger`](#trigger).
|
||||
- When using `needs:project` to download artifacts from another pipeline, the job does not wait for
|
||||
the needed job to complete. [Directed acyclic graph](../directed_acyclic_graph/index.md)
|
||||
behavior is limited to jobs in the same pipeline. Make sure that the needed job in the other
|
||||
pipeline completes before the job that needs it tries to download the artifacts.
|
||||
- You can't download artifacts from jobs that run in [`parallel:`](#parallel).
|
||||
- Support for [CI/CD variables](../variables/index.md) in `project`, `job`, and `ref` was
|
||||
[introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202093) in GitLab 13.3.
|
||||
[Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235761) in GitLab 13.4.
|
||||
|
||||
To download artifacts between [parent-child pipelines](../pipelines/parent_child_pipelines.md),
|
||||
use [`needs:pipeline`](#artifact-downloads-to-child-pipelines).
|
||||
**Related topics**:
|
||||
|
||||
You should not download artifacts from the same ref as a running pipeline. Concurrent
|
||||
pipelines running on the same ref could override the artifacts.
|
||||
- To download artifacts between [parent-child pipelines](../pipelines/parent_child_pipelines.md),
|
||||
use [`needs:pipeline:job`](#needspipelinejob).
|
||||
|
||||
#### Artifact downloads to child pipelines
|
||||
#### `needs:pipeline:job`
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/255983) in GitLab 13.7.
|
||||
|
||||
A [child pipeline](../pipelines/parent_child_pipelines.md) can download artifacts from a job in
|
||||
its parent pipeline or another child pipeline in the same parent-child pipeline hierarchy.
|
||||
|
||||
For example, with the following parent pipeline that has a job that creates some artifacts:
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job.
|
||||
|
||||
**Possible inputs**:
|
||||
|
||||
- `needs:pipeline`: A pipeline ID. Must be a pipeline present in the same parent-child pipeline hierarchy.
|
||||
- `job:`: The job to download artifacts from.
|
||||
|
||||
**Example of `needs:pipeline:job`**:
|
||||
|
||||
- Parent pipeline (`.gitlab-ci.yml`):
|
||||
|
||||
```yaml
|
||||
create-artifact:
|
||||
|
@ -1604,8 +1597,7 @@ child-pipeline:
|
|||
PARENT_PIPELINE_ID: $CI_PIPELINE_ID
|
||||
```
|
||||
|
||||
A job in the child pipeline can download artifacts from the `create-artifact` job in
|
||||
the parent pipeline:
|
||||
- Child pipeline (`child.yml`):
|
||||
|
||||
```yaml
|
||||
use-artifact:
|
||||
|
@ -1615,13 +1607,17 @@ use-artifact:
|
|||
job: create-artifact
|
||||
```
|
||||
|
||||
The `pipeline` attribute accepts a pipeline ID and it must be a pipeline present
|
||||
in the same parent-child pipeline hierarchy of the given pipeline.
|
||||
In this example, the `create-artifact` job in the parent pipeline creates some artifacts.
|
||||
The `child-pipeline` job triggers a child pipeline, and passes the `CI_PIPELINE_ID`
|
||||
variable to the child pipeline as a new `PARENT_PIPELINE_ID` variable. The child pipeline
|
||||
can use that variable in `needs:pipeline` to download artifacts from the parent pipeline.
|
||||
|
||||
The `pipeline` attribute does not accept the current pipeline ID (`$CI_PIPELINE_ID`).
|
||||
To download artifacts from a job in the current pipeline, use the basic form of [`needs`](#artifact-downloads-with-needs).
|
||||
**Additional details**:
|
||||
|
||||
#### Optional `needs`
|
||||
- The `pipeline` attribute does not accept the current pipeline ID (`$CI_PIPELINE_ID`).
|
||||
To download artifacts from a job in the current pipeline, use [`needs`](#needsartifacts).
|
||||
|
||||
#### `needs:optional`
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30680) in GitLab 13.10.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/323891) in GitLab 14.0.
|
||||
|
@ -1630,20 +1626,21 @@ To need a job that sometimes does not exist in the pipeline, add `optional: true
|
|||
to the `needs` configuration. If not defined, `optional: false` is the default.
|
||||
|
||||
Jobs that use [`rules`](#rules), [`only`, or `except`](#only--except), might
|
||||
not always exist in a pipeline. When the pipeline starts, it checks the `needs`
|
||||
relationships before running. Without `optional: true`, needs relationships that
|
||||
not always exist in a pipeline. When the pipeline is created, GitLab checks the `needs`
|
||||
relationships before starting it. Without `optional: true`, needs relationships that
|
||||
point to a job that does not exist stops the pipeline from starting and causes a pipeline
|
||||
error similar to:
|
||||
|
||||
- `'job1' job needs 'job2' job, but it was not added to the pipeline`
|
||||
|
||||
In this example:
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job.
|
||||
|
||||
- When the branch is the default branch, the `build` job exists in the pipeline, and the `rspec`
|
||||
job waits for it to complete before starting.
|
||||
- When the branch is not the default branch, the `build` job does not exist in the pipeline.
|
||||
The `rspec` job runs immediately (similar to `needs: []`) because its `needs`
|
||||
relationship to the `build` job is optional.
|
||||
**Possible inputs**:
|
||||
|
||||
- `job:`: The job to make optional.
|
||||
- `true` or `false` (default).
|
||||
|
||||
**Example of `needs:optional`**:
|
||||
|
||||
```yaml
|
||||
build:
|
||||
|
@ -1658,6 +1655,42 @@ rspec:
|
|||
optional: true
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
||||
- When the branch is the default branch, the `build` job exists in the pipeline, and the `rspec`
|
||||
job waits for it to complete before starting.
|
||||
- When the branch is not the default branch, the `build` job does not exist in the pipeline.
|
||||
The `rspec` job runs immediately (similar to `needs: []`) because its `needs`
|
||||
relationship to the `build` job is optional.
|
||||
|
||||
#### `needs:pipeline`
|
||||
|
||||
You can mirror the pipeline status from an upstream pipeline to a bridge job by
|
||||
using the `needs:pipeline` keyword. The latest pipeline status from the default branch is
|
||||
replicated to the bridge job.
|
||||
|
||||
**Keyword type**: Job keyword. You can use it only as part of a job.
|
||||
|
||||
**Possible inputs**:
|
||||
|
||||
- A full project path, including namespace and group. If the
|
||||
project is in the same group or namespace, you can omit them from the `project:`
|
||||
keyword. For example: `project: group/project-name` or `project: project-name`.
|
||||
|
||||
**Example of `needs:pipeline`**:
|
||||
|
||||
```yaml
|
||||
upstream_bridge:
|
||||
stage: test
|
||||
needs:
|
||||
pipeline: other/project
|
||||
```
|
||||
|
||||
**Additional details**:
|
||||
|
||||
- If you add the `job` keyword to `needs:pipeline`, the job no longer mirrors the
|
||||
pipeline status. The behavior changes to [`needs:pipeline:job`](#needspipelinejob).
|
||||
|
||||
### `tags`
|
||||
|
||||
> - A limit of 50 tags per job [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/338929) in GitLab 14.3.
|
||||
|
@ -2512,7 +2545,7 @@ By default, jobs in later stages automatically download all the artifacts create
|
|||
by jobs in earlier stages. You can control artifact download behavior in jobs with
|
||||
[`dependencies`](#dependencies).
|
||||
|
||||
When using the [`needs`](#artifact-downloads-with-needs) keyword, jobs can only download
|
||||
When using the [`needs`](#needs) keyword, jobs can only download
|
||||
artifacts from the jobs defined in the `needs` configuration.
|
||||
|
||||
Job artifacts are only collected for successful jobs by default, and
|
||||
|
@ -2840,7 +2873,7 @@ pipeline features from each job.
|
|||
To be able to browse the report output files, include the [`artifacts:paths`](#artifactspaths) keyword.
|
||||
|
||||
NOTE:
|
||||
Combined reports in parent pipelines using [artifacts from child pipelines](#artifact-downloads-to-child-pipelines) is
|
||||
Combined reports in parent pipelines using [artifacts from child pipelines](#needspipelinejob) is
|
||||
not supported. Track progress on adding support in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/215725).
|
||||
|
||||
##### `artifacts:reports:accessibility` **(FREE)**
|
||||
|
|
|
@ -313,12 +313,8 @@ A better strategy is to split the migration, so that we only need to acquire one
|
|||
```ruby
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
remove_column :users, :full_name
|
||||
end
|
||||
|
||||
def down
|
||||
add_column :users, :full_name, :string
|
||||
def change
|
||||
remove_column :users, :full_name, :string
|
||||
end
|
||||
```
|
||||
|
||||
|
|
|
@ -54,6 +54,16 @@ The [release-cli](https://gitlab.com/gitlab-org/release-cli) will be released as
|
|||
|
||||
Announced: 2021-08-22
|
||||
|
||||
## 14.8
|
||||
|
||||
### openSUSE Leap 15.2 packages
|
||||
|
||||
Distribution support and security updates for openSUSE Leap 15.2 are [ending December 2021](https://en.opensuse.org/Lifetime#openSUSE_Leap).
|
||||
|
||||
Starting in 14.5 we are providing packages for openSUSE Leap 15.3, and will stop providing packages for openSUSE Leap 15.2 in the 14.8 milestone.
|
||||
|
||||
Announced: 2021-11-22
|
||||
|
||||
## 15.0
|
||||
|
||||
### Audit events for repository push events
|
||||
|
@ -68,7 +78,7 @@ Announced: 2021-09-22
|
|||
|
||||
### Certificate-based integration with Kubernetes
|
||||
|
||||
We are deprecating the certificate-based integration with Kubernetes and the features that rely on it.
|
||||
[We are deprecating the certificate-based integration with Kubernetes](https://about.gitlab.com/blog/2021/11/15/deprecating-the-cert-based-kubernetes-integration/).
|
||||
The timeline of removal of the integration from the product is not yet planned and we will communicate
|
||||
more details as they emerge. The certificate-based integration will continue to receive security and
|
||||
critical fixes, and features built on the integration will continue to work with supported Kubernetes
|
||||
|
|
|
@ -2394,7 +2394,7 @@ msgstr ""
|
|||
msgid "AdminDashboard|Error loading the statistics. Please try again"
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources, including issues and merge requests. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
|
||||
msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources, including issues and merge requests. After you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
|
||||
msgstr ""
|
||||
|
||||
msgid "AdminProjects|Delete"
|
||||
|
@ -2934,6 +2934,12 @@ msgstr ""
|
|||
msgid "AdvancedSearch|Reindex required"
|
||||
msgstr ""
|
||||
|
||||
msgid "After a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc."
|
||||
msgstr ""
|
||||
|
||||
msgid "After a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. You will lose this project's repository and %{strongStart}all related resources%{strongEnd}, including issues and merge requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "After a successful password update you will be redirected to login screen."
|
||||
msgstr ""
|
||||
|
||||
|
@ -19003,15 +19009,24 @@ msgstr ""
|
|||
msgid "InviteMembersModal|Configure security features"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Congratulations on creating your project, you're almost there!"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Contribute to the codebase"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Create issues for your new team member to work on (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|GitLab is better with colleagues!"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|GitLab member or email address"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|How about inviting a colleague or two to join you?"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteMembersModal|Invite"
|
||||
msgstr ""
|
||||
|
||||
|
@ -24182,12 +24197,6 @@ msgstr ""
|
|||
msgid "OnDemandScans|You must create a repository within your project to run an on-demand scan."
|
||||
msgstr ""
|
||||
|
||||
msgid "Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc."
|
||||
msgstr ""
|
||||
|
||||
msgid "Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. You will lose this project's repository and %{strongStart}all related resources%{strongEnd}, including issues and merge requests."
|
||||
msgstr ""
|
||||
|
||||
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -109,6 +109,7 @@
|
|||
"babel-plugin-lodash": "^3.3.4",
|
||||
"bootstrap": "4.5.3",
|
||||
"cache-loader": "^4.1.0",
|
||||
"canvas-confetti": "^1.4.0",
|
||||
"clipboard": "^1.7.1",
|
||||
"codemirror": "^5.48.4",
|
||||
"codesandbox-api": "0.0.23",
|
||||
|
|
28
spec/frontend/invite_members/components/confetti_spec.js
Normal file
28
spec/frontend/invite_members/components/confetti_spec.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import confetti from 'canvas-confetti';
|
||||
import Confetti from '~/invite_members/components/confetti.vue';
|
||||
|
||||
jest.mock('canvas-confetti', () => ({
|
||||
create: jest.fn(),
|
||||
}));
|
||||
|
||||
let wrapper;
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(Confetti);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('Confetti', () => {
|
||||
it('initiates confetti', () => {
|
||||
const basicCannon = jest.spyOn(Confetti.methods, 'basicCannon').mockImplementation(() => {});
|
||||
|
||||
createComponent();
|
||||
|
||||
expect(confetti.create).toHaveBeenCalled();
|
||||
expect(basicCannon).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -15,11 +15,19 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import Api from '~/api';
|
||||
import ExperimentTracking from '~/experimentation/experiment_tracking';
|
||||
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
|
||||
import ModalConfetti from '~/invite_members/components/confetti.vue';
|
||||
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
|
||||
import {
|
||||
INVITE_MEMBERS_IN_COMMENT,
|
||||
MEMBER_AREAS_OF_FOCUS,
|
||||
INVITE_MEMBERS_FOR_TASK,
|
||||
CANCEL_BUTTON_TEXT,
|
||||
INVITE_BUTTON_TEXT,
|
||||
MEMBERS_MODAL_CELEBRATE_INTRO,
|
||||
MEMBERS_MODAL_CELEBRATE_TITLE,
|
||||
MEMBERS_MODAL_DEFAULT_TITLE,
|
||||
MEMBERS_PLACEHOLDER,
|
||||
MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT,
|
||||
} from '~/invite_members/constants';
|
||||
import eventHub from '~/invite_members/event_hub';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
@ -74,6 +82,7 @@ const user4 = {
|
|||
avatar_url: '',
|
||||
};
|
||||
const sharedGroup = { id: '981' };
|
||||
const GlEmoji = { template: '<img/>' };
|
||||
|
||||
const createComponent = (data = {}, props = {}) => {
|
||||
wrapper = shallowMountExtended(InviteMembersModal, {
|
||||
|
@ -104,6 +113,7 @@ const createComponent = (data = {}, props = {}) => {
|
|||
}),
|
||||
GlDropdown: true,
|
||||
GlDropdownItem: true,
|
||||
GlEmoji,
|
||||
GlSprintf,
|
||||
GlFormGroup: stubComponent(GlFormGroup, {
|
||||
props: ['state', 'invalidFeedback', 'description'],
|
||||
|
@ -158,6 +168,7 @@ describe('InviteMembersModal', () => {
|
|||
const findTasks = () => wrapper.findByTestId('invite-members-modal-tasks');
|
||||
const findProjectSelect = () => wrapper.findByTestId('invite-members-modal-project-select');
|
||||
const findNoProjectsAlert = () => wrapper.findByTestId('invite-members-modal-no-projects-alert');
|
||||
const findCelebrationEmoji = () => wrapper.findComponent(GlModal).find(GlEmoji);
|
||||
|
||||
describe('rendering the modal', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -165,15 +176,15 @@ describe('InviteMembersModal', () => {
|
|||
});
|
||||
|
||||
it('renders the modal with the correct title', () => {
|
||||
expect(wrapper.findComponent(GlModal).props('title')).toBe('Invite members');
|
||||
expect(wrapper.findComponent(GlModal).props('title')).toBe(MEMBERS_MODAL_DEFAULT_TITLE);
|
||||
});
|
||||
|
||||
it('renders the Cancel button text correctly', () => {
|
||||
expect(findCancelButton().text()).toBe('Cancel');
|
||||
expect(findCancelButton().text()).toBe(CANCEL_BUTTON_TEXT);
|
||||
});
|
||||
|
||||
it('renders the Invite button text correctly', () => {
|
||||
expect(findInviteButton().text()).toBe('Invite');
|
||||
expect(findInviteButton().text()).toBe(INVITE_BUTTON_TEXT);
|
||||
});
|
||||
|
||||
it('renders the Invite button modal without isLoading', () => {
|
||||
|
@ -342,11 +353,40 @@ describe('InviteMembersModal', () => {
|
|||
describe('displaying the correct introText and form group description', () => {
|
||||
describe('when inviting to a project', () => {
|
||||
describe('when inviting members', () => {
|
||||
it('includes the correct invitee, type, and formatted name', () => {
|
||||
beforeEach(() => {
|
||||
createInviteMembersToProjectWrapper();
|
||||
});
|
||||
|
||||
it('renders the modal without confetti', () => {
|
||||
expect(wrapper.findComponent(ModalConfetti).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('includes the correct invitee, type, and formatted name', () => {
|
||||
expect(findIntroText()).toBe("You're inviting members to the test name project.");
|
||||
expect(membersFormGroupDescription()).toBe('Select members or type email addresses');
|
||||
expect(findCelebrationEmoji().exists()).toBe(false);
|
||||
expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when inviting members with celebration', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ mode: 'celebrate', inviteeType: 'members' }, { isProject: true });
|
||||
});
|
||||
|
||||
it('renders the modal with confetti', () => {
|
||||
expect(wrapper.findComponent(ModalConfetti).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the modal with the correct title', () => {
|
||||
expect(wrapper.findComponent(GlModal).props('title')).toBe(MEMBERS_MODAL_CELEBRATE_TITLE);
|
||||
});
|
||||
|
||||
it('includes the correct celebration text and emoji', () => {
|
||||
expect(findIntroText()).toBe(
|
||||
`${MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT} ${MEMBERS_MODAL_CELEBRATE_INTRO}`,
|
||||
);
|
||||
expect(findCelebrationEmoji().exists()).toBe(true);
|
||||
expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -366,7 +406,7 @@ describe('InviteMembersModal', () => {
|
|||
createInviteMembersToGroupWrapper();
|
||||
|
||||
expect(findIntroText()).toBe("You're inviting members to the test name group.");
|
||||
expect(membersFormGroupDescription()).toBe('Select members or type email addresses');
|
||||
expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import { GlProgressBar } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import LearnGitlab from '~/pages/projects/learn_gitlab/components/learn_gitlab.vue';
|
||||
import eventHub from '~/invite_members/event_hub';
|
||||
import { testActions, testSections } from './mock_data';
|
||||
|
||||
describe('Learn GitLab', () => {
|
||||
let wrapper;
|
||||
let inviteMembersOpen = false;
|
||||
|
||||
const createWrapper = () => {
|
||||
wrapper = mount(LearnGitlab, { propsData: { actions: testActions, sections: testSections } });
|
||||
wrapper = mount(LearnGitlab, {
|
||||
propsData: { actions: testActions, sections: testSections, inviteMembersOpen },
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -17,6 +21,7 @@ describe('Learn GitLab', () => {
|
|||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
inviteMembersOpen = false;
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
|
@ -35,4 +40,30 @@ describe('Learn GitLab', () => {
|
|||
expect(progressBar.attributes('value')).toBe('2');
|
||||
expect(progressBar.attributes('max')).toBe('9');
|
||||
});
|
||||
|
||||
describe('Invite Members Modal', () => {
|
||||
let spy;
|
||||
|
||||
beforeEach(() => {
|
||||
spy = jest.spyOn(eventHub, '$emit');
|
||||
});
|
||||
|
||||
it('emits openModal', () => {
|
||||
inviteMembersOpen = true;
|
||||
|
||||
createWrapper();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('openModal', {
|
||||
mode: 'celebrate',
|
||||
inviteeType: 'members',
|
||||
source: 'learn-gitlab',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not emit openModal', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -77,7 +77,7 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
|
|||
4 stars
|
||||
</li>
|
||||
</ul>
|
||||
Once a project is permanently deleted, it
|
||||
After a project is permanently deleted, it
|
||||
<strong>
|
||||
cannot be recovered
|
||||
</strong>
|
||||
|
|
|
@ -19,12 +19,51 @@ RSpec.describe LearnGitlabHelper do
|
|||
OnboardingProgress.register(namespace, :git_write)
|
||||
end
|
||||
|
||||
describe '#onboarding_actions_data' do
|
||||
subject(:onboarding_actions_data) { helper.onboarding_actions_data(project) }
|
||||
describe '#learn_gitlab_enabled?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
shared_examples 'has all actions' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, namespace: user.namespace) }
|
||||
|
||||
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
|
||||
|
||||
subject { helper.learn_gitlab_enabled?(project) }
|
||||
|
||||
where(:onboarding, :learn_gitlab_available, :result) do
|
||||
true | true | true
|
||||
true | false | false
|
||||
false | true | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
allow(OnboardingProgress).to receive(:onboarding?).with(project.namespace).and_return(onboarding)
|
||||
allow_next(LearnGitlab::Project, user).to receive(:available?).and_return(learn_gitlab_available)
|
||||
end
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#learn_gitlab_data' do
|
||||
subject(:learn_gitlab_data) { helper.learn_gitlab_data(project) }
|
||||
|
||||
let(:onboarding_actions_data) { Gitlab::Json.parse(learn_gitlab_data[:actions]).deep_symbolize_keys }
|
||||
let(:onboarding_sections_data) { Gitlab::Json.parse(learn_gitlab_data[:sections]).deep_symbolize_keys }
|
||||
|
||||
shared_examples 'has all data' do
|
||||
it 'has all actions' do
|
||||
expect(onboarding_actions_data.keys).to contain_exactly(
|
||||
expected_keys = [
|
||||
:issue_created,
|
||||
:git_write,
|
||||
:pipeline_created,
|
||||
|
@ -34,11 +73,18 @@ RSpec.describe LearnGitlabHelper do
|
|||
:required_mr_approvals_enabled,
|
||||
:code_owners_enabled,
|
||||
:security_scan_enabled
|
||||
)
|
||||
]
|
||||
|
||||
expect(onboarding_actions_data.keys).to contain_exactly(*expected_keys)
|
||||
end
|
||||
|
||||
it 'has all section data', :aggregate_failures do
|
||||
expect(onboarding_sections_data.keys).to contain_exactly(:deploy, :plan, :workspace)
|
||||
expect(onboarding_sections_data.values.map { |section| section.keys }).to match_array([[:svg]] * 3)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'has all actions'
|
||||
it_behaves_like 'has all data'
|
||||
|
||||
it 'sets correct paths' do
|
||||
expect(onboarding_actions_data).to match({
|
||||
|
@ -91,7 +137,7 @@ RSpec.describe LearnGitlabHelper do
|
|||
stub_experiments(change_continuous_onboarding_link_urls: :candidate)
|
||||
end
|
||||
|
||||
it_behaves_like 'has all actions'
|
||||
it_behaves_like 'has all data'
|
||||
|
||||
it 'sets mostly new paths' do
|
||||
expect(onboarding_actions_data).to match({
|
||||
|
@ -126,51 +172,4 @@ RSpec.describe LearnGitlabHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#learn_gitlab_enabled?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, namespace: user.namespace) }
|
||||
|
||||
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
|
||||
|
||||
subject { helper.learn_gitlab_enabled?(project) }
|
||||
|
||||
where(:onboarding, :learn_gitlab_available, :result) do
|
||||
true | true | true
|
||||
true | false | false
|
||||
false | true | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
allow(OnboardingProgress).to receive(:onboarding?).with(project.namespace).and_return(onboarding)
|
||||
allow_next(LearnGitlab::Project, user).to receive(:available?).and_return(learn_gitlab_available)
|
||||
end
|
||||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(result) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not signed in' do
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#onboarding_sections_data' do
|
||||
subject(:sections) { helper.onboarding_sections_data }
|
||||
|
||||
it 'has the right keys' do
|
||||
expect(sections.keys).to contain_exactly(:deploy, :plan, :workspace)
|
||||
end
|
||||
it 'has the svg' do
|
||||
expect(sections.values.map { |section| section.keys }).to eq([[:svg]] * 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -385,6 +385,43 @@ RSpec.describe Deployment do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.archivables_in' do
|
||||
subject { described_class.archivables_in(project, limit: limit) }
|
||||
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:deployment_1) { create(:deployment, project: project) }
|
||||
let_it_be(:deployment_2) { create(:deployment, project: project) }
|
||||
let_it_be(:deployment_3) { create(:deployment, project: project) }
|
||||
|
||||
let(:limit) { 100 }
|
||||
|
||||
context 'when there are no archivable deployments in the project' do
|
||||
it 'returns nothing' do
|
||||
expect(subject).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are archivable deployments in the project' do
|
||||
before do
|
||||
stub_const("::Deployment::ARCHIVABLE_OFFSET", 1)
|
||||
end
|
||||
|
||||
it 'returns all archivable deployments' do
|
||||
expect(subject.count).to eq(2)
|
||||
expect(subject).to contain_exactly(deployment_1, deployment_2)
|
||||
end
|
||||
|
||||
context 'with limit' do
|
||||
let(:limit) { 1 }
|
||||
|
||||
it 'takes the limit into account' do
|
||||
expect(subject.count).to eq(1)
|
||||
expect(subject.take).to be_in([deployment_1, deployment_2])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
describe 'last_for_environment' do
|
||||
let(:production) { create(:environment) }
|
||||
|
@ -774,6 +811,7 @@ RSpec.describe Deployment do
|
|||
it 'schedules workers when finishing a deploy' do
|
||||
expect(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
|
||||
expect(Deployments::LinkMergeRequestWorker).to receive(:perform_async)
|
||||
expect(Deployments::ArchiveInProjectWorker).to receive(:perform_async)
|
||||
expect(Deployments::HooksWorker).to receive(:perform_async)
|
||||
|
||||
expect(deploy.update_status('success')).to eq(true)
|
||||
|
|
80
spec/services/deployments/archive_in_project_service_spec.rb
Normal file
80
spec/services/deployments/archive_in_project_service_spec.rb
Normal file
|
@ -0,0 +1,80 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Deployments::ArchiveInProjectService do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
let(:service) { described_class.new(project, nil) }
|
||||
|
||||
describe '#execute' do
|
||||
subject { service.execute }
|
||||
|
||||
context 'when there are archivable deployments' do
|
||||
let!(:deployments) { create_list(:deployment, 3, project: project) }
|
||||
let!(:deployment_refs) { deployments.map(&:ref_path) }
|
||||
|
||||
before do
|
||||
deployments.each(&:create_ref)
|
||||
allow(Deployment).to receive(:archivables_in) { deployments }
|
||||
end
|
||||
|
||||
it 'returns result code' do
|
||||
expect(subject[:result]).to eq(:archived)
|
||||
expect(subject[:status]).to eq(:success)
|
||||
expect(subject[:count]).to eq(3)
|
||||
end
|
||||
|
||||
it 'archives the deployment' do
|
||||
expect(deployments.map(&:archived?)).to be_all(false)
|
||||
expect(deployment_refs_exist?).to be_all(true)
|
||||
|
||||
subject
|
||||
|
||||
deployments.each(&:reload)
|
||||
expect(deployments.map(&:archived?)).to be_all(true)
|
||||
expect(deployment_refs_exist?).to be_all(false)
|
||||
end
|
||||
|
||||
context 'when ref does not exist by some reason' do
|
||||
before do
|
||||
project.repository.delete_refs(*deployment_refs)
|
||||
end
|
||||
|
||||
it 'does not raise an error' do
|
||||
expect(deployment_refs_exist?).to be_all(false)
|
||||
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(deployment_refs_exist?).to be_all(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when deployments_archive feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(deployments_archive: false)
|
||||
end
|
||||
|
||||
it 'does not do anything' do
|
||||
expect(subject[:status]).to eq(:error)
|
||||
expect(subject[:message]).to eq('Feature flag is not enabled')
|
||||
end
|
||||
end
|
||||
|
||||
def deployment_refs_exist?
|
||||
deployment_refs.map { |path| project.repository.ref_exists?(path) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no archivable deployments' do
|
||||
before do
|
||||
allow(Deployment).to receive(:archivables_in) { Deployment.none }
|
||||
end
|
||||
|
||||
it 'returns result code' do
|
||||
expect(subject[:result]).to eq(:empty)
|
||||
expect(subject[:status]).to eq(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
spec/workers/deployments/archive_in_project_worker_spec.rb
Normal file
18
spec/workers/deployments/archive_in_project_worker_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Deployments::ArchiveInProjectWorker do
|
||||
subject { described_class.new.perform(deployment&.project_id) }
|
||||
|
||||
describe '#perform' do
|
||||
let(:deployment) { create(:deployment, :success) }
|
||||
|
||||
it 'executes Deployments::ArchiveInProjectService' do
|
||||
expect(Deployments::ArchiveInProjectService)
|
||||
.to receive(:new).with(deployment.project, nil).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3276,6 +3276,11 @@ caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001259:
|
|||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz#96d89813c076ea061209a4e040d8dcf0c66a1d01"
|
||||
integrity sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==
|
||||
|
||||
canvas-confetti@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/canvas-confetti/-/canvas-confetti-1.4.0.tgz#840f6db4a566f8f32abe28c00dcd82acf39c92bd"
|
||||
integrity sha512-S18o4Y9PqI/uabdlT/jI3MY7XBJjNxnfapFIkjkMwpz6qNxLFZOm2b22OMf4ZYDL9lpNWI+Ih4fEMVPwO1KHFQ==
|
||||
|
||||
capture-exit@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
|
||||
|
|
Loading…
Reference in a new issue