Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-03-15 18:08:10 +00:00
parent cb7f766437
commit 7f08e6916d
88 changed files with 1165 additions and 628 deletions

View File

@ -1 +1 @@
d0d5fa790767c12eeadb40a1ecfbc00fde2a4768
4ef97df05e54269d90fdbd4d2f59fcc29b1afcdf

View File

@ -153,7 +153,7 @@ gem 'html-pipeline', '~> 2.13.2'
gem 'deckar01-task_list', '2.3.1'
gem 'gitlab-markup', '~> 1.8.0'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.23.2'
gem 'commonmarker', '~> 0.23.4'
gem 'kramdown', '~> 2.3.1'
gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.3.2'
@ -405,7 +405,7 @@ group :development, :test, :danger do
end
group :development, :test, :coverage do
gem 'simplecov', '~> 0.18.5', require: false
gem 'simplecov', '~> 0.21', require: false
gem 'simplecov-lcov', '~> 0.8.0', require: false
gem 'simplecov-cobertura', '~> 1.3.1', require: false
gem 'undercover', '~> 0.4.4', require: false

View File

@ -202,7 +202,7 @@ GEM
open4 (~> 1.3)
coderay (1.1.3)
colored2 (3.1.2)
commonmarker (0.23.2)
commonmarker (0.23.4)
concurrent-ruby (1.1.9)
connection_pool (2.2.5)
contracts (0.11.0)
@ -281,7 +281,7 @@ GEM
diffy (3.3.0)
discordrb-webhooks (3.4.2)
rest-client (>= 2.0.0)
docile (1.3.2)
docile (1.4.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.5.0.rc2)
@ -1207,13 +1207,15 @@ GEM
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simple_po_parser (1.1.2)
simplecov (0.18.5)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-cobertura (1.3.1)
simplecov (~> 0.8)
simplecov-html (0.12.3)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
sixarm_ruby_unaccent (1.2.0)
slack-messenger (2.3.4)
snowplow-tracker (0.6.1)
@ -1438,7 +1440,7 @@ DEPENDENCIES
capybara-screenshot (~> 1.0.22)
carrierwave (~> 1.3)
charlock_holmes (~> 0.7.7)
commonmarker (~> 0.23.2)
commonmarker (~> 0.23.4)
concurrent-ruby (~> 1.1)
connection_pool (~> 2.0)
countries (~> 3.0)
@ -1648,7 +1650,7 @@ DEPENDENCIES
sidekiq-cron (~> 1.2)
sigdump (~> 0.2.4)
simple_po_parser (~> 1.1.2)
simplecov (~> 0.18.5)
simplecov (~> 0.21)
simplecov-cobertura (~> 1.3.1)
simplecov-lcov (~> 0.8.0)
slack-messenger (~> 2.3.4)

View File

@ -3,6 +3,7 @@ import { GlButton, GlCollapse, GlIcon, GlBadge, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import folderQuery from '../graphql/queries/folder.query.graphql';
import { ENVIRONMENT_COUNT_BY_SCOPE } from '../constants';
import EnvironmentItem from './new_environment_item.vue';
export default {
@ -56,7 +57,8 @@ export default {
return this.visible ? this.$options.i18n.collapse : this.$options.i18n.expand;
},
count() {
return this.folder?.[`${this.scope}Count`] ?? 0;
const count = ENVIRONMENT_COUNT_BY_SCOPE[this.scope];
return this.folder?.[count] ?? 0;
},
folderClass() {
return { 'gl-font-weight-bold': this.visible };

View File

@ -9,6 +9,7 @@ import environmentToDeleteQuery from '../graphql/queries/environment_to_delete.q
import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql';
import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql';
import environmentToChangeCanaryQuery from '../graphql/queries/environment_to_change_canary.query.graphql';
import { ENVIRONMENTS_SCOPE } from '../constants';
import EnvironmentFolder from './new_environment_folder.vue';
import EnableReviewAppModal from './enable_review_app_modal.vue';
import StopEnvironmentModal from './stop_environment_modal.vue';
@ -82,12 +83,14 @@ export default {
},
modalId: 'enable-review-app-info',
data() {
const { page = '1', scope = 'available' } = queryToObject(window.location.search);
const { page = '1', scope } = queryToObject(window.location.search);
return {
interval: undefined,
isReviewAppModalVisible: false,
page: parseInt(page, 10),
scope,
scope: Object.values(ENVIRONMENTS_SCOPE).includes(scope)
? scope
: ENVIRONMENTS_SCOPE.AVAILABLE,
environmentToDelete: {},
environmentToRollback: {},
environmentToStop: {},
@ -188,6 +191,7 @@ export default {
});
},
},
ENVIRONMENTS_SCOPE,
};
</script>
<template>
@ -209,7 +213,10 @@ export default {
query-param-name="scope"
@primary="showReviewAppModal"
>
<gl-tab query-param-value="available" @click="setScope('available')">
<gl-tab
:query-param-value="$options.ENVIRONMENTS_SCOPE.AVAILABLE"
@click="setScope($options.ENVIRONMENTS_SCOPE.AVAILABLE)"
>
<template #title>
<span>{{ $options.i18n.available }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">
@ -217,7 +224,10 @@ export default {
</gl-badge>
</template>
</gl-tab>
<gl-tab query-param-value="stopped" @click="setScope('stopped')">
<gl-tab
:query-param-value="$options.ENVIRONMENTS_SCOPE.STOPPED"
@click="setScope($options.ENVIRONMENTS_SCOPE.STOPPED)"
>
<template #title>
<span>{{ $options.i18n.stopped }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">

View File

@ -38,3 +38,13 @@ export const CANARY_STATUS = {
};
export const CANARY_UPDATE_MODAL = 'confirm-canary-change';
export const ENVIRONMENTS_SCOPE = {
AVAILABLE: 'available',
STOPPED: 'stopped',
};
export const ENVIRONMENT_COUNT_BY_SCOPE = {
[ENVIRONMENTS_SCOPE.AVAILABLE]: 'availableCount',
[ENVIRONMENTS_SCOPE.STOPPED]: 'stoppedCount',
};

View File

@ -1,5 +1,4 @@
<script>
import { GlFilteredSearchToken } from '@gitlab/ui';
import { mapState } from 'vuex';
import {
getParameterByName,
@ -7,46 +6,24 @@ import {
queryToObject,
redirectTo,
} from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import {
SEARCH_TOKEN_TYPE,
SORT_QUERY_PARAM_NAME,
ACTIVE_TAB_QUERY_PARAM_NAME,
} from '~/members/constants';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
AVAILABLE_FILTERED_SEARCH_TOKENS,
} from 'ee_else_ce/members/constants';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
export default {
name: 'MembersFilteredSearchBar',
components: { FilteredSearchBar },
availableTokens: [
{
type: 'two_factor',
icon: 'lock',
title: s__('Members|2FA'),
token: GlFilteredSearchToken,
unique: true,
operators: OPERATOR_IS_ONLY,
options: [
{ value: 'enabled', title: s__('Members|Enabled') },
{ value: 'disabled', title: s__('Members|Disabled') },
],
requiredPermissions: 'canManageMembers',
},
{
type: 'with_inherited_permissions',
icon: 'group',
title: s__('Members|Membership'),
token: GlFilteredSearchToken,
unique: true,
operators: OPERATOR_IS_ONLY,
options: [
{ value: 'exclude', title: s__('Members|Direct') },
{ value: 'only', title: s__('Members|Inherited') },
],
},
],
inject: ['namespace', 'sourceId', 'canManageMembers'],
availableTokens: AVAILABLE_FILTERED_SEARCH_TOKENS,
inject: {
namespace: {},
sourceId: {},
canManageMembers: {},
canFilterByEnterprise: { default: false },
},
data() {
return {
initialFilterValue: [],

View File

@ -1,4 +1,7 @@
import { __ } from '~/locale';
import { GlFilteredSearchToken } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
export const FIELD_KEY_ACCOUNT = 'account';
export const FIELD_KEY_SOURCE = 'source';
@ -82,6 +85,38 @@ export const DEFAULT_SORT = {
sortDesc: false,
};
export const FILTERED_SEARCH_TOKEN_TWO_FACTOR = {
type: 'two_factor',
icon: 'lock',
title: s__('Members|2FA'),
token: GlFilteredSearchToken,
unique: true,
operators: OPERATOR_IS_ONLY,
options: [
{ value: 'enabled', title: s__('Members|Enabled') },
{ value: 'disabled', title: s__('Members|Disabled') },
],
requiredPermissions: 'canManageMembers',
};
export const FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS = {
type: 'with_inherited_permissions',
icon: 'group',
title: s__('Members|Membership'),
token: GlFilteredSearchToken,
unique: true,
operators: OPERATOR_IS_ONLY,
options: [
{ value: 'exclude', title: s__('Members|Direct') },
{ value: 'only', title: s__('Members|Inherited') },
],
};
export const AVAILABLE_FILTERED_SEARCH_TOKENS = [
FILTERED_SEARCH_TOKEN_TWO_FACTOR,
FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS,
];
export const AVATAR_SIZE = 48;
export const MEMBER_TYPES = {

View File

@ -18,6 +18,7 @@ export const initMembersApp = (el, options) => {
sourceId,
canManageMembers,
canExportMembers,
canFilterByEnterprise,
exportCsvPath,
...vuexStoreAttributes
} = parseDataAttributes(el);
@ -60,6 +61,7 @@ export const initMembersApp = (el, options) => {
currentUserId: gon.current_user_id || null,
sourceId,
canManageMembers,
canFilterByEnterprise,
canExportMembers,
exportCsvPath,
},

View File

@ -21,7 +21,7 @@ initMembersApp(document.querySelector('.js-group-members-list-app'), {
requestFormatter: groupMemberRequestFormatter,
filteredSearchBar: {
show: true,
tokens: ['two_factor', 'with_inherited_permissions'],
tokens: ['two_factor', 'with_inherited_permissions', 'enterprise'],
searchParam: 'search',
placeholder: s__('Members|Filter members'),
recentSearchesStorageKey: 'group_members',

View File

@ -32,7 +32,7 @@ export default {
);
},
openInNewTab() {
return ACTION_LABELS[this.action]?.openInNewTab === true;
return ACTION_LABELS[this.action]?.openInNewTab === true || this.value.openInNewTab === true;
},
},
methods: {
@ -65,8 +65,6 @@ export default {
data-testid="uncompleted-learn-gitlab-link"
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>

View File

@ -62,7 +62,6 @@ export const ACTION_LABELS = {
description: s__('LearnGitLab|Scan your code to uncover vulnerabilities before deploying.'),
section: 'deploy',
position: 1,
openInNewTab: true,
},
issueCreated: {
title: s__('LearnGitLab|Create an issue'),

View File

@ -439,6 +439,12 @@
.na {
color: inherit;
}
// Rouge `Comment` token (quoted text in email body)
.c {
color: $gl-grayish-blue;
font-style: italic;
}
}
}
}

View File

@ -1,24 +1,4 @@
@import 'framework/mixins';
@import 'framework/variables';
img {
max-width: 100%;
height: auto;
}
p.details {
font-style: italic;
color: $gray-500;
}
.footer > p {
font-size: small;
color: $gray-500;
}
pre.commit-message {
white-space: pre-wrap;
}
@import 'notify_base';
.gl-label-scoped {
border: 2px solid currentColor;
@ -52,6 +32,4 @@ pre.commit-message {
border: 1px solid $gray-100;
border-radius: $border-radius-small;
}
@include email-code-block;
}

View File

@ -0,0 +1,25 @@
@import 'framework/mixins';
@import 'framework/variables';
img {
max-width: 100%;
height: auto;
}
p.details {
font-style: italic;
color: $gray-500;
}
.footer > p {
font-size: small;
color: $gray-500;
}
pre.commit-message {
white-space: pre-wrap;
}
.content {
@include email-code-block;
}

View File

@ -0,0 +1,68 @@
// Import a subset of the GitLab UI framework:
// keep parts that affect elements that can appear in emails;
// omit Bootstrap Reboot since it adds unnecessary styles to every element.
@import 'notify_base';
@import 'bootstrap/scss/functions';
@import 'bootstrap/scss/variables';
@import 'bootstrap/scss/mixins';
@import 'bootstrap/scss/code';
@import '@gitlab/ui/src/scss/variables';
@import '@gitlab/ui/src/scss/utility-mixins/index';
@import '@gitlab/ui/src/scss/components';
@import 'bootstrap_migration';
@import 'framework/common';
@import 'framework/gfm';
@import 'framework/kbd';
@import 'framework/tables';
@import 'framework/typography';
@import 'framework/emojis';
body {
font-family: $regular-font;
font-size: inherit;
}
a {
text-decoration: none;
}
.content {
.md {
padding: 1rem 0;
}
hr {
border: 1px solid #e1e1e1;
}
blockquote {
border-top-width: 0;
border-bottom-width: 0;
border-right-width: 0;
&:dir(rtl) {
border-left-width: 0;
border-right-width: inherit;
}
}
table {
border-collapse: collapse;
}
.diff-table.code,
table.code {
width: auto;
td {
padding: inherit;
pre {
background-color: inherit;
margin: 0;
padding: 0;
border: inherit;
}
}
}
}

View File

@ -29,13 +29,14 @@ class Projects::EnvironmentsController < Projects::ApplicationController
feature_category :continuous_delivery
def index
@environments = project.environments
.with_state(params[:scope] || :available)
@project = ProjectPresenter.new(project, current_user: current_user)
respond_to do |format|
format.html
format.json do
@environments = project.environments
.with_state(params[:scope] || :available)
Gitlab::PollingInterval.set_header(response, interval: 3_000)
environments_count_by_state = project.environments.count_by_state
@ -52,14 +53,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController
# Returns all environments for a given folder
# rubocop: disable CodeReuse/ActiveRecord
def folder
folder_environments = project.environments.where(environment_type: params[:id])
@environments = folder_environments.with_state(params[:scope] || :available)
.order(:name)
@folder = params[:id]
respond_to do |format|
format.html
format.json do
folder_environments = project.environments.where(environment_type: params[:id])
@environments = folder_environments.with_state(params[:scope] || :available)
.order(:name)
render json: {
environments: serialize_environments(request, response),
available_count: folder_environments.available.count,

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
# Projects::RedirectController is used to resolve the route projects/:id.
# It's helpful for this to be in its own controller so that the
# ProjectsController can assume that :namespace_id exists
class Projects::RedirectController < ::ApplicationController
skip_before_action :authenticate_user!
feature_category :projects
def redirect_from_id
project = Project.find(params[:id])
if can?(current_user, :read_project, project)
redirect_to project
else
render_404
end
end
end

View File

@ -17,10 +17,10 @@ class ProjectsController < Projects::ApplicationController
around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
before_action :disable_query_limiting, only: [:show, :create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :resolve, :unfoldered_environment_names]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :unfoldered_environment_names]
before_action :redirect_git_extension, only: [:show]
before_action :project, except: [:index, :new, :create, :resolve]
before_action :repository, except: [:index, :new, :create, :resolve]
before_action :project, except: [:index, :new, :create]
before_action :repository, except: [:index, :new, :create]
before_action :verify_git_import_enabled, only: [:create]
before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
before_action :present_project, only: [:edit]
@ -48,7 +48,7 @@ class ProjectsController < Projects::ApplicationController
feature_category :projects, [
:index, :show, :new, :create, :edit, :update, :transfer,
:destroy, :resolve, :archive, :unarchive, :toggle_star, :activity
:destroy, :archive, :unarchive, :toggle_star, :activity
]
feature_category :source_code_management, [:remove_fork, :housekeeping, :refs]
@ -324,16 +324,6 @@ class ProjectsController < Projects::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
def resolve
@project = Project.find(params[:id])
if can?(current_user, :read_project, @project)
redirect_to @project
else
render_404
end
end
def unfoldered_environment_names
respond_to do |format|
format.json do

View File

@ -3,12 +3,21 @@
module GroupIssuableResolver
extend ActiveSupport::Concern
class_methods do
def include_subgroups(name_of_things)
argument :include_subgroups, GraphQL::Types::Boolean,
required: false,
default_value: false,
description: "Include #{name_of_things} belonging to subgroups"
end
included do
argument :include_subgroups, GraphQL::Types::Boolean,
required: false,
default_value: false,
description: "Include #{issuable_collection_name} belonging to subgroups"
argument :include_archived, GraphQL::Types::Boolean,
required: false,
default_value: false,
description: "Return #{issuable_collection_name} from archived projects"
end
def resolve(**args)
args[:non_archived] = !args.delete(:include_archived)
super
end
end

View File

@ -3,9 +3,11 @@
module Resolvers
class GroupIssuesResolver < BaseIssuesResolver
include GroupIssuableResolver
def self.issuable_collection_name
'issues'
end
include_subgroups 'issues'
include GroupIssuableResolver
def ready?(**args)
if args.dig(:not, :release_tag).present?

View File

@ -2,13 +2,16 @@
module Resolvers
class GroupMergeRequestsResolver < MergeRequestsResolver
def self.issuable_collection_name
'merge requests'
end
include GroupIssuableResolver
alias_method :group, :object
type Types::MergeRequestType.connection_type, null: true
include_subgroups 'merge requests'
accept_assignee
accept_author

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
module LearnGitlabHelper
IMAGE_PATH_PLAN = "learn_gitlab/section_plan.svg"
IMAGE_PATH_DEPLOY = "learn_gitlab/section_deploy.svg"
IMAGE_PATH_WORKSPACE = "learn_gitlab/section_workspace.svg"
def learn_gitlab_enabled?(project)
return false unless current_user
@ -25,19 +29,7 @@ module LearnGitlabHelper
def onboarding_actions_data(project)
attributes = onboarding_progress(project).attributes.symbolize_keys
urls_to_use = nil
experiment(
:change_continuous_onboarding_link_urls,
namespace: project.namespace,
actor: current_user,
sticky_to: project.namespace
) do |e|
e.control { urls_to_use = action_urls }
e.candidate { urls_to_use = new_action_urls(project) }
end
urls_to_use.to_h do |action, url|
action_urls(project).to_h do |action, url|
[
action,
url: url,
@ -50,13 +42,13 @@ module LearnGitlabHelper
def onboarding_sections_data
{
workspace: {
svg: image_path("learn_gitlab/section_workspace.svg")
svg: image_path(IMAGE_PATH_WORKSPACE)
},
plan: {
svg: image_path("learn_gitlab/section_plan.svg")
svg: image_path(IMAGE_PATH_PLAN)
},
deploy: {
svg: image_path("learn_gitlab/section_deploy.svg")
svg: image_path(IMAGE_PATH_DEPLOY)
}
}
end
@ -65,22 +57,20 @@ module LearnGitlabHelper
{ 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)
end
def new_action_urls(project)
action_urls.merge(
def action_urls(project)
action_issue_urls.merge(
issue_created: project_issues_path(project),
git_write: project_path(project),
pipeline_created: project_pipelines_path(project),
merge_request_created: project_merge_requests_path(project),
user_added: project_members_url(project),
security_scan_enabled: project_security_configuration_path(project)
)
end
def action_issue_urls
LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) }
end
def learn_gitlab_project
@learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project
end

View File

@ -3046,10 +3046,6 @@ class Project < ApplicationRecord
Projects::SyncEvent.enqueue_worker
end
end
def allow_serialization?(options = nil)
Feature.disabled?(:block_project_serialization, self, default_enabled: :yaml) || super
end
end
Project.prepend_mod_with('Project')

View File

@ -3,7 +3,10 @@
%meta{ content: "text/html; charset=utf-8", "http-equiv" => "Content-Type" }
%title
GitLab
= stylesheet_link_tag 'notify'
- if Feature.enabled?(:enhanced_notify_css)
= stylesheet_link_tag 'notify_enhanced'
- else
= stylesheet_link_tag 'notify'
= yield :head
%body
.content

View File

@ -5,7 +5,10 @@
%title
GitLab
-# haml-lint:enable NoPlainNodes
= stylesheet_link_tag 'notify'
- if Feature.enabled?(:enhanced_notify_css)
= stylesheet_link_tag 'notify_enhanced'
- else
= stylesheet_link_tag 'notify'
= yield :head
%body
.content

View File

@ -25,11 +25,11 @@
= content_for :head do
= stylesheet_link_tag 'mailers/highlighted_diff_email'
%table
%table.code
= render partial: "projects/diffs/email_line",
collection: discussion.truncated_diff_lines(diff_limit: diff_limit),
as: :line,
locals: { diff_file: discussion.diff_file }
%div{ style: note_style }
.md{ style: note_style }
= markdown(note.note, pipeline: :email, author: note.author, current_user: @recipient, issuable_reference_expansion_enabled: true)

View File

@ -8,5 +8,5 @@
This issue is due on: #{@issue.due_date.to_s(:medium)}
- if @issue.description
%div
= markdown(@issue.description, pipeline: :email, author: @issue.author, current_user: @recipient, issuable_reference_expansion_enabled: true)
.md
= markdown(@issue.description, pipeline: :email, author: @issue.author, current_user: @recipient, issuable_reference_expansion_enabled: true)

View File

@ -7,5 +7,5 @@
= assignees_label(@issue)
- if @issue.description
%div
= markdown(@issue.description, pipeline: :email, author: @issue.author, current_user: @recipient, issuable_reference_expansion_enabled: true)
.md
= markdown(@issue.description, pipeline: :email, author: @issue.author, current_user: @recipient, issuable_reference_expansion_enabled: true)

View File

@ -15,5 +15,5 @@
= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter
- if @merge_request.description
%div
.md
= markdown(@merge_request.description, pipeline: :email, author: @merge_request.author, current_user: @recipient, issuable_reference_expansion_enabled: true)

View File

@ -1,7 +1,7 @@
- release_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
- description_details = { tag: @release.tag, name: @project.name, release_link_start: release_link_start, release_link_end: '</a>'.html_safe }
%div{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
.md{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%p
= _("A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it.").html_safe % description_details

View File

@ -1,5 +1,5 @@
- if Gitlab::CurrentSettings.email_author_in_body
%div
= _("%{author_link} wrote:").html_safe % { author_link: link_to(@note.author_name, user_url(@note.author)) }
%div
.md
= markdown(@note.note, pipeline: :email, author: @note.author, issuable_reference_expansion_enabled: true)

View File

@ -247,6 +247,7 @@ module Gitlab
config.assets.precompile << "mailer.css"
config.assets.precompile << "mailer_client_specific.css"
config.assets.precompile << "notify.css"
config.assets.precompile << "notify_enhanced.css"
config.assets.precompile << "mailers/*.css"
config.assets.precompile << "page_bundles/_mixins_and_variables_and_functions.css"
config.assets.precompile << "page_bundles/admin/application_settings_metrics_and_profiling.css"

View File

@ -1,8 +1,8 @@
---
name: block_project_serialization
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81900
name: enhanced_notify_css
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78604
rollout_issue_url:
milestone: '14.9'
milestone: '14.8'
type: development
group: group::workspace
group: group::project management
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: change_continuous_onboarding_link_urls
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71408
rollout_issue_url:
milestone: '14.5'
type: experiment
group: group::conversion
default_enabled: false

View File

@ -269,7 +269,7 @@ Rails.application.routes.draw do
resources :projects, only: [:index, :new, :create]
get '/projects/:id' => 'projects#resolve'
get '/projects/:id' => 'projects/redirect#redirect_from_id'
draw :git_http
draw :api

View File

@ -0,0 +1,18 @@
---
# Error: gitlab.HeadingContent
#
# Checks for generic, unhelpful subheadings.
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: existence
message: 'Rename the subheading "%s", or re-purpose the content elsewhere.'
level: warning
scope: heading
link: https://docs.gitlab.com/ee/development/documentation/styleguide/#headings-1
ignorecase: false
tokens:
- How it works
- Limitations
- Overview
- Use cases?
- Important notes?

View File

@ -200,7 +200,7 @@ This list of limitations only reflects the latest version of GitLab. If you are
- Pushing directly to a **secondary** site redirects (for HTTP) or proxies (for SSH) the request to the **primary** site instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab/-/issues/1381), except when using Git over HTTP with credentials embedded in the URI. For example, `https://user:password@secondary.tld`.
- The **primary** site has to be online for OAuth login to happen. Existing sessions and Git are not affected. Support for the **secondary** site to use an OAuth provider independent from the primary is [being planned](https://gitlab.com/gitlab-org/gitlab/-/issues/208465).
- The installation takes multiple manual steps that together can take about an hour depending on circumstances. We are working on improving this experience. See [Omnibus GitLab issue #2978](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/2978) for details.
- The installation takes multiple manual steps that together can take about an hour depending on circumstances. Consider using [the GitLab Environment Toolkit](https://gitlab.com/gitlab-org/gitlab-environment-toolkit) to deploy and operate production GitLab instances based on our [Reference Architectures](../reference_architectures/index.md), including automation of common daily tasks. We are planning to [improve Geo's installation even further](https://gitlab.com/groups/gitlab-org/-/epics/1465).
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** site.
- GitLab Runners cannot register with a **secondary** site. Support for this is [planned for the future](https://gitlab.com/gitlab-org/gitlab/-/issues/3294).
- [Selective synchronization](replication/configuration.md#selective-synchronization) only limits what repositories and files are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accommodate compliance / export control use cases.

View File

@ -11473,6 +11473,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupissuesepicid"></a>`epicId` | [`String`](#string) | ID of an epic associated with the issues, "none" and "any" values are supported. |
| <a id="groupissuesiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
| <a id="groupissuesiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. |
| <a id="groupissuesincludearchived"></a>`includeArchived` | [`Boolean`](#boolean) | Return issues from archived projects. |
| <a id="groupissuesincludesubepics"></a>`includeSubepics` | [`Boolean`](#boolean) | Whether to include subepics when filtering issues by epicId. |
| <a id="groupissuesincludesubgroups"></a>`includeSubgroups` | [`Boolean`](#boolean) | Include issues belonging to subgroups. |
| <a id="groupissuesiterationid"></a>`iterationId` | [`[ID]`](#id) | List of iteration Global IDs applied to the issue. |
@ -11606,6 +11607,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
| <a id="groupmergerequestsdraft"></a>`draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. |
| <a id="groupmergerequestsiids"></a>`iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. |
| <a id="groupmergerequestsincludearchived"></a>`includeArchived` | [`Boolean`](#boolean) | Return merge requests from archived projects. |
| <a id="groupmergerequestsincludesubgroups"></a>`includeSubgroups` | [`Boolean`](#boolean) | Include merge requests belonging to subgroups. |
| <a id="groupmergerequestslabels"></a>`labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. |
| <a id="groupmergerequestsmergedafter"></a>`mergedAfter` | [`Time`](#time) | Merge requests merged after this date. |

View File

@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/21368) in GitLab 11.8.
This API supports managing [group labels](../user/project/labels.md#project-labels-and-group-labels).
This API supports managing [group labels](../user/project/labels.md#types-of-labels).
It allows users to list, create, update, and delete group labels. Furthermore, users can subscribe to and
unsubscribe from group labels.

View File

@ -210,6 +210,28 @@ Issues created by users on GitLab Premium or higher include the `epic` property:
}
```
Issues created by users on GitLab Premium or higher include the `iteration` property:
```json
{
"iteration": {
"id":90,
"iid":4,
"sequence":2,
"group_id":162,
"title":null,
"description":null,
"state":2,
"created_at":"2022-03-14T05:21:11.929Z",
"updated_at":"2022-03-14T05:21:11.929Z",
"start_date":"2022-03-08",
"due_date":"2022-03-14",
"web_url":"http://gitlab.com/groups/my-group/-/iterations/90"
}
...
}
```
Issues created by users on GitLab Ultimate include the `health_status` property:
```json

View File

@ -7,7 +7,7 @@ description: Learn how to contribute to GitLab Documentation.
# GitLab Documentation guidelines
The GitLab documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions for every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, and the use of GitLab with other applications.
The GitLab documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions for every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features and the use of GitLab with other applications.
In addition to this page, the following resources can help you craft and contribute to documentation:
@ -55,9 +55,9 @@ docs-only merge requests using the following guide:
[Contributions to GitLab docs](workflow.md) are welcome from the entire GitLab community.
To ensure that GitLab docs are current, there are special processes and responsibilities for all [feature changes](workflow.md), that is development work that impacts the appearance, usage, or administration of a feature.
To ensure that the GitLab docs are current, there are special processes and responsibilities for all [feature changes](workflow.md), that is development work that impacts the appearance, usage, or administration of a feature.
However, anyone can contribute [documentation improvements](workflow.md) that are not associated with a feature change. For example, adding a new doc on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab.
However, anyone can contribute [documentation improvements](workflow.md) that are not associated with a feature change. For example, adding a new document on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab.
## Markdown and styles
@ -87,8 +87,8 @@ belongs to, as well as an information block as described below:
- `group`: The [Group](https://about.gitlab.com/company/team/structure/#product-groups)
to which the majority of the page's content belongs.
- `info`: The following line, which provides direction to contributors regarding
how to contact the Technical Writer associated with the page's Stage and
Group:
how to contact the Technical Writer associated with the page's stage and
group:
```plaintext
To determine the technical writer assigned to the Stage/Group
@ -116,7 +116,7 @@ The following metadata should be added when a page is moved to another location:
location to which visitors should be redirected for a moved page.
[Learn more](redirects.md).
- `disqus_identifier`: Identifier for Disqus commenting system. Used to keep
comments with a page that's been moved to a new URL.
comments with a page that has been moved to a new URL.
[Learn more](redirects.md#redirections-for-pages-with-disqus-comments).
### Comments metadata
@ -192,8 +192,8 @@ For example:
1. The change shows up in the 14.5 self-managed release, due to missing the release cutoff
for 14.4.
The exact cutoff date for each release is flexible, and can be earlier or later
than expected due to holidays, weekends, or other events. In general, MRs merged
The exact cutoff date for each release is flexible, and can be sooner or later
than expected due to holidays, weekends or other events. In general, MRs merged
by the 17th should be present in the release on the 22nd, though it is not guaranteed.
If it is important that a documentation update is present in that month's release,
merge it as early as possible.
@ -209,7 +209,7 @@ with the following conventions:
- It's relative to the `doc/` directory in the GitLab repository.
- It omits the `.md` extension.
- It doesn't end with a slash (`/`).
- It doesn't end with a forward slash (`/`).
The help text follows the [Pajamas guidelines](https://design.gitlab.com/usability/helping-users/#formatting-help-content).
@ -316,7 +316,7 @@ process. This is configured in the `Dangerfile` in the GitLab repository under
## Automatic screenshot generator
You can now set up an automatic screenshot generator to take and compress screenshots, with the
You can now set up an automatic screenshot generator to take and compress screenshots with the
help of a configuration file known as **screenshot generator**.
### Use the tool

View File

@ -68,7 +68,10 @@ Because of these limitations we recommend you:
> Introduced in GitLab 14.1.
In GitLab versions 14.1 and later, free self-managed users running [GitLab EE](../ee_features.md) can receive paid features by registering with GitLab and sending us activity data through Service Ping. Features introduced here do not remove the feature from its paid tier. Users can continue to access the features in a paid tier without sharing usage data.
In GitLab versions 14.1 and later, GitLab Free customers with a self-managed instance running
[GitLab EE](../ee_features.md) can receive paid features by registering with GitLab and sending us
activity data through Service Ping. Features introduced here do not remove the feature from its paid
tier. Users can continue to access the features in a paid tier without sharing usage data.
#### Features available in 14.1 and later

View File

@ -46,7 +46,7 @@ To enable cluster image scanning in your pipeline, you need the following:
- [GitLab Runner](https://docs.gitlab.com/runner/)
with the [`docker`](https://docs.gitlab.com/runner/executors/docker.html)
or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html)
executor.
executor on Linux/amd64.
- Docker `18.09.03` or later installed on the same computer as the runner. If you're using the
shared runners on GitLab.com, then this is already the case.
- [Starboard Operator](https://aquasecurity.github.io/starboard/v0.10.3/operator/installation/kubectl/)

View File

@ -50,7 +50,7 @@ To enable container scanning in your pipeline, you need the following:
- Container Scanning runs in the `test` stage, which is available by default. If you redefine the stages in the `.gitlab-ci.yml` file, the `test` stage is required.
- [GitLab Runner](https://docs.gitlab.com/runner/) with the [`docker`](https://docs.gitlab.com/runner/executors/docker.html)
or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor.
or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor on Linux/amd64.
- Docker `18.09.03` or higher installed on the same computer as the runner. If you're using the
shared runners on GitLab.com, then this is already the case.
- An image matching the [supported distributions](#supported-distributions).

View File

@ -172,6 +172,7 @@ Filter a group to find members. By default, all members in the group and subgrou
- To view members in the group only, select **Membership = Direct**.
- To view members of the group and its subgroups, select **Membership = Inherited**.
- To view members with two-factor authentication enabled or disabled, select **2FA = Enabled** or **Disabled**.
- [In GitLab 14.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/349887), to view GitLab users created by [SAML SSO](saml_sso/index.md) or [SCIM provisioning](saml_sso/scim_setup.md) select **Enterprise = true**.
### Search a group

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -536,7 +536,7 @@ changing a label.
A typical workflow of using an issue board would be:
1. You have [created](labels.md#label-management) and [prioritized](labels.md#label-priority)
1. You [create](labels.md#create-a-label) and [prioritize](labels.md#set-label-priority)
labels to categorize your issues.
1. You have a bunch of issues (ideally labeled).
1. You visit the issue board and start [creating lists](#create-a-new-list) to

View File

@ -49,7 +49,7 @@ Ties are broken arbitrarily. Only the highest prioritized label is checked,
and labels with a lower priority are ignored.
For more information, see [issue 14523](https://gitlab.com/gitlab-org/gitlab/-/issues/14523).
To learn more about priority labels, read the [Labels](../labels.md#label-priority) documentation.
To learn how to change label priority, see [Label priority](../labels.md#set-label-priority).
## Sorting by last updated
@ -98,7 +98,9 @@ When you sort by **Priority**, the issue order changes to sort in this order:
1. Issues with a higher priority label.
1. Issues without a prioritized label.
To learn more about priority, read the [Labels](../labels.md#label-priority) documentation.
Ties are broken arbitrarily.
To learn how to change label priority, see [Label priority](../labels.md#set-label-priority).
## Sorting by title

View File

@ -6,10 +6,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Labels **(FREE)**
As your count of issues, merge requests, and epics grows in GitLab, it's more and more challenging
As your count of issues, merge requests, and epics grows in GitLab, it gets more challenging
to keep track of those items. Especially as your organization grows from just a few people to
hundreds or thousands. This is where labels come in. They help you organize and tag your work
so you can track and find the work items you're interested in.
hundreds or thousands. With labels, you can organize and tag your work, and track the work items
you're interested in.
Labels are a key part of [issue boards](issue_board.md). With labels you can:
@ -19,132 +19,257 @@ Labels are a key part of [issue boards](issue_board.md). With labels you can:
- [Search lists of issues, merge requests, and epics](../search/index.md#search-issues-and-merge-requests),
as well as [issue boards](../search/index.md#issue-boards).
## Project labels and group labels
## Types of labels
There are two types of labels in GitLab:
You can use two types of labels in GitLab:
- **Project labels** can be assigned to issues and merge requests in that project only.
- **Group labels** can be assigned to issues and merge requests in any project in
the selected group or its subgroups.
- They can also be assigned to [epics](../group/epics/index.md) in the selected group or its subgroups.
- **Group labels** can be assigned to issues, merge requests, and [epics](../group/epics/index.md)
in any project in the selected group or its subgroups.
## Assign and unassign labels
> Unassigning labels with the **X** button [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216881) in GitLab 13.5.
Every issue, merge request, and epic can be assigned any number of labels. The labels are
managed in the right sidebar, where you can assign or unassign labels as needed.
You can assign labels to any issue, merge request, or epic.
To assign or unassign a label:
1. In the **Labels** section of the sidebar, click **Edit**.
1. In the **Labels** section of the sidebar, select **Edit**.
1. In the **Assign labels** list, search for labels by typing their names.
You can search repeatedly to add more labels.
The selected labels are marked with a checkmark.
1. Click the labels you want to assign or unassign.
1. Select the labels you want to assign or unassign.
1. To apply your changes to labels, select **X** next to **Assign labels** or select any area
outside the label section.
Alternatively, to unassign a label, click the **X** on the label you want to unassign.
Alternatively, to unassign a label, select the **X** on the label you want to unassign.
You can also assign a label with the `/label` [quick action](quick_actions.md),
remove labels with `/unlabel`, and reassign labels (remove all and assign new ones) with `/relabel`.
You can also assign and unassign labels with [quick actions](quick_actions.md):
## Label management
- Assign labels with `/label`.
- Remove labels with `/unlabel`.
- Remove all labels and assign new ones with `/relabel`.
Users with a [permission level](../permissions.md) of Reporter or higher are able to create
and edit labels.
## View available labels
### Project labels
### View project labels
> Showing all inherited labels [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241990) in GitLab 13.5.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241990) in GitLab 13.5: the label list in a project also shows all inherited labels.
To view a project's available labels, in the project, go to **Project information > Labels**.
Its list of labels includes both the labels defined at the project level, and
all labels defined by its ancestor groups. For each label, you can see the
project or group path from where it was created. You can filter the list by
entering a search query in the **Filter** field, and then clicking its search
icon (**{search}**).
To view the **project's labels**:
To create a new project label:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Project information > Labels**.
1. In your project, go to **Project information > Labels**.
1. Select the **New label** button.
Or:
1. View an issue or merge request.
1. On the right sidebar, in the **Labels** section, select **Edit**.
1. Select **Manage project labels**.
The list of labels includes both the labels created in the project and
all labels created in the project's ancestor groups. For each label, you can see the
project or group path where it was created.
### View group labels
To view the **group's labels**:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Group information > Labels**.
Or:
1. View an epic.
1. On the right sidebar, in the **Labels** section, select **Edit**.
1. Select **Manage group labels**.
The list includes all labels created only in the group. It does not list any labels created in
the group's projects.
## Create a label
Prerequisites:
- You must have at least the Reporter role for the project or group.
### Create a project label
To create a project label:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Project information > Labels**.
1. Select **New label**.
1. In the **Title** field, enter a short, descriptive name for the label. You
can also use this field to create [scoped, mutually exclusive labels](#scoped-labels).
1. Optional. In the **Description** field, you can enter additional
1. Optional. In the **Description** field, enter additional
information about how and when to use this label.
1. Optional. Select a background color for the label by selecting one of the
available colors, or by entering a hex color value in the **Background color**
field.
1. Optional. Select a color by selecting from the available colors, or enter a hex color value for
a specific color in the **Background color** field.
1. Select **Create label**.
You can also create a new project label from within an issue or merge request. In the
label section of the right sidebar of an issue or a merge request:
### Create a project label from an issue or merge request
1. Click **Edit**.
1. Click **Create project label**.
- Fill in the name field. Note that you can't specify a description if creating a label
this way. You can add a description later by editing the label (see below).
- Optional. Select a color by clicking on the available colors, or input a hex
color value for a specific color.
1. Click **Create**.
You can also create a new project label from an issue or merge request.
Labels you create this way belong to the same project as the issue or merge request.
To edit a label after you create it, select (**{pencil}**).
Prerequisites:
To delete a project label, select (**{ellipsis_v}**) next to the **Subscribe** button
and select **Delete** or select **Delete** when you edit a label.
- You must have at least the Reporter role for the project.
To do so:
1. View an issue or merge request.
1. On the right sidebar, in the **Labels** section, select **Edit**.
1. Select **Create project label**.
1. Fill in the name field. You can't specify a description if creating a label this way.
You can add a description later by [editing the label](#edit-a-label).
1. Optional. Select a color by selecting from the available colors, or enter a hex color value for
a specific color.
1. Select **Create**.
### Create a group label
To create a group label:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Group information > Labels**.
1. Select **New label**.
1. In the **Title** field, enter a short, descriptive name for the label. You
can also use this field to create [scoped, mutually exclusive labels](#scoped-labels).
1. Optional. In the **Description** field, enter additional
information about how and when to use this label.
1. Optional. Select a color by selecting from the available colors, or enter a hex color value for
a specific color in the **Background color** field.
1. Select **Create label**.
### Create a group label from an epic **(PREMIUM)**
You can also create a new group label from an epic.
Labels you create this way belong to the same group as the epic.
Prerequisites:
- You must have at least the Reporter role for the group.
To do so:
1. View an epic.
1. On the right sidebar, in the **Labels** section, select **Edit**.
1. Select **Create group label**.
1. Fill in the name field. You can't specify a description if creating a label this way.
You can add a description later by [editing the label](#edit-a-label).
1. Optional. Select a color by selecting from the available colors,enter input a hex color value
for a specific color.
1. Select **Create**.
## Edit a label
Prerequisites:
- You must have at least the Reporter role for the project or group.
### Edit a project label
To edit a **project** label:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Project information > Labels**.
1. Next to the label you want to edit, select **Edit** (**{pencil}**).
### Edit a group label
To edit a **group** label:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Group information > Labels**.
1. Next to the label you want to edit, select **Edit** (**{pencil}**).
## Delete a label
WARNING:
If you delete a label, it is permanently deleted. All references to the label are removed from the system and you cannot undo the deletion.
If you delete a label, it is permanently deleted. All references to the label are removed from the
system and you cannot undo the deletion.
#### Promote a project label to a group label
Prerequisites:
- You must have at least the Reporter role for the project.
### Delete a project label
To delete a **project** label:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Project information > Labels**.
1. Either:
- Next to the **Subscribe** button, select (**{ellipsis_v}**).
- Next to the label you want to edit, select **Edit** (**{pencil}**).
1. Select **Delete**.
### Delete a group label
To delete a **group** label:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Group information > Labels**.
1. Either:
- Next to the **Subscribe** button, select (**{ellipsis_v}**).
- Next to the label you want to edit, select **Edit** (**{pencil}**).
1. Select **Delete**.
## Promote a project label to a group label
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/231472) in GitLab 13.6: promoting a project label keeps that label's ID and changes it into a group label. Previously, promoting a project label created a new group label with a new ID and deleted the old label.
If you previously created a project label and now want to make it available for other
projects within the same group, you can promote it to a group label.
You might want to make a project label available for other
projects in the same group. Then, you can promote the label to a group label.
If other projects in the same group have a label with the same title, they are all
merged with the new group label. If a group label with the same title exists, it is
also merged.
WARNING:
Promoting a label is a permanent action and cannot be reversed.
Prerequisites:
- You must have at least the Reporter role for the project.
- You must have at least the Reporter role for the project's parent group.
To promote a project label to a group label:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Project information > Labels**.
1. Next to the **Subscribe** button, select the three dots (**{ellipsis_v}**) and
select **Promote to group label**.
All issues, merge requests, issue board lists, issue board filters, and label subscriptions
with the old labels are assigned to the new group label.
The new group label has the same ID as the previous project label.
WARNING:
Promoting a label is a permanent action, and cannot be reversed.
## Generate default project labels
To promote a project label to a group label:
If a project or its parent group has no labels, you can generate a default set of project
labels from the label list page.
1. Navigate to **Project information > Labels** in the project.
1. Click on the three dots (**{ellipsis_v}**) next to the **Subscribe** button and
select **Promote to group label**.
Prerequisites:
### Group labels
- You must have at least the Reporter role for the project.
- The project must have no labels present.
To view the group labels list, navigate to the group and click **Group information > Labels**.
The list includes all labels that are defined at the group level only. It does not
list any labels that are defined in projects. You can filter the list by entering
a search query at the top and clicking search (**{search}**).
To add the default labels to the project:
To create a **group label**, navigate to **Group information > Labels** in the group and
follow the same process as [creating a project label](#project-labels).
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Project information > Labels**.
1. Select **Generate a default set of labels**.
#### Create group labels from epics **(ULTIMATE)**
You can create group labels from the epic sidebar. The labels you create
belong to the immediate group to which the epic belongs. The process is the same as
creating a [project label from an issue or merge request](#project-labels).
### Generate default labels
If a project or group has no labels, you can generate a default set of project or group
labels from the label list page. The page shows a **Generate a default set of labels**
button if the list is empty. Select the button to add the following default labels
to the project:
The following labels are created:
- `bug`
- `confirmed`
@ -157,131 +282,143 @@ to the project:
## Scoped labels **(PREMIUM)**
Scoped labels allow teams to use the label feature to annotate issues, merge requests
and epics with mutually exclusive labels. This can enable more complicated workflows
by preventing certain labels from being used together.
A label is scoped when it uses a special double-colon (`::`) syntax in the label's
title, for example:
Teams can use scoped labels to annotate issues, merge requests, and epics with mutually exclusive
labels. By preventing certain labels from being used together, you can create more complex workflows.
![Scoped labels](img/labels_key_value_v13_5.png)
An issue, merge request or epic cannot have two scoped labels, of the form `key::value`,
with the same `key`. Adding a new label with the same `key`, but a different `value`
causes the previous `key` label to be replaced with the new label.
A scoped label uses a double-colon (`::`) syntax in its title, for example: `workflow::in-review`.
For example:
An issue, merge request, or epic cannot have two scoped labels, of the form `key::value`,
with the same `key`. If you add a new label with the same `key` but a different `value`,
the previous `key` label is replaced with the new label.
1. An issue is identified as being low priority, and a `priority::low` project
label is added to it.
1. After more review the issue priority is increased, and a `priority::high` label is
added.
1. GitLab automatically removes the `priority::low` label, as an issue should not
have two priority labels at the same time.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For a video overview, see [Scoped Labels Speed Run](https://www.youtube.com/watch?v=ebyCiKMFODg).
### Filter by scoped labels
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12285) in GitLab 14.4.
To filter issue, merge request, or epic lists for ones with labels that belong to a given scope, enter
To filter issue, merge request, or epic lists by a given scope, enter
`<scope>::*` in the searched label name.
For example, filtering by the `platform::*` label returns issues that have `platform::iOS`,
`platform::Android`, or `platform::Linux` labels.
NOTE:
This is not available on the [issues or merge requests dashboard pages](../search/index.md#search-issues-and-merge-requests).
Filtering by scoped labels not available on the [issues or merge requests dashboard pages](../search/index.md#search-issues-and-merge-requests).
### Workflows with scoped labels
### Scoped labels examples
Suppose you wanted a custom field in issues to track the operating system platform
that your features target, where each issue should only target one platform. You
would then create three labels `platform::iOS`, `platform::Android`, `platform::Linux`.
Applying any one of these labels on a given issue would automatically remove any other
existing label that starts with `platform::`.
**Example 1.** Updating issue priority:
The same pattern could be applied to represent the workflow states of your teams.
Suppose you have the labels `workflow::development`, `workflow::review`, and
`workflow::deployed`. If an issue already has the label `workflow::development`
applied, and a developer wanted to advance the issue to `workflow::review`, they
would simply apply that label, and the `workflow::development` label would
automatically be removed. This behavior already exists when you move issues
across label lists in an [issue board](issue_board.md#create-workflows), but
now, team members who may not be working in an issue board directly would still
be able to advance workflow states consistently in issues themselves.
1. You decide that an issue is of low priority, and assign it the `priority::low` label.
1. After more review, you realize the issue's priority is higher increased, and you assign it the
`priority::high` label.
1. Because an issue shouldn't have two priority labels at the same time, GitLab removes the
`priority::low` label.
This functionality is demonstrated in a video regarding
[using scoped labels for custom fields and workflows](https://www.youtube.com/watch?v=4BCBby6du3c).
**Example 2.** You want a custom field in issues to track the operating system platform
that your features target, where each issue should only target one platform.
### Scoped labels with nested scopes
You create three labels:
- `platform::iOS`
- `platform::Android`
- `platform::Linux`
If you assign any of these labels to an issue automatically removes any other existing label that
starts with `platform::`.
**Example 3.** You can use scoped labels to represent the workflow states of your teams.
Suppose you have the following labels:
- `workflow::development`
- `workflow::review`
- `workflow::deployed`
If an issue already has the label `workflow::development` and a developer wants to show that the
issue is now under review, they assign the `workflow::review`, and the `workflow::development` label
is removed.
The same happens when you move issues across label lists in an
[issue board](issue_board.md#create-workflows). With scoped labels, team members not working in an
issue board can also advance workflow states consistently in issues themselves.
For a video explanation, see:
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=4BCBby6du3c">Use scoped labels for custom fields and custom workflows</a>.
</div>
<figure class="video-container">
<iframe src="https://www.youtube.com/embed/4BCBby6du3c" frameborder="0" allowfullscreen="true"> </iframe>
</figure>
### Nested scopes
You can create a label with a nested scope by using multiple double colons `::` when creating
it. In this case, everything before the last `::` is the scope.
For example, `workflow::backend::review` and `workflow::backend::development` are valid
scoped labels, but they **can't** exist on the same issue at the same time, as they
both share the same scope, `workflow::backend`.
For example, if your project has these labels:
Additionally, `workflow::backend::review` and `workflow::frontend::review` are valid
scoped labels, and they **can** exist on the same issue at the same time, as they
both have different scopes, `workflow::frontend` and `workflow::backend`.
- `workflow::backend::review`
- `workflow::backend::development`
- `workflow::frontend::review`
## Subscribing to labels
An issue **can't** have both `workflow::backend::review` and `workflow::backend::development`
labels at the same time, because they both share the same scope: `workflow::backend`.
From the project label list page and the group label list page, you can click **Subscribe**
to the right of any label to enable [notifications](../profile/notifications.md) for that
label. You are notified whenever the label is assigned to an epic,
issue, or merge request.
On the other hand, an issue **can** have both `workflow::backend::review` and `workflow::frontend::review`
labels at the same time, because they both have different scopes: `workflow::frontend` and `workflow::backend`.
If you are subscribing to a group label from within a project, you can select to subscribe
to label notifications for the project only, or the whole group.
## Receive notifications when a label is used
![Labels subscriptions](img/labels_subscriptions_v13_5.png)
You can subscribe to a label to [receive notifications](../profile/notifications.md) whenever the
label is assigned to an issue, merge request, or epic.
## Label priority
To subscribe to a label:
Labels can have relative priorities, which are used in the **Label priority** and
**Priority** sort orders of issues and merge request list pages. Prioritization
for both group and project labels happens at the project level, and cannot be done
from the group label list.
1. [View the label list page.](#view-available-labels)
1. To the right of any label, select **Subscribe**.
1. Optional. If you are subscribing to a group label from a project, select either:
- **Subscribe at project level** to be notified about events in this project.
- **Subscribe at group level**: to be notified about events in the whole group.
## Set label priority
Labels can have relative priorities, which are used when you sort issue and merge request lists
by [label priority](issues/sorting_issue_lists.md#sorting-by-label-priority) and [priority](issues/sorting_issue_lists.md#sorting-by-priority).
When prioritizing labels, you must do it from a project.
It's not possible to do it from the group label list.
NOTE:
Priority sorting is based on the highest priority label only. [This discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/14523) considers changing this.
Priority sorting is based on the highest priority label only.
[This discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/14523) considers changing this.
From the project label list page, star a label to indicate that it has a priority.
Prerequisites:
- You must have at least the Reporter role for the project.
To prioritize a label:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Project information > Labels**.
1. Next to a label you want to prioritize, select the star (**{star-o}**).
![Labels prioritized](img/labels_prioritized_v13_5.png)
Drag starred labels up and down the list to change their priority, where higher in the list
means higher priority.
This label now appears at the top of the label list, under **Prioritized Labels**.
To change the relative priority of these labels, drag them up and down the list.
The labels higher in the list get higher priority.
![Drag to change label priority](img/labels_drag_priority_v12_1.gif)
On the merge request and issue list pages (for both groups and projects) you
can sort by `Label priority` or `Priority`.
If you sort by `Label priority`, GitLab uses this sort comparison order:
1. Items with a higher priority label.
1. Items without a prioritized label.
Ties are broken arbitrarily. Note that only the highest prioritized label is checked,
and labels with a lower priority are ignored. See this [related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/14523)
for more information.
![Labels sort label priority](img/labels_sort_label_priority.png)
If you sort by `Priority`, GitLab uses this sort comparison order:
1. Items with milestones that have due dates, where the soonest assigned [milestone](milestones/index.md)
is listed first.
1. Items with milestones with no due dates.
1. Items with a higher priority label.
1. Items without a prioritized label.
Ties are broken arbitrarily.
![Labels sort priority](img/labels_sort_priority.png)
To learn what happens when you sort by priority or label priority, see
[Sorting and ordering issue lists](issues/sorting_issue_lists.md).
## Troubleshooting

View File

@ -194,7 +194,7 @@ Some filters can be added multiple times. These include but are not limited to a
You can search your [To-Do List](../todos.md) by "to do" and "done".
You can filter to-do items per project, author, type, and action.
Also, you can sort them by [**Label priority**](../../user/project/labels.md#label-priority),
Also, you can sort them by [**Label priority**](../../user/project/labels.md#set-label-priority),
**Last created**, and **Oldest created**.
## Projects

View File

@ -76,9 +76,9 @@ module Backup
run_create_task(task_name)
end
write_info
write_backup_information
if ENV['SKIP'] && ENV['SKIP'].include?('tar')
if skipped?('tar')
upload
else
pack
@ -96,6 +96,7 @@ module Backup
def run_create_task(task_name)
definition = @definitions[task_name]
build_backup_information
puts_time "Dumping #{definition.task.human_name} ... ".color(:blue)
unless definition.task.enabled
@ -103,7 +104,7 @@ module Backup
return
end
if ENV["SKIP"] && ENV["SKIP"].include?(task_name)
if skipped?(task_name)
puts_time "[SKIPPED]".color(:cyan)
return
end
@ -118,10 +119,11 @@ module Backup
def restore
cleanup_required = unpack
read_backup_information
verify_backup_version
@definitions.keys.each do |task_name|
run_restore_task(task_name) unless skipped?(task_name)
run_restore_task(task_name) if !skipped?(task_name) && enabled_task?(task_name)
end
Rake::Task['gitlab:shell:setup'].invoke
@ -141,6 +143,7 @@ module Backup
def run_restore_task(task_name)
definition = @definitions[task_name]
read_backup_information
puts_time "Restoring #{definition.task.human_name} ... ".color(:blue)
unless definition.task.enabled
@ -171,7 +174,11 @@ module Backup
private
def write_info
def read_backup_information
@backup_information ||= YAML.load_file(File.join(backup_path, MANIFEST_NAME))
end
def write_backup_information
# Make sure there is a connection
ActiveRecord::Base.connection.reconnect!
@ -182,6 +189,23 @@ module Backup
end
end
def build_backup_information
@backup_information ||= {
db_version: ActiveRecord::Migrator.current_version.to_s,
backup_created_at: Time.now,
gitlab_version: Gitlab::VERSION,
tar_version: tar_version,
installation_type: Gitlab::INSTALLATION_TYPE,
skipped: ENV["SKIP"]
}
end
def backup_information
raise Backup::Error, "#{MANIFEST_NAME} not yet loaded" unless @backup_information
@backup_information
end
def pack
Dir.chdir(backup_path) do
# create archive
@ -287,15 +311,15 @@ module Backup
def verify_backup_version
Dir.chdir(backup_path) do
# restoring mismatching backups can lead to unexpected problems
if settings[:gitlab_version] != Gitlab::VERSION
if backup_information[:gitlab_version] != Gitlab::VERSION
progress.puts(<<~HEREDOC.color(:red))
GitLab version mismatch:
Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!
Please switch to the following version and try again:
version: #{settings[:gitlab_version]}
version: #{backup_information[:gitlab_version]}
HEREDOC
progress.puts
progress.puts "Hint: git checkout v#{settings[:gitlab_version]}"
progress.puts "Hint: git checkout v#{backup_information[:gitlab_version]}"
exit 1
end
end
@ -351,7 +375,7 @@ module Backup
end
def skipped?(item)
settings[:skipped] && settings[:skipped].include?(item) || !enabled_task?(item)
backup_information[:skipped] && backup_information[:skipped].include?(item)
end
def enabled_task?(task_name)
@ -411,15 +435,11 @@ module Backup
def backup_contents
[MANIFEST_NAME] + @definitions.reject do |name, definition|
skipped?(name) ||
skipped?(name) || !enabled_task?(name) ||
(definition.destination_optional && !File.exist?(File.join(backup_path, definition.destination_path)))
end.values.map(&:destination_path)
end
def settings
@settings ||= YAML.load_file(MANIFEST_NAME)
end
def tar_file
@tar_file ||= if ENV['BACKUP'].present?
File.basename(ENV['BACKUP']) + FILE_NAME_SUFFIX
@ -428,17 +448,6 @@ module Backup
end
end
def backup_information
@backup_information ||= {
db_version: ActiveRecord::Migrator.current_version.to_s,
backup_created_at: Time.now,
gitlab_version: Gitlab::VERSION,
tar_version: tar_version,
installation_type: Gitlab::INSTALLATION_TYPE,
skipped: ENV["SKIP"]
}
end
def create_attributes
attrs = {
key: remote_target,

View File

@ -8,6 +8,7 @@ module Gitlab
# Entry that represents a configuration of job artifacts.
#
class Reports < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
@ -15,10 +16,13 @@ module Gitlab
%i[junit codequality sast secret_detection dependency_scanning container_scanning
dast performance browser_performance load_performance license_scanning metrics lsif
dotenv cobertura terraform accessibility cluster_applications
requirements coverage_fuzzing api_fuzzing cluster_image_scanning].freeze
requirements coverage_fuzzing api_fuzzing cluster_image_scanning
coverage_report].freeze
attributes ALLOWED_KEYS
entry :coverage_report, Reports::CoverageReport, description: 'Coverage report configuration.'
validations do
validates :config, type: Hash
validates :config, allowed_keys: ALLOWED_KEYS
@ -47,10 +51,18 @@ module Gitlab
validates :cluster_applications, array_of_strings_or_string: true # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/333441
validates :requirements, array_of_strings_or_string: true
end
validates :config, mutually_exclusive_keys: [:coverage_report, :cobertura]
end
def value
@config.transform_values { |v| Array(v) }
@config.transform_values do |value|
if value.is_a?(Hash)
value
else
Array(value)
end
end
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
class Reports
class CoverageReport < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[coverage_format path].freeze
SUPPORTED_COVERAGE = %w[cobertura].freeze
attributes ALLOWED_KEYS
validations do
validates :config, type: Hash
validates :config, allowed_keys: ALLOWED_KEYS
with_options(presence: true) do
validates :coverage_format, inclusion: { in: SUPPORTED_COVERAGE, message: "must be one of supported formats: #{SUPPORTED_COVERAGE.join(', ')}." }
validates :path, type: String
end
end
end
end
end
end
end
end

View File

@ -39,6 +39,17 @@ module Gitlab
end
end
class MutuallyExclusiveKeysValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
mutually_exclusive_keys = value.try(:keys).to_a & options[:in]
if mutually_exclusive_keys.length > 1
record.errors.add(attribute, "please use only one the following keys: " +
mutually_exclusive_keys.join(', '))
end
end
end
class AllowedValuesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless options[:in].include?(value.to_s)

View File

@ -5,19 +5,19 @@ module LearnGitlab
include Gitlab::Utils::StrongMemoize
ACTION_ISSUE_IDS = {
issue_created: 4,
git_write: 6,
pipeline_created: 7,
merge_request_created: 9,
user_added: 8,
trial_started: 2,
required_mr_approvals_enabled: 11,
code_owners_enabled: 10
}.freeze
ACTION_DOC_URLS = {
security_scan_enabled: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports'
}.freeze
ACTION_PATHS = [
:issue_created,
:git_write,
:merge_request_created,
:user_added,
:security_scan_enabled
].freeze
def initialize(namespace)
@namespace = namespace
@ -49,7 +49,7 @@ module LearnGitlab
end
def tracked_actions
ACTION_ISSUE_IDS.keys + ACTION_DOC_URLS.keys
ACTION_ISSUE_IDS.keys + ACTION_PATHS
end
attr_reader :namespace

View File

@ -5,26 +5,4 @@ require_relative '../spec/simplecov_env'
SimpleCovEnv.configure_profile
SimpleCovEnv.configure_formatter
module SimpleCov
module ResultMerger
class << self
def resultset_files
Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json'))
end
def resultset_hashes
resultset_files.map do |path|
JSON.parse(File.read(path))
rescue StandardError
{}
end
end
def resultset
resultset_hashes.reduce({}, :merge)
end
end
end
end
SimpleCov::ResultMerger.merged_result.format!
SimpleCov.collate Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json'))

View File

@ -1602,59 +1602,6 @@ RSpec.describe ProjectsController do
end
end
describe 'GET resolve' do
shared_examples 'resolvable endpoint' do
it 'redirects to the project page' do
get :resolve, params: { id: project.id }
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(project_path(project))
end
end
context 'with an authenticated user' do
before do
sign_in(user)
end
context 'when user has access to the project' do
before do
project.add_developer(user)
end
it_behaves_like 'resolvable endpoint'
end
context 'when user has no access to the project' do
it 'gives 404 for existing project' do
get :resolve, params: { id: project.id }
expect(response).to have_gitlab_http_status(:not_found)
end
end
it 'gives 404 for non-existing project' do
get :resolve, params: { id: '0' }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'non authenticated user' do
context 'with a public project' do
let(:project) { public_project }
it_behaves_like 'resolvable endpoint'
end
it 'gives 404 for private project' do
get :resolve, params: { id: project.id }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
it 'updates Service Desk attributes' do
project.add_maintainer(user)
sign_in(user)

View File

@ -70,8 +70,9 @@ describe('~/environments/components/new_environments_app.vue', () => {
previousPage: 1,
__typename: 'LocalPageInfo',
},
location = '?scope=available&page=2',
}) => {
setWindowLocation('?scope=available&page=2');
setWindowLocation(location);
environmentAppMock.mockReturnValue(environmentsApp);
environmentFolderMock.mockReturnValue(folder);
paginationMock.mockReturnValue(pageInfo);
@ -98,6 +99,21 @@ describe('~/environments/components/new_environments_app.vue', () => {
wrapper.destroy();
});
it('should request available environments if the scope is invalid', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
location: '?scope=bad&page=2',
});
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ scope: 'available', page: 2 }),
expect.anything(),
expect.anything(),
);
});
it('should show all the folders that are fetched', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,

View File

@ -1,12 +1,14 @@
import { GlFilteredSearchToken } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import { redirectTo } from '~/lib/utils/url_utility';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import {
MEMBER_TYPES,
FILTERED_SEARCH_TOKEN_TWO_FACTOR,
FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS,
} from '~/members/constants';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
jest.mock('~/lib/utils/url_utility', () => {
@ -32,7 +34,7 @@ describe('MembersFilteredSearchBar', () => {
state: {
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
tokens: [FILTERED_SEARCH_TOKEN_TWO_FACTOR.type],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
@ -70,21 +72,7 @@ describe('MembersFilteredSearchBar', () => {
it('includes tokens set in `filteredSearchBar.tokens`', () => {
createComponent();
expect(findFilteredSearchBar().props('tokens')).toEqual([
{
type: 'two_factor',
icon: 'lock',
title: '2FA',
token: GlFilteredSearchToken,
unique: true,
operators: OPERATOR_IS_ONLY,
options: [
{ value: 'enabled', title: 'Enabled' },
{ value: 'disabled', title: 'Disabled' },
],
requiredPermissions: 'canManageMembers',
},
]);
expect(findFilteredSearchBar().props('tokens')).toEqual([FILTERED_SEARCH_TOKEN_TWO_FACTOR]);
});
describe('when `canManageMembers` is false', () => {
@ -93,7 +81,10 @@ describe('MembersFilteredSearchBar', () => {
state: {
filteredSearchBar: {
show: true,
tokens: ['two_factor', 'with_inherited_permissions'],
tokens: [
FILTERED_SEARCH_TOKEN_TWO_FACTOR.type,
FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS.type,
],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
@ -105,18 +96,7 @@ describe('MembersFilteredSearchBar', () => {
});
expect(findFilteredSearchBar().props('tokens')).toEqual([
{
type: 'with_inherited_permissions',
icon: 'group',
title: 'Membership',
token: GlFilteredSearchToken,
unique: true,
operators: OPERATOR_IS_ONLY,
options: [
{ value: 'exclude', title: 'Direct' },
{ value: 'only', title: 'Inherited' },
],
},
FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS,
]);
});
});
@ -134,7 +114,7 @@ describe('MembersFilteredSearchBar', () => {
expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([
{
type: 'two_factor',
type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type,
value: {
data: 'enabled',
operator: '=',
@ -183,7 +163,7 @@ describe('MembersFilteredSearchBar', () => {
createComponent();
findFilteredSearchBar().vm.$emit('onFilter', [
{ type: 'two_factor', value: { data: 'enabled', operator: '=' } },
{ type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } },
]);
expect(redirectTo).toHaveBeenCalledWith('https://localhost/?two_factor=enabled');
@ -193,7 +173,7 @@ describe('MembersFilteredSearchBar', () => {
createComponent();
findFilteredSearchBar().vm.$emit('onFilter', [
{ type: 'two_factor', value: { data: 'enabled', operator: '=' } },
{ type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } },
{ type: 'filtered-search-term', value: { data: 'foobar' } },
]);
@ -206,7 +186,7 @@ describe('MembersFilteredSearchBar', () => {
createComponent();
findFilteredSearchBar().vm.$emit('onFilter', [
{ type: 'two_factor', value: { data: 'enabled', operator: '=' } },
{ type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } },
{ type: 'filtered-search-term', value: { data: 'foo bar baz' } },
]);
@ -221,7 +201,7 @@ describe('MembersFilteredSearchBar', () => {
createComponent();
findFilteredSearchBar().vm.$emit('onFilter', [
{ type: 'two_factor', value: { data: 'enabled', operator: '=' } },
{ type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } },
{ type: 'filtered-search-term', value: { data: 'foobar' } },
]);

View File

@ -137,9 +137,7 @@ exports[`Learn GitLab renders correctly 1`] = `
class="gl-link"
data-testid="uncompleted-learn-gitlab-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/"
target="_self"
>
@ -157,9 +155,7 @@ exports[`Learn GitLab renders correctly 1`] = `
class="gl-link"
data-testid="uncompleted-learn-gitlab-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/"
target="_self"
>
@ -177,9 +173,7 @@ exports[`Learn GitLab renders correctly 1`] = `
class="gl-link"
data-testid="uncompleted-learn-gitlab-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/"
target="_self"
>
@ -204,9 +198,7 @@ exports[`Learn GitLab renders correctly 1`] = `
class="gl-link"
data-testid="uncompleted-learn-gitlab-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/"
target="_self"
>
@ -267,9 +259,7 @@ exports[`Learn GitLab renders correctly 1`] = `
class="gl-link"
data-testid="uncompleted-learn-gitlab-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/"
target="_self"
>
@ -287,9 +277,7 @@ exports[`Learn GitLab renders correctly 1`] = `
class="gl-link"
data-testid="uncompleted-learn-gitlab-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/"
target="_self"
>
@ -343,9 +331,7 @@ exports[`Learn GitLab renders correctly 1`] = `
class="gl-link"
data-testid="uncompleted-learn-gitlab-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="https://docs.gitlab.com/ee/foobar/"
rel="noopener noreferrer"
target="_blank"

View File

@ -12,8 +12,9 @@ const defaultProps = {
completed: false,
};
const docLinkProps = {
const openInNewTabProps = {
url: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/',
openInNewTab: true,
};
describe('Learn GitLab Section Link', () => {
@ -59,9 +60,9 @@ describe('Learn GitLab Section Link', () => {
expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(true);
});
describe('doc links', () => {
describe('links marked with openInNewTab', () => {
beforeEach(() => {
createWrapper('securityScanEnabled', docLinkProps);
createWrapper('securityScanEnabled', openInNewTabProps);
});
it('renders links with blank target', () => {
@ -78,7 +79,6 @@ describe('Learn GitLab Section Link', () => {
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
label: 'Run a Security scan using CI/CD',
property: 'Growth::Conversion::Experiment::LearnGitLab',
});
unmockTracking();

View File

@ -38,6 +38,7 @@ export const testActions = {
url: 'https://docs.gitlab.com/ee/foobar/',
completed: false,
svg: 'http://example.com/images/illustration.svg',
openInNewTab: true,
},
issueCreated: {
url: 'http://example.com/',

View File

@ -239,16 +239,16 @@ RSpec.describe Resolvers::BaseResolver do
it 'increases complexity based on arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: described_class, null: false, max_page_size: 1)
expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3
expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7
expect(field.complexity.call({}, { sort: 'foo' }, 1)).to eq 3
expect(field.complexity.call({}, { search: 'foo' }, 1)).to eq 7
end
it 'does not increase complexity when filtering by iids' do
field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6
expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3
expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
expect(field.complexity.call({}, { sort: 'foo' }, 1)).to eq 6
expect(field.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3
expect(field.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3
end
end

View File

@ -106,9 +106,9 @@ RSpec.describe ResolvesPipelines do
it 'increases field complexity based on arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, null: false, max_page_size: 1)
expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 2
expect(field.to_graphql.complexity.call({}, { sha: 'foo' }, 1)).to eq 4
expect(field.to_graphql.complexity.call({}, { sha: 'ref' }, 1)).to eq 4
expect(field.complexity.call({}, {}, 1)).to eq 2
expect(field.complexity.call({}, { sha: 'foo' }, 1)).to eq 4
expect(field.complexity.call({}, { sha: 'ref' }, 1)).to eq 4
end
def resolve_pipelines(args = {}, context = { current_user: current_user })

View File

@ -618,8 +618,8 @@ RSpec.describe Resolvers::IssuesResolver do
it 'increases field complexity based on arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4
expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8
expect(field.complexity.call({}, {}, 1)).to eq 4
expect(field.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8
end
def create_issue_with_severity(project, severity:)

View File

@ -147,8 +147,8 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
it 'has an high complexity regardless of arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: described_class, null: false, max_page_size: 100)
expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24
expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24
expect(field.complexity.call({}, {}, 1)).to eq 24
expect(field.complexity.call({}, { include_subgroups: true }, 1)).to eq 24
end
def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil, ids: nil }, context = { current_user: current_user })

View File

@ -36,8 +36,8 @@ RSpec.describe Resolvers::ProjectResolver do
field1 = Types::BaseField.new(name: 'test', type: GraphQL::Types::String, resolver_class: described_class, null: false, max_page_size: 100)
field2 = Types::BaseField.new(name: 'test', type: GraphQL::Types::String, resolver_class: described_class, null: false, max_page_size: 1)
expect(field1.to_graphql.complexity.call({}, {}, 1)).to eq 2
expect(field2.to_graphql.complexity.call({}, {}, 1)).to eq 2
expect(field1.complexity.call({}, {}, 1)).to eq 2
expect(field2.complexity.call({}, {}, 1)).to eq 2
end
def resolve_project(full_path)

View File

@ -136,7 +136,7 @@ RSpec.describe Types::BaseEnum do
value 'TEST_VALUE', **args
end
enum.to_graphql.values['TEST_VALUE']
enum.values['TEST_VALUE']
end
end
end

View File

@ -19,7 +19,7 @@ RSpec.describe Types::BaseField do
it 'defaults to 1' do
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true)
expect(field.to_graphql.complexity).to eq 1
expect(field.complexity).to eq 1
end
describe '#base_complexity' do
@ -43,7 +43,7 @@ RSpec.describe Types::BaseField do
it 'has specified value' do
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12)
expect(field.to_graphql.complexity).to eq 12
expect(field.complexity).to eq 12
end
context 'when field has a resolver' do
@ -51,7 +51,7 @@ RSpec.describe Types::BaseField do
let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: resolver, complexity: 2, max_page_size: 100, null: true) }
it 'uses this complexity' do
expect(field.to_graphql.complexity).to eq 2
expect(field.complexity).to eq 2
end
end
@ -59,13 +59,13 @@ RSpec.describe Types::BaseField do
let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: resolver, max_page_size: 100, null: true) }
it 'sets complexity depending on arguments for resolvers' do
expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4
expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3
expect(field.complexity.call({}, {}, 2)).to eq 4
expect(field.complexity.call({}, { first: 50 }, 2)).to eq 3
end
it 'sets complexity depending on number load limits for resolvers' do
expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2
expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
expect(field.complexity.call({}, { first: 1 }, 2)).to eq 2
expect(field.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4
end
end
@ -73,8 +73,8 @@ RSpec.describe Types::BaseField do
it 'sets complexity as normal' do
field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, max_page_size: 100, null: true)
expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2
expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2
expect(field.complexity.call({}, {}, 2)).to eq 2
expect(field.complexity.call({}, { first: 50 }, 2)).to eq 2
end
end
end
@ -84,9 +84,9 @@ RSpec.describe Types::BaseField do
it 'adds 1 if true' do
with_gitaly_field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, null: true, calls_gitaly: true)
without_gitaly_field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, null: true)
base_result = without_gitaly_field.to_graphql.complexity.call({}, {}, 2)
base_result = without_gitaly_field.complexity.call({}, {}, 2)
expect(with_gitaly_field.to_graphql.complexity.call({}, {}, 2)).to eq base_result + 1
expect(with_gitaly_field.complexity.call({}, {}, 2)).to eq base_result + 1
end
end
@ -94,7 +94,7 @@ RSpec.describe Types::BaseField do
it 'adds 1 if true' do
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, calls_gitaly: true)
expect(field.to_graphql.complexity).to eq 2
expect(field.complexity).to eq 2
end
end
@ -108,14 +108,14 @@ RSpec.describe Types::BaseField do
it 'has complexity set to that constant' do
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12)
expect(field.to_graphql.complexity).to eq 12
expect(field.complexity).to eq 12
end
it 'does not raise an error even with Gitaly calls' do
allow(Gitlab::GitalyClient).to receive(:get_request_count).and_return([0, 1])
field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12)
expect(field.to_graphql.complexity).to eq 12
expect(field.complexity).to eq 12
end
end
end

View File

@ -11,7 +11,7 @@ RSpec.describe Types::GlobalIDType do
let(:gid) { project.to_global_id }
it 'is has the correct name' do
expect(described_class.to_graphql.name).to eq('GlobalID')
expect(described_class.graphql_name).to eq('GlobalID')
end
describe '.coerce_result' do
@ -63,7 +63,7 @@ RSpec.describe Types::GlobalIDType do
let(:type) { ::Types::GlobalIDType[::Project] }
it 'is has the correct name' do
expect(type.to_graphql.name).to eq('ProjectID')
expect(type.graphql_name).to eq('ProjectID')
end
context 'the GID is appropriate' do
@ -126,7 +126,7 @@ RSpec.describe Types::GlobalIDType do
let(:deprecating_gid) { Gitlab::GlobalId.build(model_name: 'Issue', id: issue.id) }
it 'appends the description with a deprecation notice for the old Global ID' do
expect(type.to_graphql.description).to include('The older format `"gid://gitlab/OldIssue/1"` was deprecated in 10.0')
expect(type.description).to include('The older format `"gid://gitlab/OldIssue/1"` was deprecated in 10.0')
end
describe 'coercing input against the type (parsing the Global ID string when supplied as an argument)' do
@ -242,7 +242,7 @@ RSpec.describe Types::GlobalIDType do
let(:type) { ::Types::GlobalIDType[::Ci::Build] }
it 'is has a valid GraphQL identifier for a name' do
expect(type.to_graphql.name).to eq('CiBuildID')
expect(type.graphql_name).to eq('CiBuildID')
end
end

View File

@ -97,29 +97,29 @@ RSpec.describe LearnGitlabHelper do
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})
),
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})
),
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})
),
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})
url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z})
)
})
end
@ -137,58 +137,5 @@ RSpec.describe LearnGitlabHelper do
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
it 'calls experiment with expected context & options' do
allow(helper).to receive(:current_user).and_return(user)
expect(helper).to receive(:experiment).with(
:change_continuous_onboarding_link_urls,
namespace: namespace,
actor: user,
sticky_to: namespace
)
learn_gitlab_data
end
end
end
end

View File

@ -71,7 +71,7 @@ RSpec.describe Backup::Manager do
end
before do
allow(YAML).to receive(:load_file).with('backup_information.yml')
allow(YAML).to receive(:load_file).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
.and_return(backup_information)
end
@ -171,7 +171,8 @@ RSpec.describe Backup::Manager do
before do
allow(ActiveRecord::Base.connection).to receive(:reconnect!)
allow(Kernel).to receive(:system).and_return(true)
allow(YAML).to receive(:load_file).and_return(backup_information)
allow(YAML).to receive(:load_file).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
.and_return(backup_information)
allow(subject).to receive(:backup_information).and_return(backup_information)
allow(task1).to receive(:dump).with(File.join(Gitlab.config.backup.path, 'task1.tar.gz'))
@ -571,7 +572,8 @@ RSpec.describe Backup::Manager do
allow(task1).to receive(:restore).with(File.join(Gitlab.config.backup.path, 'task1.tar.gz'))
allow(task2).to receive(:restore).with(File.join(Gitlab.config.backup.path, 'task2.tar.gz'))
allow(YAML).to receive(:load_file).and_return(backup_information)
allow(YAML).to receive(:load_file).with(File.join(Gitlab.config.backup.path, 'backup_information.yml'))
.and_return(backup_information)
allow(Rake::Task['gitlab:shell:setup']).to receive(:invoke)
allow(Rake::Task['cache:clear']).to receive(:invoke)
end

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Reports::CoverageReport do
let(:entry) { described_class.new(config) }
describe 'validations' do
context 'when it is valid' do
let(:config) { { coverage_format: 'cobertura', path: 'cobertura-coverage.xml' } }
it { expect(entry).to be_valid }
it { expect(entry.value).to eq(config) }
end
context 'with unsupported coverage format' do
let(:config) { { coverage_format: 'jacoco', path: 'jacoco.xml' } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /format must be one of supported formats/ }
end
context 'without coverage format' do
let(:config) { { path: 'cobertura-coverage.xml' } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /format can't be blank/ }
end
context 'without path' do
let(:config) { { coverage_format: 'cobertura' } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /path can't be blank/ }
end
context 'with invalid path' do
let(:config) { { coverage_format: 'cobertura', path: 123 } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /path should be a string/ }
end
context 'with unknown keys' do
let(:config) { { coverage_format: 'cobertura', path: 'cobertura-coverage.xml', foo: :bar } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /contains unknown keys/ }
end
end
end

View File

@ -6,12 +6,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do
let(:entry) { described_class.new(config) }
describe 'validates ALLOWED_KEYS' do
let(:artifact_file_types) { Ci::JobArtifact.file_types }
described_class::ALLOWED_KEYS.each do |keyword, _|
it "expects #{keyword} to be an artifact file_type" do
expect(artifact_file_types).to include(keyword)
end
it "expects ALLOWED_KEYS to be an artifact file_type or coverage_report" do
expect(Ci::JobArtifact.file_types.keys.map(&:to_sym) + [:coverage_report]).to include(*described_class::ALLOWED_KEYS)
end
end
@ -68,6 +64,45 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do
it_behaves_like 'a valid entry', params[:keyword], params[:file]
end
end
context 'when coverage_report is specified' do
let(:coverage_format) { :cobertura }
let(:filename) { 'cobertura-coverage.xml' }
let(:coverage_report) { { path: filename, coverage_format: coverage_format } }
let(:config) { { coverage_report: coverage_report } }
it 'is valid' do
expect(entry).to be_valid
end
it 'returns artifacts configuration' do
expect(entry.value).to eq(config)
end
context 'and another report is specified' do
let(:config) { { coverage_report: coverage_report, dast: 'gl-dast-report.json' } }
it 'is valid' do
expect(entry).to be_valid
end
it 'returns artifacts configuration' do
expect(entry.value).to eq({ coverage_report: coverage_report, dast: ['gl-dast-report.json'] })
end
end
context 'and a direct coverage report format is specified' do
let(:config) { { coverage_report: coverage_report, cobertura: 'cobertura-coverage.xml' } }
it 'is not valid' do
expect(entry).not_to be_valid
end
it 'reports error' do
expect(entry.errors).to include /please use only one the following keys: coverage_report, cobertura/
end
end
end
end
context 'when entry value is not correct' do

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Config::Entry::Validators do
let(:klass) do
Class.new do
include ActiveModel::Validations
include Gitlab::Config::Entry::Validators
end
end
let(:instance) { klass.new }
describe described_class::MutuallyExclusiveKeysValidator do
using RSpec::Parameterized::TableSyntax
before do
klass.instance_eval do
validates :config, mutually_exclusive_keys: [:foo, :bar]
end
allow(instance).to receive(:config).and_return(config)
end
where(:context, :config, :valid_result) do
'with mutually exclusive keys' | { foo: 1, bar: 2 } | false
'without mutually exclusive keys' | { foo: 1 } | true
'without mutually exclusive keys' | { bar: 1 } | true
'with other keys' | { foo: 1, baz: 2 } | true
end
with_them do
it 'validates the instance' do
expect(instance.valid?).to be(valid_result)
unless valid_result
expect(instance.errors.messages_for(:config)).to include /please use only one the following keys: foo, bar/
end
end
end
end
end

View File

@ -11,7 +11,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
expect(field.name).to eq('testHtml')
expect(field.description).to eq('The GitLab Flavored Markdown rendering of `hello`')
expect(field.type).to eq(GraphQL::Types::String)
expect(field.to_graphql.complexity).to eq(5)
expect(field.complexity).to eq(5)
end
context 'developer warnings' do
@ -43,7 +43,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
let(:field) { type_class.fields['noteHtml'] }
it 'renders markdown from the same property as the field name without the `_html` suffix' do
expect(field.to_graphql.resolve(type_instance, {}, context)).to eq(expected_markdown)
expect(field.resolve(type_instance, {}, context)).to eq(expected_markdown)
end
context 'when a `method` argument is passed' do
@ -51,7 +51,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
let(:field) { type_class.fields['testHtml'] }
it 'renders markdown from a specific property' do
expect(field.to_graphql.resolve(type_instance, {}, context)).to eq(expected_markdown)
expect(field.resolve(type_instance, {}, context)).to eq(expected_markdown)
end
end
@ -62,21 +62,21 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
let(:note) { build(:note, note: "Referencing #{issue.to_reference(full: true)}") }
it 'renders markdown correctly' do
expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue))
expect(field.resolve(type_instance, {}, context)).to include(issue_path(issue))
end
context 'when the issue is not publicly accessible' do
let_it_be(:project) { create(:project, :private) }
it 'hides the references from users that are not allowed to see the reference' do
expect(field.to_graphql.resolve(type_instance, {}, context)).not_to include(issue_path(issue))
expect(field.resolve(type_instance, {}, context)).not_to include(issue_path(issue))
end
it 'shows the reference to users that are allowed to see it' do
context = GraphQL::Query::Context.new(query: query, values: { current_user: project.first_owner }, object: nil)
type_instance = type_class.authorized_new(note, context)
expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue))
expect(field.resolve(type_instance, {}, context)).to include(issue_path(issue))
end
end
end

View File

@ -17,7 +17,7 @@ RSpec.describe Gitlab::Graphql::MountMutation do
f.mount_mutation(mutation)
end
mutation_type.get_field('testMutation').to_graphql
mutation_type.get_field('testMutation')
end
it 'mounts a mutation' do
@ -31,7 +31,7 @@ RSpec.describe Gitlab::Graphql::MountMutation do
f.mount_aliased_mutation('MyAlias', mutation)
end
mutation_type.get_field('myAlias').to_graphql
mutation_type.get_field('myAlias')
end
it 'mounts a mutation' do
@ -43,11 +43,11 @@ RSpec.describe Gitlab::Graphql::MountMutation do
end
it 'has a correct type' do
expect(field.type.name).to eq('MyAliasPayload')
expect(field.type.to_type_signature).to eq('MyAliasPayload')
end
it 'has a correct input argument' do
expect(field.arguments['input'].type.unwrap.name).to eq('MyAliasInput')
expect(field.arguments['input'].type.unwrap.to_type_signature).to eq('MyAliasInput')
end
end

View File

@ -9,7 +9,7 @@ RSpec.describe LearnGitlab::Onboarding do
let(:namespace) { build(:namespace) }
let_it_be(:tracked_action_columns) do
tracked_actions = described_class::ACTION_ISSUE_IDS.keys + described_class::ACTION_DOC_URLS.keys
tracked_actions = described_class::ACTION_ISSUE_IDS.keys + described_class::ACTION_PATHS
tracked_actions.map { |key| OnboardingProgress.column_name(key) }
end

View File

@ -8026,14 +8026,6 @@ RSpec.describe Project, factory_default: :keep do
let(:object) { build(:project) }
it_behaves_like 'blocks unsafe serialization'
context 'when feature flag block_project_serialization is disabled' do
before do
stub_feature_flags(block_project_serialization: false)
end
it_behaves_like 'allows unsafe serialization'
end
end
private

View File

@ -44,6 +44,31 @@ RSpec.describe 'getting an issue list for a group' do
end
end
context 'when there are archived projects' do
let_it_be(:archived_project) { create(:project, :archived, group: group1) }
let_it_be(:archived_issue) { create(:issue, project: archived_project) }
before_all do
group1.add_developer(current_user)
end
it 'excludes issues from archived projects by default' do
post_graphql(query, current_user: current_user)
expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid)
end
context 'when include_archived is true' do
let(:issue_filter_params) { { include_archived: true } }
it 'includes issues from archived projects' do
post_graphql(query, current_user: current_user)
expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid, archived_issue.to_global_id.to_s)
end
end
end
context 'when there is a confidential issue' do
let_it_be(:confidential_issue1) { create(:issue, :confidential, project: project1) }
let_it_be(:confidential_issue2) { create(:issue, :confidential, project: project2) }

View File

@ -16,6 +16,9 @@ RSpec.describe 'Query.group.mergeRequests' do
let_it_be(:project_x) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project_x]) }
let_it_be(:archived_project) { create(:project, :archived, :repository, group: group) }
let_it_be(:archived_mr) { create(:merge_request, source_project: archived_project) }
let_it_be(:mr_attrs) do
{ target_branch: 'master' }
end
@ -119,4 +122,22 @@ RSpec.describe 'Query.group.mergeRequests' do
expect(mrs_data).to match_array(expected_mrs(mrs_a + mrs_b + mrs_c))
end
end
describe 'passing include_archived: true' do
let(:query) do
<<~GQL
query($path: ID!) {
group(fullPath: $path) {
mergeRequests(includeArchived: true) { nodes { id } }
}
}
GQL
end
it 'can find all merge requests in the group, including from archived projects' do
post_graphql(query, current_user: user, variables: { path: group.full_path })
expect(mrs_data).to match_array(expected_mrs(mrs_a + mrs_b + [archived_mr]))
end
end
end

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe "Projects::RedirectController requests" do
using RSpec::Parameterized::TableSyntax
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:public_project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
before_all do
private_project.add_developer(user)
end
describe 'GET redirect_from_id' do
where(:authenticated, :project, :is_found) do
true | ref(:private_project) | true
false | ref(:private_project) | false
true | ref(:public_project) | true
false | ref(:public_project) | true
true | build(:project, id: 0) | false
end
with_them do
before do
sign_in(user) if authenticated
get "/projects/#{project.id}"
end
if params[:is_found]
it 'redirects to the project page' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(project_path(project))
end
else
it 'gives 404' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
# This is a regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/351058
context 'with sourcegraph enabled' do
let_it_be(:sourcegraph_url) { 'https://sourcegraph.test' }
before do
allow(Gitlab::CurrentSettings).to receive(:sourcegraph_url).and_return(sourcegraph_url)
allow(Gitlab::CurrentSettings).to receive(:sourcegraph_enabled).and_return(true)
sign_in(user)
end
context 'with projects/:id route' do
subject { get "/projects/#{public_project.id}" }
it 'redirects successfully' do
subject
expect(response).to redirect_to(project_path(public_project))
end
end
end
end

View File

@ -70,9 +70,11 @@ RSpec.describe 'project routing' do
route_to('projects#preview_markdown', namespace_id: 'gitlab', id: 'gitlabhq')
)
end
end
it 'to #resolve' do
expect(get('/projects/1')).to route_to('projects#resolve', id: '1')
describe Projects::RedirectController, 'routing' do
it 'to #redirect_from_id' do
expect(get('/projects/1')).to route_to('projects/redirect#redirect_from_id', id: '1')
end
end

View File

@ -0,0 +1,67 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.first_owner }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
let(:service) { described_class.new(project, user, { ref: ref }) }
let(:pipeline) { service.execute(source).payload }
describe 'artifacts:' do
before do
stub_ci_pipeline_yaml_file(config)
allow_next_instance_of(Ci::BuildScheduleWorker) do |instance|
allow(instance).to receive(:perform).and_return(true)
end
end
describe 'reports:' do
context 'with valid config' do
let(:config) do
<<~YAML
test-job:
script: "echo 'hello world' > cobertura.xml"
artifacts:
reports:
coverage_report:
coverage_format: 'cobertura'
path: 'cobertura.xml'
dependency-scanning-job:
script: "echo 'hello world' > gl-dependency-scanning-report.json"
artifacts:
reports:
dependency_scanning: 'gl-dependency-scanning-report.json'
YAML
end
it 'creates pipeline with builds' do
expect(pipeline).to be_persisted
expect(pipeline).not_to have_yaml_errors
expect(pipeline.builds.pluck(:name)).to contain_exactly('test-job', 'dependency-scanning-job')
end
end
context 'with invalid config' do
let(:config) do
<<~YAML
test-job:
script: "echo 'hello world' > cobertura.xml"
artifacts:
reports:
foo: 'bar'
YAML
end
it 'creates pipeline with yaml errors' do
expect(pipeline).to be_persisted
expect(pipeline).to have_yaml_errors
end
end
end
end
end

View File

@ -1072,6 +1072,7 @@ RSpec.describe NotificationService, :mailer do
end
before do
project.reload
add_user_subscriptions(issue)
reset_delivered_emails!
update_custom_notification(:new_issue, @u_guest_custom, resource: project)