Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-18 15:10:19 +00:00
parent 9796aa22cd
commit bf0d6692fc
49 changed files with 997 additions and 676 deletions

View File

@ -1,15 +0,0 @@
test-reliability-report:
extends:
- .qa:rules:reliable-reports:schedule
image:
name: ${CI_REGISTRY_IMAGE}/gitlab-ee-qa:${CI_DEFAULT_BRANCH}
entrypoint: [""]
before_script:
- cd /home/gitlab/qa
script:
- echo "Generate report for 'staging-full' runs"
- bundle exec rake "reliable_spec_report[staging-full,30,true]"
- bundle exec rake "unreliable_spec_report[staging-full,30,true]"
- echo "Generate report for 'package-and-qa' runs"
- bundle exec rake "reliable_spec_report[package-and-qa,30,true]"
- bundle exec rake "unreliable_spec_report[package-and-qa,30,true]"

View File

@ -766,11 +766,6 @@
changes: *feature-flag-development-config-patterns
allow_failure: true
.qa:rules:reliable-reports:schedule:
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $QA_RELIABLE_REPORT == "true"'
allow_failure: true
###############
# Rails rules #
###############

View File

@ -94,10 +94,7 @@ _Consider adding links to check for Sentry errors, Production logs for 5xx, 302s
### Global rollout on production
For visibility, all `/chatops` commands that target production should be:
- Executed in the `#production` slack channel.
- Cross-posted (with the command results) to the responsible team's slack channel (`#g_TEAM_NAME`).
For visibility, all `/chatops` commands that target production should be executed in the `#production` slack channel and cross-posted (with the command results) to the responsible team's slack channel (`#g_TEAM_NAME`).
- [ ] [Incrementally roll out](https://docs.gitlab.com/ee/development/feature_flags/controls.html#process) the feature.
- If the feature flag in code has [an actor](https://docs.gitlab.com/ee/development/feature_flags/#feature-actors), perform **actor-based** rollout.

View File

@ -2594,7 +2594,6 @@ Style/OpenStructUse:
- 'spec/lib/gitlab/database/migrations/runner_spec.rb'
- 'spec/lib/gitlab/gitaly_client/blobs_stitcher_spec.rb'
- 'spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb'
- 'spec/lib/gitlab/grape_logging/loggers/exception_logger_spec.rb'
- 'spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb'
- 'spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb'
- 'spec/lib/gitlab/legacy_github_import/project_creator_spec.rb'
@ -2604,7 +2603,6 @@ Style/OpenStructUse:
- 'spec/models/design_management/design_at_version_spec.rb'
- 'spec/models/user_spec.rb'
- 'spec/presenters/packages/nuget/search_results_presenter_spec.rb'
- 'spec/requests/api/import_github_spec.rb'
- 'spec/services/packages/nuget/metadata_extraction_service_spec.rb'
- 'spec/services/projects/import_service_spec.rb'
- 'spec/services/system_note_service_spec.rb'

View File

@ -30,7 +30,7 @@ export function getAllExperimentContexts() {
return Object.values(getExperimentsData()).map(createGitlabExperimentContext);
}
export function isExperimentVariant(experimentName, variantName) {
export function isExperimentVariant(experimentName, variantName = CANDIDATE_VARIANT) {
return getExperimentData(experimentName)?.variant === variantName;
}

View File

@ -26,6 +26,7 @@ import {
MEMBER_AREAS_OF_FOCUS,
INVITE_MEMBERS_FOR_TASK,
MODAL_LABELS,
LEARN_GITLAB,
} from '../constants';
import eventHub from '../event_hub';
import {
@ -200,7 +201,8 @@ export default {
},
tasksToBeDoneEnabled() {
return (
getParameterValues('open_modal')[0] === 'invite_members_for_task' &&
(getParameterValues('open_modal')[0] === 'invite_members_for_task' ||
this.isOnLearnGitlab) &&
this.tasksToBeDoneOptions.length
);
},
@ -221,11 +223,18 @@ export default {
? this.selectedTaskProject.id
: '';
},
isOnLearnGitlab() {
return this.source === LEARN_GITLAB;
},
},
mounted() {
eventHub.$on('openModal', (options) => {
this.openModal(options);
this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.view);
if (this.isOnLearnGitlab) {
this.trackEvent(INVITE_MEMBERS_FOR_TASK.name, this.source);
} else {
this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.view);
}
});
if (this.tasksToBeDoneEnabled) {
@ -303,7 +312,7 @@ export default {
: Api.groupShareWithGroup.bind(Api);
apiShareWithGroup(this.id, this.shareWithGroupPostData(this.groupToBeSharedWith.id))
.then(this.showToastMessageSuccess)
.then(this.showSuccessMessage)
.catch(this.showInvalidFeedbackMessage);
},
submitInviteMembers() {
@ -332,7 +341,7 @@ export default {
this.trackinviteMembersForTask();
Promise.all(promises)
.then(this.conditionallyShowToastSuccess)
.then(this.conditionallyShowSuccessMessage)
.catch(this.showInvalidFeedbackMessage);
},
inviteByEmailPostData(usersToInviteByEmail) {
@ -364,11 +373,11 @@ export default {
group_access: this.selectedAccessLevel,
};
},
conditionallyShowToastSuccess(response) {
conditionallyShowSuccessMessage(response) {
const message = this.unescapeMsg(responseMessageFromSuccess(response));
if (message === '') {
this.showToastMessageSuccess();
this.showSuccessMessage();
return;
}
@ -376,8 +385,12 @@ export default {
this.invalidFeedbackMessage = message;
this.isLoading = false;
},
showToastMessageSuccess() {
this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions);
showSuccessMessage() {
if (this.isOnLearnGitlab) {
eventHub.$emit('showSuccessfulInvitationsAlert');
} else {
this.$toast.show(this.$options.labels.toastMessageSuccessful, this.toastOptions);
}
this.closeModal();
},
showInvalidFeedbackMessage(response) {

View File

@ -144,3 +144,5 @@ export const MODAL_LABELS = {
headerCloseLabel: HEADER_CLOSE_LABEL,
areasOfFocusLabel: AREAS_OF_FOCUS_LABEL,
};
export const LEARN_GITLAB = 'learn_gitlab';

View File

@ -1,18 +1,21 @@
<script>
import { GlProgressBar, GlSprintf } from '@gitlab/ui';
import { GlProgressBar, GlSprintf, GlAlert } 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';
export default {
components: { GlProgressBar, GlSprintf, LearnGitlabSectionCard },
components: { GlProgressBar, GlSprintf, GlAlert, LearnGitlabSectionCard },
i18n: {
title: s__('LearnGitLab|Learn GitLab'),
description: s__(
'LearnGitLab|Ready to get started with GitLab? Follow these steps to set up your workspace, plan and commit changes, and deploy your project.',
),
percentageCompleted: s__(`LearnGitLab|%{percentage}%{percentSymbol} completed`),
successfulInvitations: s__(
"LearnGitLab|Your team is growing! You've successfully invited new team members to the %{projectName} project.",
),
},
props: {
actions: {
@ -28,12 +31,22 @@ export default {
required: false,
default: false,
},
project: {
required: true,
type: Object,
},
},
data() {
return {
showSuccessfulInvitationsAlert: false,
actionsData: this.actions,
};
},
maxValue: Object.keys(ACTION_LABELS).length,
actionSections: Object.keys(ACTION_SECTIONS),
computed: {
progressValue() {
return Object.values(this.actions).filter((a) => a.completed).length;
return Object.values(this.actionsData).filter((a) => a.completed).length;
},
progressPercentage() {
return Math.round((this.progressValue / this.$options.maxValue) * 100);
@ -43,14 +56,23 @@ export default {
if (this.inviteMembersOpen) {
this.openInviteMembersModal('celebrate');
}
eventHub.$on('showSuccessfulInvitationsAlert', this.handleShowSuccessfulInvitationsAlert);
},
beforeDestroy() {
eventHub.$off('showSuccessfulInvitationsAlert', this.handleShowSuccessfulInvitationsAlert);
},
methods: {
openInviteMembersModal(mode) {
eventHub.$emit('openModal', { mode, inviteeType: 'members', source: 'learn-gitlab' });
},
handleShowSuccessfulInvitationsAlert() {
this.showSuccessfulInvitationsAlert = true;
this.markActionAsCompleted('userAdded');
},
actionsFor(section) {
const actions = Object.fromEntries(
Object.entries(this.actions).filter(
Object.entries(this.actionsData).filter(
([action]) => ACTION_LABELS[action].section === section,
),
);
@ -59,11 +81,34 @@ export default {
svgFor(section) {
return this.sections[section].svg;
},
markActionAsCompleted(completedAction) {
Object.keys(this.actionsData).forEach((action) => {
if (action === completedAction) {
this.actionsData[action].completed = true;
this.modifySidebarPercentage();
}
});
},
modifySidebarPercentage() {
const el = document.querySelector('.sidebar-top-level-items .active .count');
el.textContent = `${this.progressPercentage}%`;
},
},
};
</script>
<template>
<div>
<gl-alert
v-if="showSuccessfulInvitationsAlert"
class="gl-mt-5"
@dismiss="showSuccessfulInvitationsAlert = false"
>
<gl-sprintf :message="$options.i18n.successfulInvitations">
<template #projectName>
<strong>{{ project.name }}</strong>
</template>
</gl-sprintf>
</gl-alert>
<div class="row">
<div class="gl-mb-7 gl-ml-5">
<h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>

View File

@ -1,5 +1,7 @@
<script>
import { GlLink, GlIcon } from '@gitlab/ui';
import { isExperimentVariant } from '~/experimentation/utils';
import eventHub from '~/invite_members/event_hub';
import { s__ } from '~/locale';
import { ACTION_LABELS } from '../constants';
@ -24,6 +26,20 @@ export default {
trialOnly() {
return ACTION_LABELS[this.action].trialRequired;
},
showInviteModalLink() {
return (
this.action === 'userAdded' && isExperimentVariant('invite_for_help_continuous_onboarding')
);
},
},
methods: {
openModal() {
eventHub.$emit('openModal', {
inviteeType: 'members',
source: 'learn_gitlab',
tasksToBeDoneEnabled: true,
});
},
},
};
</script>
@ -33,18 +49,27 @@ export default {
<gl-icon name="check-circle-filled" :size="16" data-testid="completed-icon" />
{{ $options.i18n.ACTION_LABELS[action].title }}
</span>
<span v-else>
<gl-link
target="_blank"
:href="value.url"
data-track-action="click_link"
:data-track-label="$options.i18n.ACTION_LABELS[action].title"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
data-track-experiment="change_continuous_onboarding_link_urls"
>
{{ $options.i18n.ACTION_LABELS[action].title }}
</gl-link>
</span>
<gl-link
v-else-if="showInviteModalLink"
data-track-action="click_link"
:data-track-label="$options.i18n.ACTION_LABELS[action].title"
data-track-property="Growth::Activation::Experiment::InviteForHelpContinuousOnboarding"
data-testid="invite-for-help-continuous-onboarding-experiment-link"
@click="openModal"
>
{{ $options.i18n.ACTION_LABELS[action].title }}
</gl-link>
<gl-link
v-else
target="_blank"
:href="value.url"
data-track-action="click_link"
:data-track-label="$options.i18n.ACTION_LABELS[action].title"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
data-track-experiment="change_continuous_onboarding_link_urls"
>
{{ $options.i18n.ACTION_LABELS[action].title }}
</gl-link>
<span v-if="trialOnly" class="gl-font-style-italic gl-text-gray-500" data-testid="trial-only">
- {{ $options.i18n.trialOnly }}
</span>

View File

@ -12,17 +12,18 @@ function initLearnGitlab() {
const actions = convertObjectPropsToCamelCase(JSON.parse(el.dataset.actions));
const sections = convertObjectPropsToCamelCase(JSON.parse(el.dataset.sections));
const project = convertObjectPropsToCamelCase(JSON.parse(el.dataset.project));
const { inviteMembersOpen } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(LearnGitlab, {
props: { actions, sections, inviteMembersOpen },
props: { actions, sections, project, inviteMembersOpen },
});
},
});
}
initInviteMembersModal();
initLearnGitlab();
initInviteMembersModal();

View File

@ -3,6 +3,7 @@
class Projects::LearnGitlabController < Projects::ApplicationController
before_action :authenticate_user!
before_action :check_experiment_enabled?
before_action :enable_invite_for_help_continuous_onboarding_experiment
feature_category :users
@ -14,4 +15,13 @@ class Projects::LearnGitlabController < Projects::ApplicationController
def check_experiment_enabled?
return access_denied! unless helpers.learn_gitlab_enabled?(project)
end
def enable_invite_for_help_continuous_onboarding_experiment
return unless current_user.can?(:admin_group_member, project.namespace)
experiment(:invite_for_help_continuous_onboarding, namespace: project.namespace) do |e|
e.candidate {}
e.record!
end
end
end

View File

@ -42,7 +42,7 @@ module InviteMembersHelper
e.candidate { dataset.merge!(areas_of_focus_options: member_areas_of_focus_options.to_json, no_selection_areas_of_focus: ['no_selection']) }
end
if show_invite_members_for_task?
if show_invite_members_for_task?(source)
dataset.merge!(
tasks_to_be_done_options: tasks_to_be_done_options.to_json,
projects: projects_for_source(source).to_json,
@ -80,10 +80,12 @@ module InviteMembersHelper
{}
end
def show_invite_members_for_task?
return unless current_user && experiment(:invite_members_for_task).enabled?
def show_invite_members_for_task?(source)
return unless current_user
params[:open_modal] == 'invite_members_for_task'
invite_members_for_task_experiment = experiment(:invite_members_for_task).enabled? && params[:open_modal] == 'invite_members_for_task'
invite_for_help_continuous_onboarding = source.is_a?(Project) && experiment(:invite_for_help_continuous_onboarding, namespace: source.namespace).variant.name == 'candidate'
invite_members_for_task_experiment || invite_for_help_continuous_onboarding
end
def tasks_to_be_done_options

View File

@ -10,7 +10,8 @@ module LearnGitlabHelper
def learn_gitlab_data(project)
{
actions: onboarding_actions_data(project).to_json,
sections: onboarding_sections_data.to_json
sections: onboarding_sections_data.to_json,
project: onboarding_project_data(project).to_json
}
end
@ -56,6 +57,10 @@ module LearnGitlabHelper
}
end
def onboarding_project_data(project)
{ name: project.name }
end
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,11 +12,7 @@ class U2fRegistration < ApplicationRecord
converter = Gitlab::Auth::U2fWebauthnConverter.new(self)
WebauthnRegistration.create!(converter.convert)
rescue StandardError => ex
Gitlab::AppJsonLogger.error(
event: 'u2f_migration',
error: ex.class.name,
backtrace: ::Gitlab::BacktraceCleaner.clean_backtrace(ex.backtrace),
message: "U2F to WebAuthn conversion failed")
Gitlab::ErrorTracking.track_exception(ex, u2f_registration_id: self.id)
end
def update_webauthn_registration

View File

@ -1614,8 +1614,14 @@ class User < ApplicationRecord
.joins(:runner)
.select('ci_runners.*')
base_and_descendants = if Feature.enabled?(:linear_user_ci_owned_runners, self, default_enabled: :yaml)
owned_groups.self_and_descendant_ids
else
Gitlab::ObjectHierarchy.new(owned_groups).base_and_descendants.select(:id)
end
group_runners = Ci::RunnerNamespace
.where(namespace_id: Gitlab::ObjectHierarchy.new(owned_groups).base_and_descendants.select(:id))
.where(namespace_id: base_and_descendants)
.joins(:runner)
.select('ci_runners.*')

View File

@ -4,9 +4,10 @@
- data = learn_gitlab_data(@project)
- invite_members_open = session.delete(:confetti_post_signup)
= render 'projects/invite_members_modal', project: @project
- 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

@ -0,0 +1,8 @@
---
name: linear_user_ci_owned_runners
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68848
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339435
milestone: '14.6'
type: development
group: group::access
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: invite_for_help_continuous_onboarding
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73846
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345708
milestone: '14.5'
type: experiment
group: group::activation
default_enabled: false

View File

@ -1,83 +1,9 @@
---
stage: Enablement
group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
redirect_to: 'supported_os.md'
remove_date: '2022-02-18'
---
# OS Versions that are no longer supported **(FREE SELF)**
This document was moved to [another location](supported_os.md).
GitLab provides omnibus packages for operating systems only until their
EOL (End-Of-Life). After the EOL date of the OS, GitLab will stop releasing
official packages. The list of deprecated operating systems and the final GitLab
release for them can be found below:
| OS Version | End Of Life | Last supported GitLab version |
| --------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Raspbian Wheezy | [May 2015](https://downloads.raspberrypi.org/raspbian/images/raspbian-2015-05-07/) | [GitLab CE](https://packages.gitlab.com/app/gitlab/raspberry-pi2/search?q=gitlab-ce_8.17&dist=debian%2Fwheezy) 8.17 |
| OpenSUSE 13.2 | [January 2017](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-9.1&dist=opensuse%2F13.2) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-9.1&dist=opensuse%2F13.2) 9.1 |
| Ubuntu 12.04 | [April 2017](https://ubuntu.com/info/release-end-of-life) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_9.1&dist=ubuntu%2Fprecise) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_9.1&dist=ubuntu%2Fprecise) 9.1 |
| OpenSUSE 42.1 | [May 2017](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-9.3&dist=opensuse%2F42.1) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-9.3&dist=opensuse%2F42.1) 9.3 |
| OpenSUSE 42.2 | [January 2018](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-10.4&dist=opensuse%2F42.2) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-10.4&dist=opensuse%2F42.2) 10.4 |
| Debian Wheezy | [May 2018](https://www.debian.org/News/2018/20180601) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_11.6&dist=debian%2Fwheezy) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_11.6&dist=debian%2Fwheezy) 11.6 |
| Raspbian Jessie | [May 2017](https://downloads.raspberrypi.org/raspbian/images/raspbian-2017-07-05/) | [GitLab CE](https://packages.gitlab.com/app/gitlab/raspberry-pi2/search?q=gitlab-ce_11.7&dist=debian%2Fjessie) 11.7 |
| Ubuntu 14.04 | [April 2019](https://ubuntu.com/info/release-end-of-life) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_11.10&dist=ubuntu%2Ftrusty) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_11.10&dist=ubuntu%2Ftrusty) 11.10 |
| OpenSUSE 42.3 | [July 2019](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-12.1&dist=opensuse%2F42.3) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-12.1&dist=opensuse%2F42.3) 12.1 |
| OpenSUSE 15.0 | [December 2019](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-12.5&dist=opensuse%2F15.0) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-12.5&dist=opensuse%2F15.0) 12.5 |
| Raspbian Stretch | [June 2020](https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-04-09/) | [GitLab CE](https://packages.gitlab.com/app/gitlab/raspberry-pi2/search?q=gitlab-ce_13.2&dist=raspbian%2Fstretch) 13.3 |
| Debian Jessie | [June 2020](https://www.debian.org/News/2020/20200709) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_13.2&dist=debian%2Fjessie) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_13.2&dist=debian%2Fjessie) 13.3 |
| CentOS 6 | [November 2020](https://wiki.centos.org/About/Product) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=13.6&filter=all&filter=all&dist=el%2F6) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=13.6&filter=all&filter=all&dist=el%2F6) 13.6 |
| OpenSUSE 15.1 | [November 2020](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-13.12&dist=opensuse%2F15.1) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-13.12&dist=opensuse%2F15.2) 13.12 |
| Ubuntu 16.04 | [April 2021](https://ubuntu.com/info/release-end-of-life) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_13.12&dist=ubuntu%2Fxenial) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_13.12&dist=ubuntu%2Fxenial) 13.12 |
NOTE:
An exception to this deprecation policy is when we are unable to provide
packages for the next version of the operating system. The most common reason
for this our package repository provider, Packagecloud, not supporting newer
versions and hence we can't upload packages to it.
## Update GitLab package sources after upgrading the OS
After upgrading the Operating System (OS) as per its own documentation,
it may be necessary to also update the GitLab package source URL
in your package manager configuration.
If your package manager reports that no further updates are available,
although [new versions have been released](https://about.gitlab.com/releases/categories/releases/), repeat the
"Add the GitLab package repository" instructions
of the [Linux package install guide](https://about.gitlab.com/install/#content).
Future GitLab upgrades will now be fetched according to your upgraded OS.
## Supported Operating Systems
GitLab officially supports LTS versions of operating systems. While OSs like
Ubuntu have a clear distinction between LTS and non-LTS versions, there are
other OSs, openSUSE for example, that don't follow the LTS concept. Hence to
avoid confusion, the official policy is that at any point of time, all the
operating systems supported by GitLab are listed in the [installation
page](https://about.gitlab.com/install/).
The following lists the currently supported OSs and their possible EOL dates.
| OS Version | First supported GitLab version | Arch | OS EOL | Details |
| ---------------- | ------------------------------ | --------------- | ------------- | ------------------------------------------------------------ |
| CentOS 7 | GitLab CE / GitLab EE 7.10.0 | x86_64 | June 2024 | <https://wiki.centos.org/About/Product> |
| CentOS 8 | GitLab CE / GitLab EE 12.8.1 | x86_64, aarch64 | Dec 2021 | <https://wiki.centos.org/About/Product> |
| Debian 9 | GitLab CE / GitLab EE 9.3.0 | amd64 | 2022 | <https://wiki.debian.org/DebianReleases#Production_Releases> |
| Debian 10 | GitLab CE / GitLab EE 12.2.0 | amd64, arm64 | TBD | <https://wiki.debian.org/DebianReleases#Production_Releases> |
| OpenSUSE 15.2 | GitLab CE / GitLab EE 13.11.0 | x86_64, aarch64 | Dec 2021 | <https://en.opensuse.org/Lifetime> |
| OpenSUSE 15.3 | GitLab CE / GitLab EE 14.5.0 | x86_64, aarch64 | Nov 2022 | <https://en.opensuse.org/Lifetime> |
| SLES 12 | GitLab EE 9.0.0 | x86_64 | Oct 2027 | <https://www.suse.com/lifecycle/> |
| Ubuntu 18.04 | GitLab CE / GitLab EE 10.7.0 | amd64 | April 2023 | <https://wiki.ubuntu.com/Releases> |
| Ubuntu 20.04 | GitLab CE / GitLab EE 13.2.0 | amd64, arm64 | April 2025 | <https://wiki.ubuntu.com/Releases> |
| Raspbian Buster | GitLab CE 12.2.0 | armhf | 2022 | <https://wiki.debian.org/DebianReleases#Production_Releases> |
### Packages for ARM64
> [Introduced](https://gitlab.com/gitlab-org/gitlab-omnibus-builder/-/issues/27) in GitLab 13.4.
GitLab provides arm64/aarch64 packages for some supported operating systems.
You can see if your operating system architecture is supported in the table
above.
WARNING:
There are currently still some [known issues and limitation](https://gitlab.com/groups/gitlab-org/-/epics/4397)
running GitLab on ARM.
<!-- This redirect file can be deleted after <2022-02-18>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->

View File

@ -0,0 +1,89 @@
---
stage: Enablement
group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Supported Operating Systems
GitLab officially supports LTS versions of operating systems. While OSs like
Ubuntu have a clear distinction between LTS and non-LTS versions, there are
other OSs, openSUSE for example, that don't follow the LTS concept. Hence to
avoid confusion, the official policy is that at any point of time, all the
operating systems supported by GitLab are listed in the [installation
page](https://about.gitlab.com/install/).
The following lists the currently supported OSs and their possible EOL dates.
| OS Version | First supported GitLab version | Arch | OS EOL | Details |
| ---------------- | ------------------------------ | --------------- | ------------- | ------------------------------------------------------------ |
| CentOS 7 | GitLab CE / GitLab EE 7.10.0 | x86_64 | June 2024 | <https://wiki.centos.org/About/Product> |
| CentOS 8 | GitLab CE / GitLab EE 12.8.1 | x86_64, aarch64 | Dec 2021 | <https://wiki.centos.org/About/Product> |
| Debian 9 | GitLab CE / GitLab EE 9.3.0 | amd64 | 2022 | <https://wiki.debian.org/DebianReleases#Production_Releases> |
| Debian 10 | GitLab CE / GitLab EE 12.2.0 | amd64, arm64 | TBD | <https://wiki.debian.org/DebianReleases#Production_Releases> |
| OpenSUSE 15.2 | GitLab CE / GitLab EE 13.11.0 | x86_64, aarch64 | Dec 2021 | <https://en.opensuse.org/Lifetime> |
| OpenSUSE 15.3 | GitLab CE / GitLab EE 14.5.0 | x86_64, aarch64 | Nov 2022 | <https://en.opensuse.org/Lifetime> |
| SLES 12 | GitLab EE 9.0.0 | x86_64 | Oct 2027 | <https://www.suse.com/lifecycle/> |
| Ubuntu 18.04 | GitLab CE / GitLab EE 10.7.0 | amd64 | April 2023 | <https://wiki.ubuntu.com/Releases> |
| Ubuntu 20.04 | GitLab CE / GitLab EE 13.2.0 | amd64, arm64 | April 2025 | <https://wiki.ubuntu.com/Releases> |
| Raspbian Buster | GitLab CE 12.2.0 | armhf | 2022 | <https://wiki.debian.org/DebianReleases#Production_Releases> |
NOTE:
CentOS 8 will be EOL on December 31, 2021. In GitLab 14.5 and later,
[CentOS builds work in AlmaLinux](https://gitlab.com/gitlab-org/distribution/team-tasks/-/issues/954#note_730198505).
We will officially support all distributions that are binary compatible with Red Hat Enterprise Linux.
This gives users a path forward for their CentOS 8 builds at its end of life.
## Update GitLab package sources after upgrading the OS
After upgrading the Operating System (OS) as per its own documentation,
it may be necessary to also update the GitLab package source URL
in your package manager configuration.
If your package manager reports that no further updates are available,
although [new versions have been released](https://about.gitlab.com/releases/categories/releases/), repeat the
"Add the GitLab package repository" instructions
of the [Linux package install guide](https://about.gitlab.com/install/#content).
Future GitLab upgrades will now be fetched according to your upgraded OS.
## Packages for ARM64
> [Introduced](https://gitlab.com/gitlab-org/gitlab-omnibus-builder/-/issues/27) in GitLab 13.4.
GitLab provides arm64/aarch64 packages for some supported operating systems.
You can see if your operating system architecture is supported in the table
above.
WARNING:
There are currently still some [known issues and limitation](https://gitlab.com/groups/gitlab-org/-/epics/4397)
running GitLab on ARM.
## OS Versions that are no longer supported **(FREE SELF)**
GitLab provides omnibus packages for operating systems only until their
EOL (End-Of-Life). After the EOL date of the OS, GitLab will stop releasing
official packages. The list of deprecated operating systems and the final GitLab
release for them can be found below:
| OS Version | End Of Life | Last supported GitLab version |
| --------------- | ---------------------------------------------------------------------------------- | -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Raspbian Wheezy | [May 2015](https://downloads.raspberrypi.org/raspbian/images/raspbian-2015-05-07/) | [GitLab CE](https://packages.gitlab.com/app/gitlab/raspberry-pi2/search?q=gitlab-ce_8.17&dist=debian%2Fwheezy) 8.17 |
| OpenSUSE 13.2 | [January 2017](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-9.1&dist=opensuse%2F13.2) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-9.1&dist=opensuse%2F13.2) 9.1 |
| Ubuntu 12.04 | [April 2017](https://ubuntu.com/info/release-end-of-life) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_9.1&dist=ubuntu%2Fprecise) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_9.1&dist=ubuntu%2Fprecise) 9.1 |
| OpenSUSE 42.1 | [May 2017](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-9.3&dist=opensuse%2F42.1) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-9.3&dist=opensuse%2F42.1) 9.3 |
| OpenSUSE 42.2 | [January 2018](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-10.4&dist=opensuse%2F42.2) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-10.4&dist=opensuse%2F42.2) 10.4 |
| Debian Wheezy | [May 2018](https://www.debian.org/News/2018/20180601) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_11.6&dist=debian%2Fwheezy) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_11.6&dist=debian%2Fwheezy) 11.6 |
| Raspbian Jessie | [May 2017](https://downloads.raspberrypi.org/raspbian/images/raspbian-2017-07-05/) | [GitLab CE](https://packages.gitlab.com/app/gitlab/raspberry-pi2/search?q=gitlab-ce_11.7&dist=debian%2Fjessie) 11.7 |
| Ubuntu 14.04 | [April 2019](https://ubuntu.com/info/release-end-of-life) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_11.10&dist=ubuntu%2Ftrusty) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_11.10&dist=ubuntu%2Ftrusty) 11.10 |
| OpenSUSE 42.3 | [July 2019](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-12.1&dist=opensuse%2F42.3) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-12.1&dist=opensuse%2F42.3) 12.1 |
| OpenSUSE 15.0 | [December 2019](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-12.5&dist=opensuse%2F15.0) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-12.5&dist=opensuse%2F15.0) 12.5 |
| Raspbian Stretch | [June 2020](https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-04-09/) | [GitLab CE](https://packages.gitlab.com/app/gitlab/raspberry-pi2/search?q=gitlab-ce_13.2&dist=raspbian%2Fstretch) 13.3 |
| Debian Jessie | [June 2020](https://www.debian.org/News/2020/20200709) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_13.2&dist=debian%2Fjessie) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_13.2&dist=debian%2Fjessie) 13.3 |
| CentOS 6 | [November 2020](https://wiki.centos.org/About/Product) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=13.6&filter=all&filter=all&dist=el%2F6) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=13.6&filter=all&filter=all&dist=el%2F6) 13.6 |
| OpenSUSE 15.1 | [November 2020](https://en.opensuse.org/Lifetime#Discontinued_distributions) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce-13.12&dist=opensuse%2F15.1) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee-13.12&dist=opensuse%2F15.2) 13.12 |
| Ubuntu 16.04 | [April 2021](https://ubuntu.com/info/release-end-of-life) | [GitLab CE](https://packages.gitlab.com/app/gitlab/gitlab-ce/search?q=gitlab-ce_13.12&dist=ubuntu%2Fxenial) / [GitLab EE](https://packages.gitlab.com/app/gitlab/gitlab-ee/search?q=gitlab-ee_13.12&dist=ubuntu%2Fxenial) 13.12 |
NOTE:
An exception to this deprecation policy is when we are unable to provide
packages for the next version of the operating system. The most common reason
for this our package repository provider, PackageCloud, not supporting newer
versions and hence we can't upload packages to it.

View File

@ -1281,7 +1281,7 @@ in all of your GitLab Pages instances.
### 500 error with `securecookie: failed to generate random iv` and `Failed to save the session`
This problem most likely results from an [out-dated operating system](../package_information/deprecated_os.md).
This problem most likely results from an [out-dated operating system](../package_information/supported_os.md#os-versions-that-are-no-longer-supported).
The [Pages daemon uses the `securecookie` library](https://gitlab.com/search?group_id=9970&project_id=734943&repository_ref=master&scope=blobs&search=securecookie&snippets=false) to get random strings via [`crypto/rand` in Go](https://golang.org/pkg/crypto/rand/#pkg-variables).
This requires the `getrandom` system call or `/dev/urandom` to be available on the host OS.
Upgrading to an [officially supported operating system](https://about.gitlab.com/install/) is recommended.

View File

@ -618,7 +618,7 @@ users.count
# If that count looks sane:
# You can either block the users:
users.each { |user| user.block! }
users.each { |user| user.blocked? ? nil : user.block! }
# Or you can delete them:
# need 'current user' (your user) for auditing purposes

View File

@ -35,7 +35,7 @@ For the installation options, see [the main installation page](index.md).
Installation of GitLab on these operating systems is possible, but not supported.
Please see the [installation from source guide](installation.md) and the [installation guides](https://about.gitlab.com/install/) for more information.
Please see [OS versions that are no longer supported](../administration/package_information/deprecated_os.md) for Omnibus installs page
Please see [OS versions that are no longer supported](../administration/package_information/supported_os.md#os-versions-that-are-no-longer-supported) for Omnibus installs page
for a list of supported and unsupported OS versions as well as the last support GitLab version for that OS.
### Microsoft Windows

View File

@ -20,15 +20,15 @@ NOTE:
GitLab submits all issues to Akismet.
Akismet configuration is available to users on self-managed GitLab. Akismet is already enabled on
GitLab SaaS (GitLab.com), where it's configuration and management are handled by GitLab Inc.
GitLab SaaS (GitLab.com), where its configuration and management are handled by GitLab Inc.
## Configuration **(FREE SELF)**
## Configure Akismet **(FREE SELF)**
To use Akismet:
1. Go to the [Akismet sign-in page](https://akismet.com/account/).
1. Sign in or create a new account.
1. Click **Show** to reveal the API key, and copy the API key's value.
1. Select **Show** to reveal the API key, and copy the API key's value.
1. Sign in to GitLab as an administrator.
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > Reporting** (`/admin/application_settings/reporting`).
@ -38,7 +38,7 @@ To use Akismet:
![Screenshot of Akismet settings](img/akismet_settings.png)
## Training **(FREE SELF)**
## Train the Akismet filter **(FREE SELF)**
To better differentiate between spam and ham, you can train the Akismet
filter whenever there is a false positive or false negative.

View File

@ -317,39 +317,65 @@ main quota. You can find pricing for additional CI/CD minutes on the
- Are only used after the shared quota included in your subscription runs out.
- Roll over month to month.
To purchase additional minutes for your group on GitLab SaaS:
1. From your group, go to **Settings > Usage Quotas**.
1. Select **Buy additional minutes** and GitLab directs you to the Customers Portal.
1. Locate the subscription card that's linked to your group on GitLab SaaS, click **Buy more CI minutes**, and complete the details about the transaction.
1. Once we have processed your payment, the extra CI minutes are synced to your group namespace.
1. To confirm the available CI minutes, go to your group, then **Settings > Usage Quotas**.
The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any minutes rolled over from last month.
To purchase additional minutes for your personal namespace:
1. In the top-right corner, select your avatar.
1. Select **Edit profile**.
1. On the left sidebar, select **Usage Quotas**.
1. Select **Buy additional minutes** and GitLab redirects you to the Customers Portal.
1. Locate the subscription card that's linked to your personal namespace on GitLab SaaS, click **Buy more CI minutes**, and complete the details about the transaction. Once we have processed your payment, the extra CI minutes are synced to your personal namespace.
1. To confirm the available CI minutes for your personal projects, go to the **Usage Quotas** settings again.
1. Locate the subscription card that's linked to your personal namespace on GitLab SaaS, click **Buy more CI minutes**, and complete the details about the transaction.
The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any minutes rolled over from last month.
After we process your payment, the extra CI minutes are synced to your group
namespace.
To confirm the available CI minutes for your personal projects, go to the **Usage Quotas** settings again.
The **Additional minutes** displayed now includes the purchased additional CI
minutes, plus any minutes rolled over from last month.
Be aware that:
- If you have purchased extra CI minutes before the purchase of a paid plan,
we calculate a pro-rated charge for your paid plan. That means you may
be charged for less than one year because your subscription was previously
created with the extra CI minutes.
- After the extra CI minutes have been assigned to a Group, they can't be transferred
to a different Group by themselves, but they will transfer along with a subscription when
changing the linked namespace for the subscription.
- If you have used more minutes than your default quota, these minutes will
be deducted from your Additional Minutes quota immediately after your purchase of additional
minutes.
- Extra CI minutes assigned to one group cannot be transferred to a different
group.
- If you have used more minutes than your default quota, those minutes are
deducted from your Additional Minutes quota immediately after your purchase of
additional minutes.
### Purchase additional CI minutes on GitLab SaaS
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6574) in GitLab 14.5.
If you're using GitLab SaaS, you can purchase additional CI minutes so your
pipelines aren't blocked after you have used all your CI minutes from your
main quota. You can find pricing for additional CI/CD minutes on the
[GitLab Pricing page](https://about.gitlab.com/pricing/). Additional minutes:
- Are only used after the shared quota included in your subscription runs out.
- Roll over month to month.
To purchase additional minutes for your group on GitLab SaaS:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Settings > Usage Quotas**.
1. Select **Buy additional minutes**.
1. Complete the details about the transaction.
After we process your payment, the extra CI minutes are synced to your group
namespace.
To confirm the available CI minutes, go to your group, and then select
**Settings > Usage Quotas**.
The **Additional minutes** displayed now includes the purchased additional CI
minutes, plus any minutes rolled over from last month.
Be aware that:
- Extra CI minutes assigned to one group cannot be transferred to a different
group.
- If you have used more minutes than your default quota, those minutes are
deducted from your Additional Minutes quota immediately after your purchase of
additional minutes.
## Storage subscription

View File

@ -82,13 +82,18 @@ See the guide to [plan your GitLab upgrade](plan_your_upgrade.md).
## Checking for background migrations before upgrading
Certain releases may require different migrations to be
finished before you update to the newer version. Additionally check
[batched migrations](#batched-background-migrations) from GitLab 14.0.
finished before you update to the newer version.
[Batched migrations](#batched-background-migrations) are a migration type available in GitLab 14.0 and later.
Background migrations and batched migrations not the same, so you should check that both are
complete before updating.
Decrease the time required to complete these migrations by increasing the number of
[Sidekiq workers](../administration/operations/extra_sidekiq_processes.md)
that can process jobs in the `background_migration` queue.
### Background migrations
**For Omnibus installations:**
```shell

View File

@ -188,7 +188,7 @@ For the GitLab Community Edition, replace `gitlab-ee` with
### GitLab 13.7 and later unavailable on Amazon Linux 2
Amazon Linux 2 is not an [officially supported operating system](../../administration/package_information/deprecated_os.md#supported-operating-systems).
Amazon Linux 2 is not an [officially supported operating system](../../administration/package_information/supported_os.md).
However, in past the [official package installation script](https://packages.gitlab.com/gitlab/gitlab-ee/install)
installed the `el/6` package repository if run on Amazon Linux. From GitLab 13.7, we no longer
provide `el/6` packages so administrators must run the [installation script](https://packages.gitlab.com/gitlab/gitlab-ee/install)

View File

@ -18,7 +18,7 @@ General notes:
to create your plan, share details of your architecture, including:
- How is GitLab installed?
- What is the operating system of the node?
(check [OS versions that are no longer supported](../administration/package_information/deprecated_os.md) to confirm that later updates are available).
(check [OS versions that are no longer supported](../administration/package_information/supported_os.md#os-versions-that-are-no-longer-supported) to confirm that later updates are available).
- Is it a single-node or a multi-node setup? If multi-node, share any architectural details about each node with us.
- Are you using [GitLab Geo](../administration/geo/index.md)? If so, share any architectural details about each secondary node.
- What else might be unique or interesting in your setup that might be important for us to understand?
@ -112,7 +112,7 @@ to your instance and then upgrade it for any relevant features you're using.
- [Determine what upgrade path](index.md#upgrade-paths) to follow.
- Account for any [version-specific update instructions](index.md#version-specific-upgrading-instructions).
- Account for any [version-specific changes](package/index.md#version-specific-changes).
- Check the [OS compatibility with the target GitLab version](../administration/package_information/deprecated_os.md).
- Check the [OS compatibility with the target GitLab version](../administration/package_information/supported_os.md).
- Due to background migrations, plan to pause any further upgrades after upgrading
to a new major version.
[All migrations must finish running](index.md#checking-for-background-migrations-before-upgrading)

View File

@ -29,15 +29,15 @@ Prerequisites:
- You need to [authenticate with the API](../../../api/index.md#authentication). If authenticating with a deploy token, it must be configured with the `write_package_registry` scope.
```plaintext
PUT /projects/:id/packages/terraform/modules/:module_name/:module_system/:module_version/file
PUT /projects/:id/packages/terraform/modules/:module-name/:module-system/:module-version/file
```
| Attribute | Type | Required | Description |
| -------------------| --------------- | ---------| -------------------------------------------------------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../../../api/index.md#namespaced-path-encoding). |
| `module_name` | string | yes | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), or hyphens (`-`) and cannot exceed 64 characters.
| `module_system` | string | yes | The package system. It can contain only lowercase letters (`a-z`) and numbers (`0-9`), and cannot exceed 64 characters.
| `module_version` | string | yes | The package version. It must be valid according to the [Semantic Versioning Specification](https://semver.org/).
| `module-name` | string | yes | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), or hyphens (`-`) and cannot exceed 64 characters.
| `module-system` | string | yes | The package system. It can contain only lowercase letters (`a-z`) and numbers (`0-9`), and cannot exceed 64 characters.
| `module-version` | string | yes | The package version. It must be valid according to the [Semantic Versioning Specification](https://semver.org/).
Provide the file content in the request body.
@ -97,7 +97,7 @@ You can then reference your Terraform Module from a downstream Terraform project
```plaintext
module "<module>" {
source = "gitlab.com/<namespace>/<module_name>/<module_system>"
source = "gitlab.com/<namespace>/<module-name>/<module-system>"
}
```

View File

@ -76,7 +76,7 @@ a self-managed instance from an old server to a new server.
The backups produced don't depend on the operating system running GitLab. You can therefore use
the restore method to switch between different operating system distributions or versions, as long
as the same GitLab version [is available for installation](../../../administration/package_information/deprecated_os.md).
as the same GitLab version [is available for installation](../../../administration/package_information/supported_os.md).
To instead merge two self-managed GitLab instances together, use the instructions in
[Migrate from self-managed GitLab to GitLab.com](#migrate-from-self-managed-gitlab-to-gitlabcom).

View File

@ -20583,6 +20583,9 @@ msgstr ""
msgid "LearnGitLab|Use your new GitLab workflow to deploy your application, monitor its health, and keep it secure:"
msgstr ""
msgid "LearnGitLab|Your team is growing! You've successfully invited new team members to the %{projectName} project."
msgstr ""
msgid "LearnGitlab|Creating your onboarding experience..."
msgstr ""

View File

@ -55,9 +55,9 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.220.0",
"@gitlab/svgs": "1.221.0",
"@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "32.36.0",
"@gitlab/ui": "32.38.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.4-1",
"@rails/ujs": "6.1.4-1",

View File

@ -199,7 +199,7 @@ module QA
Page::Project::Artifact::Show.perform do |artifacts|
artifacts.go_to_directory('node_modules')
artifacts.go_to_directory("@#{registry_scope}")
expect(artifacts).to have_content( "#{project.name}")
expect(artifacts).to have_content("#{project.name}")
end
project.visit!

View File

@ -91,7 +91,7 @@ module QA
file_path: 'package.json',
content: <<~JSON
{
"name": "@#{registry_scope}/mypackage",
"name": "#{package.name}",
"version": "1.0.0",
"description": "Example package for GitLab npm registry",
"publishConfig": {
@ -133,7 +133,7 @@ module QA
end
end
it "push and pull a npm package via CI using a #{params[:token_name]}", quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/344537', type: :investigating } do
it "push and pull a npm package via CI using a #{params[:token_name]}" do
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'
@ -168,7 +168,7 @@ module QA
Page::Project::Artifact::Show.perform do |artifacts|
artifacts.go_to_directory('node_modules')
artifacts.go_to_directory("@#{registry_scope}")
expect(artifacts).to have_content("mypackage")
expect(artifacts).to have_content('mypackage')
end
project.visit!

View File

@ -5,14 +5,15 @@ require 'spec_helper'
RSpec.describe Projects::LearnGitlabController do
describe 'GET #index' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
let_it_be(:project) { create(:project, namespace: create(:group)) }
let(:learn_gitlab_enabled) { true }
let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
subject { get :index, params: params }
subject(:action) { get :index, params: params }
before do
project.namespace.add_owner(user)
allow(controller.helpers).to receive(:learn_gitlab_enabled?).and_return(learn_gitlab_enabled)
end
@ -32,6 +33,10 @@ RSpec.describe Projects::LearnGitlabController do
it { is_expected.to have_gitlab_http_status(:not_found) }
end
it_behaves_like 'tracks assignment and records the subject', :invite_for_help_continuous_onboarding, :namespace do
subject { project.namespace }
end
end
end
end

View File

@ -99,12 +99,13 @@ describe('experiment Utilities', () => {
describe('isExperimentVariant', () => {
describe.each`
experiment | variant | input | output
${ABC_KEY} | ${DEFAULT_VARIANT} | ${[ABC_KEY, DEFAULT_VARIANT]} | ${true}
${ABC_KEY} | ${'_variant_name'} | ${[ABC_KEY, '_variant_name']} | ${true}
${ABC_KEY} | ${'_variant_name'} | ${[ABC_KEY, '_bogus_name']} | ${false}
${ABC_KEY} | ${'_variant_name'} | ${['boguskey', '_variant_name']} | ${false}
${undefined} | ${undefined} | ${[ABC_KEY, '_variant_name']} | ${false}
experiment | variant | input | output
${ABC_KEY} | ${CANDIDATE_VARIANT} | ${[ABC_KEY]} | ${true}
${ABC_KEY} | ${DEFAULT_VARIANT} | ${[ABC_KEY, DEFAULT_VARIANT]} | ${true}
${ABC_KEY} | ${'_variant_name'} | ${[ABC_KEY, '_variant_name']} | ${true}
${ABC_KEY} | ${'_variant_name'} | ${[ABC_KEY, '_bogus_name']} | ${false}
${ABC_KEY} | ${'_variant_name'} | ${['boguskey', '_variant_name']} | ${false}
${undefined} | ${undefined} | ${[ABC_KEY, '_variant_name']} | ${false}
`(
'with input=$input, experiment=$experiment, variant=$variant',
({ experiment, variant, input, output }) => {

View File

@ -28,6 +28,7 @@ import {
MEMBERS_MODAL_DEFAULT_TITLE,
MEMBERS_PLACEHOLDER,
MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT,
LEARN_GITLAB,
} from '~/invite_members/constants';
import eventHub from '~/invite_members/event_hub';
import axios from '~/lib/utils/axios_utils';
@ -268,6 +269,14 @@ describe('InviteMembersModal', () => {
expect(findTasksToBeDone().exists()).toBe(false);
});
describe('when opened from the Learn GitLab page', () => {
it('does render the tasks to be done', () => {
setupComponent({ source: LEARN_GITLAB }, {}, []);
expect(findTasksToBeDone().exists()).toBe(true);
});
});
});
describe('rendering the tasks', () => {
@ -465,7 +474,6 @@ describe('InviteMembersModal', () => {
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
});
it('includes the non-default selected areas of focus', () => {
@ -492,7 +500,23 @@ describe('InviteMembersModal', () => {
});
it('displays the successful toastMessage', () => {
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
onComplete: expect.any(Function),
});
});
});
describe('when opened from a Learn GitLab page', () => {
it('emits the `showSuccessfulInvitationsAlert` event', async () => {
eventHub.$emit('openModal', { inviteeType: 'members', source: LEARN_GITLAB });
jest.spyOn(eventHub, '$emit').mockImplementation();
clickInviteButton();
await waitForPromises();
expect(eventHub.$emit).toHaveBeenCalledWith('showSuccessfulInvitationsAlert');
});
});
});
@ -649,7 +673,6 @@ describe('InviteMembersModal', () => {
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
});
it('includes the non-default selected areas of focus', () => {
@ -672,7 +695,9 @@ describe('InviteMembersModal', () => {
});
it('displays the successful toastMessage', () => {
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
onComplete: expect.any(Function),
});
});
});
});
@ -711,13 +736,14 @@ describe('InviteMembersModal', () => {
it('displays the successful toast message when email has already been invited', async () => {
mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EMAIL_TAKEN);
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
clickInviteButton();
await waitForPromises();
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
onComplete: expect.any(Function),
});
expect(findMembersSelect().props('validationState')).toBe(null);
});
@ -782,7 +808,6 @@ describe('InviteMembersModal', () => {
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
jest.spyOn(wrapper.vm, 'trackInvite');
});
@ -800,7 +825,9 @@ describe('InviteMembersModal', () => {
});
it('displays the successful toastMessage', () => {
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
onComplete: expect.any(Function),
});
});
});
@ -855,7 +882,6 @@ describe('InviteMembersModal', () => {
wrapper.setData({ inviteeType: 'group' });
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'groupShareWithGroup').mockResolvedValue({ data: groupPostData });
jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
clickInviteButton();
});
@ -865,7 +891,9 @@ describe('InviteMembersModal', () => {
});
it('displays the successful toastMessage', () => {
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
onComplete: expect.any(Function),
});
});
});
@ -930,6 +958,13 @@ describe('InviteMembersModal', () => {
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(MEMBER_AREAS_OF_FOCUS.view);
});
it('tracks the view for learn_gitlab source', () => {
eventHub.$emit('openModal', { inviteeType: 'members', source: LEARN_GITLAB });
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(LEARN_GITLAB);
});
it('tracks the invite for areas_of_focus', () => {
eventHub.$emit('openModal', { inviteeType: 'members' });

View File

@ -2,6 +2,8 @@
exports[`Learn GitLab renders correctly 1`] = `
<div>
<!---->
<div
class="row"
>
@ -131,66 +133,60 @@ exports[`Learn GitLab renders correctly 1`] = `
<div
class="gl-mb-4"
>
<span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Set up CI/CD"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Set up CI/CD
</a>
</span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Set up CI/CD"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Set up CI/CD
</a>
<!---->
</div>
<div
class="gl-mb-4"
>
<span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Start a free Ultimate trial"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Start a free Ultimate trial
</a>
</span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Start a free Ultimate trial"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Start a free Ultimate trial
</a>
<!---->
</div>
<div
class="gl-mb-4"
>
<span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Add code owners"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Add code owners
</a>
</span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Add code owners"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Add code owners
</a>
<span
class="gl-font-style-italic gl-text-gray-500"
@ -204,22 +200,20 @@ exports[`Learn GitLab renders correctly 1`] = `
<div
class="gl-mb-4"
>
<span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Add merge request approval"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Add merge request approval
</a>
</span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Add merge request approval"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Add merge request approval
</a>
<span
class="gl-font-style-italic gl-text-gray-500"
@ -269,44 +263,40 @@ exports[`Learn GitLab renders correctly 1`] = `
<div
class="gl-mb-4"
>
<span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Create an issue"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Create an issue
</a>
</span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Create an issue"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Create an issue
</a>
<!---->
</div>
<div
class="gl-mb-4"
>
<span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Submit a merge request"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Submit a merge request
</a>
</span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Submit a merge request"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Submit a merge request
</a>
<!---->
</div>
@ -349,22 +339,20 @@ exports[`Learn GitLab renders correctly 1`] = `
<div
class="gl-mb-4"
>
<span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Run a Security scan using CI/CD"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Run a Security scan using CI/CD
</a>
</span>
<a
class="gl-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Run a Security scan using CI/CD"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
>
Run a Security scan using CI/CD
</a>
<!---->
</div>

View File

@ -1,4 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { stubExperiments } from 'helpers/experimentation_helper';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import eventHub from '~/invite_members/event_hub';
import LearnGitlabSectionLink from '~/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue';
const defaultAction = 'gitWrite';
@ -23,6 +26,9 @@ describe('Learn GitLab Section Link', () => {
});
};
const openInviteMembesrModalLink = () =>
wrapper.find('[data-testid="invite-for-help-continuous-onboarding-experiment-link"]');
it('renders no icon when not completed', () => {
createWrapper(undefined, { completed: false });
@ -46,4 +52,54 @@ describe('Learn GitLab Section Link', () => {
expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(true);
});
describe('rendering a link to open the invite_members modal instead of a regular link', () => {
it.each`
action | experimentVariant | showModal
${'userAdded'} | ${'candidate'} | ${true}
${'userAdded'} | ${'control'} | ${false}
${defaultAction} | ${'candidate'} | ${false}
${defaultAction} | ${'control'} | ${false}
`(
'when the invite_for_help_continuous_onboarding experiment has variant: $experimentVariant and action is $action, the modal link is shown: $showModal',
({ action, experimentVariant, showModal }) => {
stubExperiments({ invite_for_help_continuous_onboarding: experimentVariant });
createWrapper(action);
expect(openInviteMembesrModalLink().exists()).toBe(showModal);
},
);
});
describe('clicking the link to open the invite_members modal', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit').mockImplementation();
stubExperiments({ invite_for_help_continuous_onboarding: 'candidate' });
createWrapper('userAdded');
});
it('calls the eventHub', () => {
openInviteMembesrModalLink().vm.$emit('click');
expect(eventHub.$emit).toHaveBeenCalledWith('openModal', {
inviteeType: 'members',
source: 'learn_gitlab',
tasksToBeDoneEnabled: true,
});
});
it('tracks the click', async () => {
const trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
triggerEvent(openInviteMembesrModalLink().element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
label: 'Invite your colleagues',
property: 'Growth::Activation::Experiment::InviteForHelpContinuousOnboarding',
});
unmockTracking();
});
});
});

View File

@ -1,20 +1,35 @@
import { GlProgressBar } from '@gitlab/ui';
import { GlProgressBar, GlAlert } 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';
import { testActions, testSections, testProject } from './mock_data';
describe('Learn GitLab', () => {
let wrapper;
let sidebar;
let inviteMembersOpen = false;
const createWrapper = () => {
wrapper = mount(LearnGitlab, {
propsData: { actions: testActions, sections: testSections, inviteMembersOpen },
propsData: {
actions: testActions,
sections: testSections,
project: testProject,
inviteMembersOpen,
},
});
};
beforeEach(() => {
sidebar = document.createElement('div');
sidebar.innerHTML = `
<div class="sidebar-top-level-items">
<div class="active">
<div class="count"></div>
</div>
</div>
`;
document.body.appendChild(sidebar);
createWrapper();
});
@ -22,6 +37,7 @@ describe('Learn GitLab', () => {
wrapper.destroy();
wrapper = null;
inviteMembersOpen = false;
sidebar.remove();
});
it('renders correctly', () => {
@ -66,4 +82,26 @@ describe('Learn GitLab', () => {
expect(spy).not.toHaveBeenCalled();
});
});
describe('when the showSuccessfulInvitationsAlert event is fired', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
beforeEach(() => {
eventHub.$emit('showSuccessfulInvitationsAlert');
});
it('displays the successful invitations alert', () => {
expect(findAlert().exists()).toBe(true);
});
it('displays a message with the project name', () => {
expect(findAlert().text()).toBe(
"Your team is growing! You've successfully invited new team members to the test-project project.",
);
});
it('modifies the sidebar percentage', () => {
expect(sidebar.textContent.trim()).toBe('22%');
});
});
});

View File

@ -57,3 +57,7 @@ export const testSections = {
svg: 'plan.svg',
},
};
export const testProject = {
name: 'test-project',
};

View File

@ -6,6 +6,7 @@ RSpec.describe InviteMembersHelper do
include Devise::Test::ControllerHelpers
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group, projects: [project]) }
let_it_be(:developer) { create(:user, developer_projects: [project]) }
let(:owner) { project.owner }
@ -64,48 +65,13 @@ RSpec.describe InviteMembersHelper do
end
context 'tasks_to_be_done' do
using RSpec::Parameterized::TableSyntax
subject(:output) { helper.common_invite_modal_dataset(source) }
let_it_be(:source) { project }
before do
stub_experiments(invite_members_for_task: true)
end
context 'when not logged in' do
before do
allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' })
end
it "doesn't have the tasks to be done attributes" do
expect(output[:tasks_to_be_done_options]).to be_nil
expect(output[:projects]).to be_nil
expect(output[:new_project_path]).to be_nil
end
end
context 'when logged in but the open_modal param is not present' do
before do
allow(helper).to receive(:current_user).and_return(developer)
end
it "doesn't have the tasks to be done attributes" do
expect(output[:tasks_to_be_done_options]).to be_nil
expect(output[:projects]).to be_nil
expect(output[:new_project_path]).to be_nil
end
end
context 'when logged in and the open_modal param is present' do
before do
allow(helper).to receive(:current_user).and_return(developer)
allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' })
end
context 'for a group' do
let_it_be(:source) { create(:group, projects: [project]) }
it 'has the expected attributes', :aggregate_failures do
shared_examples_for 'including the tasks to be done attributes' do
it 'includes the tasks to be done attributes when expected' do
if expected?
expect(output[:tasks_to_be_done_options]).to eq(
[
{ value: :code, text: 'Create/import code into a project (repository)' },
@ -117,24 +83,75 @@ RSpec.describe InviteMembersHelper do
[{ id: project.id, title: project.title }].to_json
)
expect(output[:new_project_path]).to eq(
new_project_path(namespace_id: source.id)
source.is_a?(Project) ? '' : new_project_path(namespace_id: group.id)
)
else
expect(output[:tasks_to_be_done_options]).to be_nil
expect(output[:projects]).to be_nil
expect(output[:new_project_path]).to be_nil
end
end
end
context 'for a project' do
it 'has the expected attributes', :aggregate_failures do
expect(output[:tasks_to_be_done_options]).to eq(
[
{ value: :code, text: 'Create/import code into a project (repository)' },
{ value: :ci, text: 'Set up CI/CD pipelines to build, test, deploy, and monitor code' },
{ value: :issues, text: 'Create/import issues (tickets) to collaborate on ideas and plan work' }
].to_json
)
expect(output[:projects]).to eq(
[{ id: project.id, title: project.title }].to_json
)
expect(output[:new_project_path]).to eq('')
context 'the invite_members_for_task experiment' do
where(:invite_members_for_task_enabled?, :open_modal_param_present?, :logged_in?, :expected?) do
true | true | true | true
true | true | false | false
true | false | true | false
true | false | false | false
false | true | true | false
false | true | false | false
false | false | true | false
false | false | false | false
end
with_them do
before do
allow(helper).to receive(:current_user).and_return(developer) if logged_in?
stub_experiments(invite_members_for_task: true) if invite_members_for_task_enabled?
allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' }) if open_modal_param_present?
end
context 'when the source is a project' do
let_it_be(:source) { project }
it_behaves_like 'including the tasks to be done attributes'
end
context 'when the source is a group' do
let_it_be(:source) { group }
it_behaves_like 'including the tasks to be done attributes'
end
end
end
context 'the invite_for_help_continuous_onboarding experiment' do
where(:invite_for_help_continuous_onboarding?, :logged_in?, :expected?) do
true | true | true
true | false | false
false | true | false
false | false | false
end
with_them do
before do
allow(helper).to receive(:current_user).and_return(developer) if logged_in?
stub_experiments(invite_for_help_continuous_onboarding: :candidate) if invite_for_help_continuous_onboarding?
end
context 'when the source is a project' do
let_it_be(:source) { project }
it_behaves_like 'including the tasks to be done attributes'
end
context 'when the source is a group' do
let_it_be(:source) { group }
let(:expected?) { false }
it_behaves_like 'including the tasks to be done attributes'
end
end
end

View File

@ -60,6 +60,7 @@ RSpec.describe LearnGitlabHelper do
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 }
let(:onboarding_project_data) { Gitlab::Json.parse(learn_gitlab_data[:project]).deep_symbolize_keys }
shared_examples 'has all data' do
it 'has all actions' do
@ -82,6 +83,11 @@ RSpec.describe LearnGitlabHelper 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
it 'has all project data', :aggregate_failures do
expect(onboarding_project_data.keys).to contain_exactly(:name)
expect(onboarding_project_data.values).to match_array([project.name])
end
end
it_behaves_like 'has all data'

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do
let(:mock_request) { OpenStruct.new(env: {}) }
let(:mock_request) { double('env', env: {}) }
let(:response_body) { nil }
describe ".parameters" do
@ -76,7 +76,7 @@ RSpec.describe Gitlab::GrapeLogging::Loggers::ExceptionLogger do
describe 'when an exception is available' do
let(:exception) { RuntimeError.new('This is a test') }
let(:mock_request) do
OpenStruct.new(
double('env',
env: {
::API::Helpers::API_EXCEPTION_ENV => exception
}

View File

@ -20,9 +20,9 @@ RSpec.describe U2fRegistration do
describe '#create_webauthn_registration' do
shared_examples_for 'creates webauthn registration' do
it 'creates webauthn registration' do
u2f_registration.save!
created_record = u2f_registration
webauthn_registration = WebauthnRegistration.where(u2f_registration_id: u2f_registration.id)
webauthn_registration = WebauthnRegistration.where(u2f_registration_id: created_record.id)
expect(webauthn_registration).to exist
end
end
@ -43,13 +43,16 @@ RSpec.describe U2fRegistration do
it 'logs error' do
allow(Gitlab::Auth::U2fWebauthnConverter).to receive(:new).and_raise('boom!')
expect(Gitlab::AppJsonLogger).to(
receive(:error).with(a_hash_including(event: 'u2f_migration',
error: 'RuntimeError',
message: 'U2F to WebAuthn conversion failed'))
)
u2f_registration.save!
allow_next_instance_of(U2fRegistration) do |u2f_registration|
allow(u2f_registration).to receive(:id).and_return(123)
end
expect(Gitlab::ErrorTracking).to(
receive(:track_exception).with(kind_of(StandardError),
u2f_registration_id: 123))
u2f_registration
end
end
end

View File

@ -3673,309 +3673,321 @@ RSpec.describe User do
end
describe '#ci_owned_runners' do
let(:user) { create(:user) }
shared_examples 'ci_owned_runners examples' do
let(:user) { create(:user) }
shared_examples :nested_groups_owner do
context 'when the user is the owner of a multi-level group' do
before do
set_permissions_for_users
end
shared_examples :nested_groups_owner do
context 'when the user is the owner of a multi-level group' do
before do
set_permissions_for_users
end
it 'loads all the runners in the tree of groups' do
expect(user.ci_owned_runners).to contain_exactly(runner, group_runner)
end
it 'loads all the runners in the tree of groups' do
expect(user.ci_owned_runners).to contain_exactly(runner, group_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(true)
expect(user.owns_runner?(group_runner)).to eq(true)
it 'returns true for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(true)
expect(user.owns_runner?(group_runner)).to eq(true)
end
end
end
end
shared_examples :group_owner do
context 'when the user is the owner of a one level group' do
before do
shared_examples :group_owner do
context 'when the user is the owner of a one level group' do
before do
group.add_owner(user)
end
it 'loads the runners in the group' do
expect(user.ci_owned_runners).to contain_exactly(group_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(group_runner)).to eq(true)
end
end
end
shared_examples :project_owner do
context 'when the user is the owner of a project' do
it 'loads the runner belonging to the project' do
expect(user.ci_owned_runners).to contain_exactly(runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(true)
end
end
end
shared_examples :project_member do
context 'when the user is a maintainer' do
before do
add_user(:maintainer)
end
it 'loads the runners of the project' do
expect(user.ci_owned_runners).to contain_exactly(project_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(true)
end
end
context 'when the user is a developer' do
before do
add_user(:developer)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
context 'when the user is a reporter' do
before do
add_user(:reporter)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
context 'when the user is a guest' do
before do
add_user(:guest)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
end
shared_examples :group_member do
context 'when the user is a maintainer' do
before do
add_user(:maintainer)
end
it 'does not load the runners of the group' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a developer' do
before do
add_user(:developer)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a reporter' do
before do
add_user(:reporter)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a guest' do
before do
add_user(:guest)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
end
context 'without any projects nor groups' do
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(create(:ci_runner))).to eq(false)
end
end
context 'with runner in a personal project' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:project) { create(:project, namespace: namespace) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it_behaves_like :project_owner
end
context 'with group runner in a non owned group' do
let!(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
def add_user(access)
group.add_user(user, access)
end
it_behaves_like :group_member
end
context 'with group runner in an owned group' do
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
it_behaves_like :group_owner
end
context 'with group runner in an owned group and group runner in a different owner subgroup' do
let!(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:another_user) { create(:user) }
def set_permissions_for_users
group.add_owner(user)
subgroup.add_owner(another_user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an an owned group and a group runner in that same group' do
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it 'loads the runners in the group' do
expect(user.ci_owned_runners).to contain_exactly(group_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(group_runner)).to eq(true)
end
end
end
shared_examples :project_owner do
context 'when the user is the owner of a project' do
it 'loads the runner belonging to the project' do
expect(user.ci_owned_runners).to contain_exactly(runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(true)
end
end
end
shared_examples :project_member do
context 'when the user is a maintainer' do
before do
add_user(:maintainer)
end
it 'loads the runners of the project' do
expect(user.ci_owned_runners).to contain_exactly(project_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(true)
end
it_behaves_like :nested_groups_owner
end
context 'when the user is a developer' do
before do
add_user(:developer)
context 'with personal project runner in an owned group and a group runner in a subgroup' do
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned group in an owned namespace and a group runner in that group' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project) { create(:project, namespace: namespace, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned namespace, an owned group, a subgroup and a group runner in that subgroup' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:project) { create(:project, namespace: namespace, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with a project runner that belong to projects that belong to a not owned group' do
let!(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
let!(:project_runner) { create(:ci_runner, :project, projects: [project]) }
def add_user(access)
project.add_user(user, access)
end
it_behaves_like :project_member
end
context 'with project runners that belong to projects that do not belong to any group' do
let!(:project) { create(:project) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
context 'when the user is a reporter' do
before do
add_user(:reporter)
context 'with a group runner that belongs to a subgroup of a group owned by another user' do
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:another_user) { create(:user) }
def add_user(access)
subgroup.add_user(user, access)
group.add_user(another_user, :owner)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
end
context 'when the user is a guest' do
before do
add_user(:guest)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
end
it_behaves_like :group_member
end
end
shared_examples :group_member do
context 'when the user is a maintainer' do
before do
add_user(:maintainer)
end
it_behaves_like 'ci_owned_runners examples'
it 'does not load the runners of the group' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
context 'when feature flag :linear_user_ci_owned_runners is disabled' do
before do
stub_feature_flags(linear_user_ci_owned_runners: false)
end
context 'when the user is a developer' do
before do
add_user(:developer)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a reporter' do
before do
add_user(:reporter)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
context 'when the user is a guest' do
before do
add_user(:guest)
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(runner)).to eq(false)
end
end
end
context 'without any projects nor groups' do
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(create(:ci_runner))).to eq(false)
end
end
context 'with runner in a personal project' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:project) { create(:project, namespace: namespace) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it_behaves_like :project_owner
end
context 'with group runner in a non owned group' do
let!(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
def add_user(access)
group.add_user(user, access)
end
it_behaves_like :group_member
end
context 'with group runner in an owned group' do
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
it_behaves_like :group_owner
end
context 'with group runner in an owned group and group runner in a different owner subgroup' do
let!(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:another_user) { create(:user) }
def set_permissions_for_users
group.add_owner(user)
subgroup.add_owner(another_user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an an owned group and a group runner in that same group' do
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned group and a group runner in a subgroup' do
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:project) { create(:project, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned group in an owned namespace and a group runner in that group' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:group) { create(:group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
let!(:project) { create(:project, namespace: namespace, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with personal project runner in an owned namespace, an owned group, a subgroup and a group runner in that subgroup' do
let!(:namespace) { create(:user_namespace, owner: user) }
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:group_runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:project) { create(:project, namespace: namespace, group: group) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
def set_permissions_for_users
group.add_owner(user)
end
it_behaves_like :nested_groups_owner
end
context 'with a project runner that belong to projects that belong to a not owned group' do
let!(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
let!(:project_runner) { create(:ci_runner, :project, projects: [project]) }
def add_user(access)
project.add_user(user, access)
end
it_behaves_like :project_member
end
context 'with project runners that belong to projects that do not belong to any group' do
let!(:project) { create(:project) }
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
end
end
context 'with a group runner that belongs to a subgroup of a group owned by another user' do
let!(:group) { create(:group) }
let!(:subgroup) { create(:group, parent: group) }
let!(:runner) { create(:ci_runner, :group, groups: [subgroup]) }
let!(:another_user) { create(:user) }
def add_user(access)
subgroup.add_user(user, access)
group.add_user(another_user, :owner)
end
it_behaves_like :group_member
it_behaves_like 'ci_owned_runners examples'
end
end

View File

@ -11,12 +11,12 @@ RSpec.describe API::ImportGithub do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:provider_username) { user.username }
let(:provider_user) { OpenStruct.new(login: provider_username) }
let(:provider_user) { double('provider', login: provider_username) }
let(:provider_repo) do
OpenStruct.new(
double('provider',
name: 'vim',
full_name: "#{provider_username}/vim",
owner: OpenStruct.new(login: provider_username)
owner: double('provider', login: provider_username)
)
end

View File

@ -24,6 +24,16 @@ RSpec.describe Groups::CreateService, '#execute' do
end
end
context 'when `setup_for_company:true` is passed' do
let(:params) { group_params.merge(setup_for_company: true) }
let(:service) { described_class.new(user, params) }
let(:created_group) { service.execute }
it 'creates group with the specified setup_for_company' do
expect(created_group.setup_for_company).to eq(true)
end
end
context 'creating a group with `default_branch_protection` attribute' do
let(:params) { group_params.merge(default_branch_protection: Gitlab::Access::PROTECTION_NONE) }
let(:service) { described_class.new(user, params) }

View File

@ -914,20 +914,20 @@
stylelint-declaration-strict-value "1.7.7"
stylelint-scss "3.18.0"
"@gitlab/svgs@1.220.0":
version "1.220.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.220.0.tgz#188bdefe86cdbf8be1faa7a92dbac31c728066c7"
integrity sha512-9QRXQG6IrQoviU86g2Y4l19yE81UyEg/iMoGetMfUdQ64NW6unLN7uNbUaO1ws1J0p7uG0dKwR6ohD7tEUPLFA==
"@gitlab/svgs@1.221.0":
version "1.221.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.221.0.tgz#1804d181b09672d13005d49818eb2da040d67488"
integrity sha512-Xn29eer39uVqeuefL/3hVuxo2tazHoEFIXC0F7DnESmBggrcjueNM2tuBUk40oaX6kCzM2Bn4Qn9ESIR+V0PgQ==
"@gitlab/tributejs@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
"@gitlab/ui@32.36.0":
version "32.36.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.36.0.tgz#f3fb6f86dc51a6941bd230c047f0014364732efd"
integrity sha512-cX/+P011FD6TrD9/tKuG5xX/tSx9oRwb/bRA45RG1zbkmRh/ohr5zQMuNK2utrHFl4OqrcPirqXMyFl+kKnCdg==
"@gitlab/ui@32.38.0":
version "32.38.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.38.0.tgz#580bda8daacdf23c1f6a75d77f6df44b1dbe1726"
integrity sha512-BS0+4JicfuiCbaWTTok0dQUzUCI8m8t5T7//DQUQqqwCZLYeJlb1AxMatd6IjwcdE0m+AhST3iOZi2x+hDrkbQ==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "2.20.1"