Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cb7f766437
commit
7f08e6916d
|
@ -1 +1 @@
|
|||
d0d5fa790767c12eeadb40a1ecfbc00fde2a4768
|
||||
4ef97df05e54269d90fdbd4d2f59fcc29b1afcdf
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -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
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -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)
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -439,6 +439,12 @@
|
|||
.na {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
// Rouge `Comment` token (quoted text in email body)
|
||||
.c {
|
||||
color: $gl-grayish-blue;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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?
|
|
@ -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.
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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/)
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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' } },
|
||||
]);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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/',
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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:)
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue