Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-17 18:14:12 +00:00
parent a331169e6e
commit 2c06e006d8
44 changed files with 1028 additions and 396 deletions

View file

@ -1 +1 @@
3772b182b5b714770956de4d76f3ce09a220fb85
57ac403e3ac18dbfa7e1e72bfb3eb8ab01cea626

View file

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

View file

@ -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,20 +404,28 @@ 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>
<p ref="introText">
<gl-sprintf :message="introText">
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
</p>
<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">
<template #strong="{ content }">
<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"

View file

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

View file

@ -46,7 +46,7 @@ export default {
return sprintf(
s__(`AdminProjects|
Youre 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>`,

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View 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

View 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

View 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

View file

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

View 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

View file

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

View file

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

View file

@ -0,0 +1 @@
26c534cdae8630e3f28ad2b61a1049aaab5c5b7a1b761f0961831b621e148ed3

View file

@ -0,0 +1 @@
63495b9f9ca2d4fa121b75eea36f2923942a6e11f27bef2c51414e00ccd48973

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
with `needs`.
In this example:
#### Cross project artifact downloads with `needs` **(PREMIUM)**
- 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`.
#### `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,62 +1541,83 @@ 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
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.
- 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.
```yaml
create-artifact:
stage: build
script: echo 'sample artifact' > artifact.txt
artifacts:
paths: [artifact.txt]
**Possible inputs**:
child-pipeline:
stage: test
trigger:
include: child.yml
strategy: depend
variables:
PARENT_PIPELINE_ID: $CI_PIPELINE_ID
```
- `needs:pipeline`: A pipeline ID. Must be a pipeline present in the same parent-child pipeline hierarchy.
- `job:`: The job to download artifacts from.
A job in the child pipeline can download artifacts from the `create-artifact` job in
the parent pipeline:
**Example of `needs:pipeline:job`**:
```yaml
use-artifact:
script: cat artifact.txt
needs:
- pipeline: $PARENT_PIPELINE_ID
job: create-artifact
```
- Parent pipeline (`.gitlab-ci.yml`):
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.
```yaml
create-artifact:
stage: build
script: echo 'sample artifact' > artifact.txt
artifacts:
paths: [artifact.txt]
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).
child-pipeline:
stage: test
trigger:
include: child.yml
strategy: depend
variables:
PARENT_PIPELINE_ID: $CI_PIPELINE_ID
```
#### Optional `needs`
- Child pipeline (`child.yml`):
```yaml
use-artifact:
script: cat artifact.txt
needs:
- pipeline: $PARENT_PIPELINE_ID
job: create-artifact
```
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.
**Additional details**:
- 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)**

View file

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

View file

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

View file

@ -2394,7 +2394,7 @@ msgstr ""
msgid "AdminDashboard|Error loading the statistics. Please try again"
msgstr ""
msgid "AdminProjects| Youre 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| Youre 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 ""

View file

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

View 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();
});
});

View file

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

View file

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

View file

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

View file

@ -19,114 +19,6 @@ RSpec.describe LearnGitlabHelper do
OnboardingProgress.register(namespace, :git_write)
end
describe '#onboarding_actions_data' do
subject(:onboarding_actions_data) { helper.onboarding_actions_data(project) }
shared_examples 'has all actions' do
it 'has all actions' do
expect(onboarding_actions_data.keys).to contain_exactly(
:issue_created,
:git_write,
:pipeline_created,
:merge_request_created,
:user_added,
:trial_started,
:required_mr_approvals_enabled,
:code_owners_enabled,
:security_scan_enabled
)
end
end
it_behaves_like 'has all actions'
it 'sets correct paths' do
expect(onboarding_actions_data).to match({
trial_started: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
),
issue_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/4\z})
),
git_write: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/6\z})
),
pipeline_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/7\z})
),
user_added: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/8\z})
),
merge_request_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/9\z})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
),
security_scan_enabled: a_hash_including(
url: a_string_matching(%r{docs\.gitlab\.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports\z})
)
})
end
it 'sets correct completion statuses' do
expect(onboarding_actions_data).to match({
issue_created: a_hash_including(completed: false),
git_write: a_hash_including(completed: true),
pipeline_created: a_hash_including(completed: false),
merge_request_created: a_hash_including(completed: false),
user_added: a_hash_including(completed: false),
trial_started: a_hash_including(completed: false),
required_mr_approvals_enabled: a_hash_including(completed: false),
code_owners_enabled: a_hash_including(completed: false),
security_scan_enabled: a_hash_including(completed: false)
})
end
context 'when in the new action URLs experiment' do
before do
stub_experiments(change_continuous_onboarding_link_urls: :candidate)
end
it_behaves_like 'has all actions'
it 'sets mostly new paths' do
expect(onboarding_actions_data).to match({
trial_started: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
),
issue_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues\z})
),
git_write: a_hash_including(
url: a_string_matching(%r{/learn_gitlab\z})
),
pipeline_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/pipelines\z})
),
user_added: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/project_members\z})
),
merge_request_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/merge_requests\z})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
),
security_scan_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z})
)
})
end
end
end
describe '#learn_gitlab_enabled?' do
using RSpec::Parameterized::TableSyntax
@ -163,14 +55,121 @@ RSpec.describe LearnGitlabHelper do
end
end
describe '#onboarding_sections_data' do
subject(:sections) { helper.onboarding_sections_data }
describe '#learn_gitlab_data' do
subject(:learn_gitlab_data) { helper.learn_gitlab_data(project) }
it 'has the right keys' do
expect(sections.keys).to contain_exactly(:deploy, :plan, :workspace)
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
expected_keys = [
:issue_created,
:git_write,
:pipeline_created,
:merge_request_created,
:user_added,
:trial_started,
: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 'has the svg' do
expect(sections.values.map { |section| section.keys }).to eq([[:svg]] * 3)
it_behaves_like 'has all data'
it 'sets correct paths' do
expect(onboarding_actions_data).to match({
trial_started: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
),
issue_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/4\z})
),
git_write: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/6\z})
),
pipeline_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/7\z})
),
user_added: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/8\z})
),
merge_request_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/9\z})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
),
security_scan_enabled: a_hash_including(
url: a_string_matching(%r{docs\.gitlab\.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports\z})
)
})
end
it 'sets correct completion statuses' do
expect(onboarding_actions_data).to match({
issue_created: a_hash_including(completed: false),
git_write: a_hash_including(completed: true),
pipeline_created: a_hash_including(completed: false),
merge_request_created: a_hash_including(completed: false),
user_added: a_hash_including(completed: false),
trial_started: a_hash_including(completed: false),
required_mr_approvals_enabled: a_hash_including(completed: false),
code_owners_enabled: a_hash_including(completed: false),
security_scan_enabled: a_hash_including(completed: false)
})
end
context 'when in the new action URLs experiment' do
before do
stub_experiments(change_continuous_onboarding_link_urls: :candidate)
end
it_behaves_like 'has all data'
it 'sets mostly new paths' do
expect(onboarding_actions_data).to match({
trial_started: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
),
issue_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues\z})
),
git_write: a_hash_including(
url: a_string_matching(%r{/learn_gitlab\z})
),
pipeline_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/pipelines\z})
),
user_added: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/project_members\z})
),
merge_request_created: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/merge_requests\z})
),
code_owners_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
),
required_mr_approvals_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
),
security_scan_enabled: a_hash_including(
url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z})
)
})
end
end
end
end

View file

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

View 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

View 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

View file

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