Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
37b8d1f7b5
commit
e8360356fb
4
Gemfile
4
Gemfile
|
|
@ -141,11 +141,11 @@ gem 'grape-path-helpers', '~> 2.0.0', feature_category: :api
|
|||
gem 'rack-cors', '~> 2.0.1', require: 'rack/cors' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
# GraphQL API
|
||||
gem 'graphql', '~> 2.0.27', feature_category: :api
|
||||
gem 'graphql', '~> 2.2.5', feature_category: :api
|
||||
gem 'graphql-docs', '~> 4.0.0', group: [:development, :test], feature_category: :api
|
||||
gem 'graphiql-rails', '~> 1.8.0', feature_category: :api
|
||||
gem 'apollo_upload_server', '~> 2.1.5', feature_category: :api
|
||||
gem 'graphlient', '~> 0.5.0', feature_category: :importers # Used by BulkImport feature (group::import)
|
||||
gem 'graphlient', '~> 0.6.0', feature_category: :importers # Used by BulkImport feature (group::import)
|
||||
|
||||
# Generate Fake data
|
||||
gem 'ffaker', '~> 2.10' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
|
|
|||
|
|
@ -275,10 +275,10 @@
|
|||
{"name":"grape-swagger-entity","version":"0.5.1","platform":"ruby","checksum":"f51e372d00ac96cf90d948f87b3f4eb287ab053976ca57ad503d442ad8605523"},
|
||||
{"name":"grape_logging","version":"1.8.4","platform":"ruby","checksum":"efcc3e322dbd5d620a68f078733b7db043cf12680144cd03c982f14115c792d1"},
|
||||
{"name":"graphiql-rails","version":"1.8.0","platform":"ruby","checksum":"02e2c5098be2c6c29219a0e9b2910a2cd3c494301587a3199a7c4484d8038ed1"},
|
||||
{"name":"graphlient","version":"0.5.0","platform":"ruby","checksum":"0f2c9416142e50b6bd4edcd86fe6810f792951732c487f9061aee6d420e0f292"},
|
||||
{"name":"graphlient","version":"0.6.0","platform":"ruby","checksum":"b8d8664b4c8ec215012cbe3cca918a045b0a206d709712d68b6db51fd215c5c0"},
|
||||
{"name":"graphlyte","version":"1.0.0","platform":"ruby","checksum":"b5af4ab67dde6e961f00ea1c18f159f73b52ed11395bb4ece297fe628fa1804d"},
|
||||
{"name":"graphql","version":"2.0.27","platform":"ruby","checksum":"1f59be5a770248595971a261c96edef3adcf323e93387e53d1ca1ffd16448b36"},
|
||||
{"name":"graphql-client","version":"0.18.0","platform":"ruby","checksum":"98aadc810f23dce5404621903945aa584279574f87855b4301d69c90ddc6250b"},
|
||||
{"name":"graphql","version":"2.2.5","platform":"ruby","checksum":"15eeb4b4b29b8502de22e6f2794ea5b7bf75b3a8c0aa5d776f5e614ef543bc7e"},
|
||||
{"name":"graphql-client","version":"0.19.0","platform":"ruby","checksum":"fe699d81976f916bd8f989216155326449cb8475a5d69fa1dd054012a86969c7"},
|
||||
{"name":"graphql-docs","version":"4.0.0","platform":"ruby","checksum":"f68296959263db26e1b7ba7058856d67b641cf508187222268be58f09dfa02d7"},
|
||||
{"name":"grpc","version":"1.60.0","platform":"aarch64-linux","checksum":"f8b29900bf9a8f18ac362da4057983ad7fe3774bec3f308ac3f3006669c670f9"},
|
||||
{"name":"grpc","version":"1.60.0","platform":"arm64-darwin","checksum":"57e4477f85fd98822b9421a5c702c642ff8a8cc1624ec4325604867017c67ec3"},
|
||||
|
|
|
|||
11
Gemfile.lock
11
Gemfile.lock
|
|
@ -870,13 +870,14 @@ GEM
|
|||
graphiql-rails (1.8.0)
|
||||
railties
|
||||
sprockets-rails
|
||||
graphlient (0.5.0)
|
||||
graphlient (0.6.0)
|
||||
faraday (>= 1.0)
|
||||
faraday_middleware
|
||||
graphql-client
|
||||
graphlyte (1.0.0)
|
||||
graphql (2.0.27)
|
||||
graphql-client (0.18.0)
|
||||
graphql (2.2.5)
|
||||
racc (~> 1.4)
|
||||
graphql-client (0.19.0)
|
||||
activesupport (>= 3.0)
|
||||
graphql
|
||||
graphql-docs (4.0.0)
|
||||
|
|
@ -1959,9 +1960,9 @@ DEPENDENCIES
|
|||
grape-swagger-entity (~> 0.5.1)
|
||||
grape_logging (~> 1.8)
|
||||
graphiql-rails (~> 1.8.0)
|
||||
graphlient (~> 0.5.0)
|
||||
graphlient (~> 0.6.0)
|
||||
graphlyte (~> 1.0.0)
|
||||
graphql (~> 2.0.27)
|
||||
graphql (~> 2.2.5)
|
||||
graphql-docs (~> 4.0.0)
|
||||
grpc (~> 1.60.0)
|
||||
gssapi (~> 1.3.1)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ export default () => {
|
|||
basePath,
|
||||
groupFullPath,
|
||||
groupIssuesPath,
|
||||
groupOrganizationsPath,
|
||||
canAdminCrmContact,
|
||||
canReadCrmOrganization,
|
||||
groupId,
|
||||
textQuery,
|
||||
} = el.dataset;
|
||||
|
|
@ -44,7 +46,9 @@ export default () => {
|
|||
provide: {
|
||||
groupFullPath,
|
||||
groupIssuesPath,
|
||||
groupOrganizationsPath,
|
||||
canAdminCrmContact: parseBoolean(canAdminCrmContact),
|
||||
canReadCrmOrganization: parseBoolean(canReadCrmOrganization),
|
||||
groupId,
|
||||
textQuery,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,7 +22,14 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['canAdminCrmContact', 'groupFullPath', 'groupIssuesPath', 'textQuery'],
|
||||
inject: [
|
||||
'canAdminCrmContact',
|
||||
'canReadCrmOrganization',
|
||||
'groupFullPath',
|
||||
'groupIssuesPath',
|
||||
'groupOrganizationsPath',
|
||||
'textQuery',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
contacts: { list: [] },
|
||||
|
|
@ -147,6 +154,7 @@ export default {
|
|||
title: s__('Crm|Customer relations contacts'),
|
||||
newContact: s__('Crm|New contact'),
|
||||
errorMsg: __('Something went wrong. Please try again.'),
|
||||
organizations: s__('Crm|Organizations'),
|
||||
},
|
||||
EDIT_ROUTE_NAME,
|
||||
NEW_ROUTE_NAME,
|
||||
|
|
@ -191,11 +199,20 @@ export default {
|
|||
@error-alert-dismissed="errorAlertDismissed"
|
||||
>
|
||||
<template #header-actions>
|
||||
<router-link v-if="canAdminCrmContact" :to="{ name: $options.NEW_ROUTE_NAME }">
|
||||
<gl-button class="gl-my-3 gl-mr-5" variant="confirm" data-testid="new-contact-button">
|
||||
{{ $options.i18n.newContact }}
|
||||
</gl-button>
|
||||
</router-link>
|
||||
<div class="gl-display-flex gl-align-items-center gl-justify-content-end gl-my-3 gl-mr-5">
|
||||
<a
|
||||
v-if="canReadCrmOrganization"
|
||||
:href="groupOrganizationsPath"
|
||||
class="gl-mr-3"
|
||||
data-testid="organizations-link"
|
||||
>{{ $options.i18n.organizations }}</a
|
||||
>
|
||||
<router-link v-if="canAdminCrmContact" :to="{ name: $options.NEW_ROUTE_NAME }">
|
||||
<gl-button variant="confirm" data-testid="new-contact-button">
|
||||
{{ $options.i18n.newContact }}
|
||||
</gl-button>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ export default () => {
|
|||
const {
|
||||
basePath,
|
||||
canAdminCrmOrganization,
|
||||
canReadCrmContact,
|
||||
groupContactsPath,
|
||||
groupFullPath,
|
||||
groupId,
|
||||
groupIssuesPath,
|
||||
|
|
@ -43,6 +45,8 @@ export default () => {
|
|||
apolloProvider,
|
||||
provide: {
|
||||
canAdminCrmOrganization: parseBoolean(canAdminCrmOrganization),
|
||||
canReadCrmContact: parseBoolean(canReadCrmContact),
|
||||
groupContactsPath,
|
||||
groupFullPath,
|
||||
groupId,
|
||||
groupIssuesPath,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,14 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['canAdminCrmOrganization', 'groupFullPath', 'groupIssuesPath', 'textQuery'],
|
||||
inject: [
|
||||
'canAdminCrmOrganization',
|
||||
'canReadCrmContact',
|
||||
'groupContactsPath',
|
||||
'groupFullPath',
|
||||
'groupIssuesPath',
|
||||
'textQuery',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
organizations: { list: [] },
|
||||
|
|
@ -138,6 +145,7 @@ export default {
|
|||
title: s__('Crm|Customer relations organizations'),
|
||||
newOrganization: s__('Crm|New organization'),
|
||||
errorMsg: __('Something went wrong. Please try again.'),
|
||||
contacts: s__('Crm|Contacts'),
|
||||
},
|
||||
EDIT_ROUTE_NAME,
|
||||
NEW_ROUTE_NAME,
|
||||
|
|
@ -182,15 +190,20 @@ export default {
|
|||
@error-alert-dismissed="errorAlertDismissed"
|
||||
>
|
||||
<template #header-actions>
|
||||
<router-link v-if="canAdminCrmOrganization" :to="{ name: $options.NEW_ROUTE_NAME }">
|
||||
<gl-button
|
||||
class="gl-my-3 gl-mr-5"
|
||||
variant="confirm"
|
||||
data-testid="new-organization-button"
|
||||
<div class="gl-display-flex gl-align-items-center gl-justify-content-end gl-my-3 gl-mr-5">
|
||||
<a
|
||||
v-if="canReadCrmContact"
|
||||
:href="groupContactsPath"
|
||||
class="gl-mr-3"
|
||||
data-testid="contacts-link"
|
||||
>{{ $options.i18n.contacts }}</a
|
||||
>
|
||||
{{ $options.i18n.newOrganization }}
|
||||
</gl-button>
|
||||
</router-link>
|
||||
<router-link v-if="canAdminCrmOrganization" :to="{ name: $options.NEW_ROUTE_NAME }">
|
||||
<gl-button variant="confirm" data-testid="new-organization-button">
|
||||
{{ $options.i18n.newOrganization }}
|
||||
</gl-button>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@
|
|||
"WorkItemWidgetStartAndDueDate",
|
||||
"WorkItemWidgetStatus",
|
||||
"WorkItemWidgetTestReports",
|
||||
"WorkItemWidgetTimeTracking",
|
||||
"WorkItemWidgetWeight"
|
||||
],
|
||||
"WorkItemWidgetDefinition": [
|
||||
|
|
|
|||
|
|
@ -79,7 +79,11 @@ class GitlabSchema < GraphQL::Schema
|
|||
def resolve_type(type, object, ctx = :__undefined__)
|
||||
return if type.respond_to?(:assignable?) && !type.assignable?(object)
|
||||
|
||||
super
|
||||
if type.kind.object?
|
||||
type
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Find an object by looking it up from its 'GlobalID'.
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@ module Types
|
|||
null: true,
|
||||
description: 'Indicates if a group has email notifications disabled.'
|
||||
|
||||
field :emails_enabled,
|
||||
type: GraphQL::Types::Boolean,
|
||||
null: true,
|
||||
description: 'Indicates if a group has email notifications enabled.'
|
||||
|
||||
field :max_access_level, Types::AccessLevelType,
|
||||
null: false,
|
||||
description: 'The maximum access level of the current user in the group.'
|
||||
|
|
@ -365,6 +370,10 @@ module Types
|
|||
end
|
||||
end
|
||||
|
||||
def emails_disabled
|
||||
!group.emails_enabled?
|
||||
end
|
||||
|
||||
def projects_count
|
||||
BatchLoader::GraphQL.for(object.id).batch do |group_ids, loader|
|
||||
projects_counts = Group.id_in(group_ids).projects_counts
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ module Types
|
|||
::Types::WorkItems::Widgets::CurrentUserTodosType,
|
||||
::Types::WorkItems::Widgets::AwardEmojiType,
|
||||
::Types::WorkItems::Widgets::LinkedItemsType,
|
||||
::Types::WorkItems::Widgets::ParticipantsType
|
||||
::Types::WorkItems::Widgets::ParticipantsType,
|
||||
::Types::WorkItems::Widgets::TimeTrackingType
|
||||
].freeze
|
||||
|
||||
def self.ce_orphan_types
|
||||
|
|
@ -33,6 +34,8 @@ module Types
|
|||
# Whenever a new widget is added make sure to update the spec to avoid N + 1 queries in
|
||||
# spec/requests/api/graphql/project/work_items_spec.rb and add the necessary preloads
|
||||
# in app/graphql/resolvers/work_items_resolver.rb
|
||||
#
|
||||
# rubocop:disable Metrics/CyclomaticComplexity -- we'll have a lot of widgets to handle in the WidgetInterface
|
||||
def self.resolve_type(object, context)
|
||||
case object
|
||||
when ::WorkItems::Widgets::Description
|
||||
|
|
@ -59,10 +62,13 @@ module Types
|
|||
::Types::WorkItems::Widgets::LinkedItemsType
|
||||
when ::WorkItems::Widgets::Participants
|
||||
::Types::WorkItems::Widgets::ParticipantsType
|
||||
when ::WorkItems::Widgets::TimeTracking
|
||||
::Types::WorkItems::Widgets::TimeTrackingType
|
||||
else
|
||||
raise "Unknown GraphQL type for widget #{object}"
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
|
||||
orphan_types(*ORPHAN_TYPES)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module WorkItems
|
||||
module Widgets
|
||||
# rubocop:disable Graphql/AuthorizeTypes -- we already authorize the work item itself
|
||||
class TimeTrackingType < BaseObject
|
||||
graphql_name 'WorkItemWidgetTimeTracking'
|
||||
description 'Represents a time tracking widget'
|
||||
|
||||
implements Types::WorkItems::WidgetInterface
|
||||
|
||||
field :time_estimate, GraphQL::Types::Int,
|
||||
null: false,
|
||||
description: 'Time estimate of the work item.'
|
||||
field :total_time_spent, GraphQL::Types::Int,
|
||||
null: false,
|
||||
description: 'Total time (in seconds) reported as spent on the work item.'
|
||||
|
||||
field :timelogs, Types::TimelogType.connection_type,
|
||||
null: false,
|
||||
description: 'Timelogs on the work item.'
|
||||
end
|
||||
# rubocop:enable Graphql/AuthorizeTypes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -123,6 +123,6 @@ module TimeTrackable
|
|||
end
|
||||
|
||||
def category_id(category)
|
||||
TimeTracking::TimelogCategory.find_by_name(project.root_namespace, category).first&.id
|
||||
TimeTracking::TimelogCategory.find_by_name(project&.root_namespace, category).first&.id
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -174,6 +174,10 @@ class WorkItem < Issue
|
|||
linked_work_items(authorize: false).size
|
||||
end
|
||||
|
||||
def supports_time_tracking?
|
||||
work_item_type.supports_time_tracking?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
override :parent_link_confidentiality
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ module WorkItems
|
|||
widgets.include? ::WorkItems::Widgets::Assignees
|
||||
end
|
||||
|
||||
def supports_time_tracking?
|
||||
widgets.include?(::WorkItems::Widgets::TimeTracking)
|
||||
end
|
||||
|
||||
def default_issue?
|
||||
name == WorkItems::Type::TYPE_NAMES[:issue]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ module WorkItems
|
|||
linked_items: 17,
|
||||
color: 18, # EE-only
|
||||
rolledup_dates: 19, # EE-only
|
||||
participants: 20
|
||||
participants: 20,
|
||||
time_tracking: 21
|
||||
}
|
||||
|
||||
def self.available_widgets
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
module Widgets
|
||||
class TimeTracking < Base
|
||||
delegate :time_estimate, :total_time_spent, :timelogs, to: :work_item
|
||||
|
||||
def self.quick_action_commands
|
||||
[
|
||||
# time estimation quick actions
|
||||
:estimate, :estimate_time,
|
||||
# remove time estimation quick actions
|
||||
:remove_estimate, :remove_time_estimate,
|
||||
# add spent time quick actions
|
||||
:spend, :spent, :spend_time,
|
||||
# remove time spent quick actions
|
||||
:remove_time_spent
|
||||
]
|
||||
end
|
||||
|
||||
def self.quick_action_params
|
||||
[:time_estimate, :spend_time]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -196,6 +196,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
|
|||
enable :admin_milestone
|
||||
enable :admin_issue_board_list
|
||||
enable :admin_issue
|
||||
enable :update_issue
|
||||
enable :read_metrics_dashboard_annotation
|
||||
enable :read_prometheus
|
||||
enable :read_package
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
module Callbacks
|
||||
class TimeTracking < Base
|
||||
def after_initialize
|
||||
if excluded_in_new_type?
|
||||
params.delete(:time_estimate)
|
||||
params.delete(:spend_time)
|
||||
end
|
||||
|
||||
return unless has_permission?(:admin_work_item)
|
||||
return if !params.present? || (!params.key?(:time_estimate) && !params.key?(:spend_time))
|
||||
|
||||
work_item.time_estimate = params[:time_estimate] if params[:time_estimate].present?
|
||||
work_item.spend_time = params[:spend_time] if params[:spend_time].present?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +1,17 @@
|
|||
- breadcrumb_title _('Customer relations contacts')
|
||||
- page_title _('Customer relations contacts')
|
||||
- @content_wrapper_class = "gl-relative"
|
||||
- add_page_specific_style 'page_bundles/incident_management_list'
|
||||
|
||||
= content_for :after_content do
|
||||
#js-crm-form-portal
|
||||
|
||||
#js-crm-contacts-app{ data: { group_full_path: @group.full_path, group_issues_path: issues_group_path(@group), group_id: @group.id, can_admin_crm_contact: can?(current_user, :admin_crm_contact, @group).to_s, base_path: group_crm_contacts_path(@group), text_query: params[:search] } }
|
||||
#js-crm-contacts-app{ data: {
|
||||
group_full_path: @group.full_path,
|
||||
group_issues_path: issues_group_path(@group),
|
||||
group_id: @group.id,
|
||||
group_organizations_path: group_crm_organizations_path(@group),
|
||||
can_admin_crm_contact: can?(current_user, :admin_crm_contact, @group).to_s,
|
||||
can_read_crm_organization: can?(current_user, :read_crm_organization, @group).to_s,
|
||||
base_path: group_crm_contacts_path(@group),
|
||||
text_query: params[:search] } }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
- breadcrumb_title _('Customer relations organizations')
|
||||
- page_title _('Customer relations organizations')
|
||||
- @content_wrapper_class = "gl-relative"
|
||||
- add_page_specific_style 'page_bundles/incident_management_list'
|
||||
|
||||
= content_for :after_content do
|
||||
#js-crm-form-portal
|
||||
|
||||
#js-crm-organizations-app{ data: { base_path: group_crm_organizations_path(@group), can_admin_crm_organization: can?(current_user, :admin_crm_organization, @group).to_s, group_full_path: @group.full_path, group_id: @group.id, group_issues_path: issues_group_path(@group), text_query: params[:search] } }
|
||||
#js-crm-organizations-app{ data: {
|
||||
base_path: group_crm_organizations_path(@group),
|
||||
can_admin_crm_organization: can?(current_user, :admin_crm_organization, @group).to_s,
|
||||
can_read_crm_contact: can?(current_user, :read_crm_contact, @group).to_s,
|
||||
group_contacts_path: group_crm_contacts_path(@group),
|
||||
group_full_path: @group.full_path,
|
||||
group_id: @group.id,
|
||||
group_issues_path: issues_group_path(@group),
|
||||
text_query: params[:search] } }
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@ classes:
|
|||
- Security::Training
|
||||
feature_categories:
|
||||
- vulnerability_management
|
||||
description: Stores information about the primary security training provider for a given project
|
||||
description: Stores information about the primary security training provider for a
|
||||
given project
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78195
|
||||
milestone: '14.7'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -4,7 +4,23 @@ classes:
|
|||
- Terraform::StateVersion
|
||||
feature_categories:
|
||||
- infrastructure_as_code
|
||||
description: Represents a Terraform state file at a point in time, with a corresponding file stored in object storage
|
||||
description: Represents a Terraform state file at a point in time, with a corresponding
|
||||
file stored in object storage
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35211
|
||||
milestone: '13.4'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: terraform_state_id
|
||||
table: terraform_states
|
||||
sharding_key: project_id
|
||||
belongs_to: terraform_state
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: Stores projects which users select to appear in their Security Dashboard
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18708
|
||||
milestone: '12.5'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@ classes:
|
|||
- Vulnerability
|
||||
feature_categories:
|
||||
- vulnerability_management
|
||||
description: Stores information about vulnerabilites present in the project's source code
|
||||
description: Stores information about vulnerabilites present in the project's source
|
||||
code
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16181
|
||||
milestone: '12.4'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@ classes:
|
|||
- Vulnerabilities::Feedback
|
||||
feature_categories:
|
||||
- vulnerability_management
|
||||
description: Stores information about the confirm, dismiss, or create issue to investigate actions taken on vulnerabilities
|
||||
description: Stores information about the confirm, dismiss, or create issue to investigate
|
||||
actions taken on vulnerabilities
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5452
|
||||
milestone: '10.8'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@ classes:
|
|||
- Vulnerabilities::HistoricalStatistic
|
||||
feature_categories:
|
||||
- vulnerability_management
|
||||
description: Stores aggregate vulnerability statistics which are used in the Security Dashboard
|
||||
description: Stores aggregate vulnerability statistics which are used in the Security
|
||||
Dashboard
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36955
|
||||
milestone: '13.3'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -4,7 +4,16 @@ classes:
|
|||
- Vulnerabilities::Identifier
|
||||
feature_categories:
|
||||
- vulnerability_management
|
||||
description: Stores identifiers (like CVE or CWE) for vulnerabilities that have been found
|
||||
description: Stores identifiers (like CVE or CWE) for vulnerabilities that have been
|
||||
found
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6896
|
||||
milestone: '11.4'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: Stores information about findings for a given vulnerability
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6896
|
||||
milestone: '11.4'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: Denormalized version of the vulnerabilites table used for faster reads
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74733
|
||||
milestone: '14.6'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -8,4 +8,12 @@ feature_categories:
|
|||
description: Stores remediation information, such as diffs, for a given vulnerability
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47166
|
||||
milestone: '13.7'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: Stores information about the vulnerability scanners used by projects
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6896
|
||||
milestone: '11.4'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: Stores pre-calculated vulnerability statistics for projects
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34289
|
||||
milestone: '13.2'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTimeTrackingWidgetDefinitionToWorkItemTypes < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.9'
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
disable_ddl_transaction!
|
||||
|
||||
WIDGET_NAME = 'Time Tracking'
|
||||
WIDGET_ENUM_VALUE = 21
|
||||
|
||||
# WorkItemTypes that would support Time tracking are provided in: https://gitlab.com/groups/gitlab-org/-/epics/12396
|
||||
WORK_ITEM_TYPES = [
|
||||
"Issue",
|
||||
"Task",
|
||||
"Epic",
|
||||
"Requirement",
|
||||
"Test Case",
|
||||
"Ticket",
|
||||
"Incident"
|
||||
].freeze
|
||||
|
||||
class WorkItemType < MigrationRecord
|
||||
self.table_name = 'work_item_types'
|
||||
self.inheritance_column = :_type_disabled
|
||||
end
|
||||
|
||||
def up
|
||||
widgets = []
|
||||
|
||||
WORK_ITEM_TYPES.each do |type_name|
|
||||
type = WorkItemType.find_by_name_and_namespace_id(type_name, nil)
|
||||
|
||||
unless type
|
||||
Gitlab::AppLogger.warn("type #{type_name} is missing, not adding widget")
|
||||
|
||||
next
|
||||
end
|
||||
|
||||
widgets << {
|
||||
work_item_type_id: type.id,
|
||||
name: WIDGET_NAME,
|
||||
widget_type: WIDGET_ENUM_VALUE
|
||||
}
|
||||
end
|
||||
|
||||
return if widgets.empty?
|
||||
|
||||
work_item_widget_definitions.upsert_all(
|
||||
widgets,
|
||||
unique_by: :index_work_item_widget_definitions_on_default_witype_and_name
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
work_item_widget_definitions.where(name: WIDGET_NAME).delete_all
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def work_item_widget_definitions
|
||||
define_batchable_model('work_item_widget_definitions')
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
a1df0ace59e3eecb27d4911c7169b59d2ad3407736b66a9c4c485bf9c793e026
|
||||
|
|
@ -45,7 +45,7 @@ compliance:
|
|||
|:------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------------------------------------------------------------------------------------------|
|
||||
| [Compliance frameworks](../user/group/compliance_frameworks.md) | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Describe the type of compliance requirements projects must follow. |
|
||||
| [Compliance pipelines](../user/group/compliance_frameworks.md#compliance-pipelines) | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Define a pipeline configuration to run for any projects with a given compliance framework. |
|
||||
| [Scan result policy approval settings](../user/application_security/policies/scan-result-policies.md#approval_settings) | **{dotted-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Enforce a scan result policy enforcing multiple approvers and override various project settings in all enforced groups or projects across your GitLab instance or group. |
|
||||
| [Merge request approval policy approval settings](../user/application_security/policies/scan-result-policies.md#approval_settings) | **{dotted-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Enforce a merge request approval policy enforcing multiple approvers and override various project settings in all enforced groups or projects across your GitLab instance or group. |
|
||||
|
||||
## Audit management
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ The following metrics are available:
|
|||
| `gitlab_ci_queue_retrieval_duration_seconds` | Histogram | 16.3 | Time it takes to execute a SQL query to retrieve builds queue |
|
||||
| `gitlab_connection_pool_size` | Gauge | 16.7 | Size of connection pool |
|
||||
| `gitlab_connection_pool_available_count` | Gauge | 16.7 | Number of available connections in the pool |
|
||||
| `gitlab_security_policies_scan_result_process_duration_seconds` | Histogram | 16.7 | The amount of time to process scan result policies |
|
||||
| `gitlab_security_policies_scan_result_process_duration_seconds` | Histogram | 16.7 | The amount of time to process merge request approval policies |
|
||||
| `gitlab_highlight_usage` | Counter | 16.8 | The number of times `Gitlab::Highlight` is used | `used_on` |
|
||||
| `dependency_linker_usage` | Counter | 16.8 | The number of times dependency linker is used | `used_on` |
|
||||
|
||||
|
|
|
|||
|
|
@ -19482,6 +19482,7 @@ GPG signature for a signed commit.
|
|||
| <a id="groupdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `description`. |
|
||||
| <a id="groupdora"></a>`dora` | [`Dora`](#dora) | Group's DORA metrics. |
|
||||
| <a id="groupemailsdisabled"></a>`emailsDisabled` | [`Boolean`](#boolean) | Indicates if a group has email notifications disabled. |
|
||||
| <a id="groupemailsenabled"></a>`emailsEnabled` | [`Boolean`](#boolean) | Indicates if a group has email notifications enabled. |
|
||||
| <a id="groupenforcefreeusercap"></a>`enforceFreeUserCap` | [`Boolean`](#boolean) | Indicates whether the group has limited users for a free plan. |
|
||||
| <a id="groupepicboards"></a>`epicBoards` | [`EpicBoardConnection`](#epicboardconnection) | Find epic boards. (see [Connections](#connections)) |
|
||||
| <a id="groupepicsenabled"></a>`epicsEnabled` | [`Boolean`](#boolean) | Indicates if Epics are enabled for namespace. |
|
||||
|
|
@ -29507,6 +29508,19 @@ Represents a test reports widget.
|
|||
| <a id="workitemwidgettestreportstestreports"></a>`testReports` | [`TestReportConnection`](#testreportconnection) | Test reports of the work item. (see [Connections](#connections)) |
|
||||
| <a id="workitemwidgettestreportstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
|
||||
|
||||
### `WorkItemWidgetTimeTracking`
|
||||
|
||||
Represents a time tracking widget.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemwidgettimetrackingtimeestimate"></a>`timeEstimate` | [`Int!`](#int) | Time estimate of the work item. |
|
||||
| <a id="workitemwidgettimetrackingtimelogs"></a>`timelogs` | [`TimelogConnection!`](#timelogconnection) | Timelogs on the work item. (see [Connections](#connections)) |
|
||||
| <a id="workitemwidgettimetrackingtotaltimespent"></a>`totalTimeSpent` | [`Int!`](#int) | Total time (in seconds) reported as spent on the work item. |
|
||||
| <a id="workitemwidgettimetrackingtype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
|
||||
|
||||
### `WorkItemWidgetWeight`
|
||||
|
||||
Represents a weight widget.
|
||||
|
|
@ -32440,6 +32454,7 @@ Type of a work item widget.
|
|||
| <a id="workitemwidgettypestart_and_due_date"></a>`START_AND_DUE_DATE` | Start And Due Date widget. |
|
||||
| <a id="workitemwidgettypestatus"></a>`STATUS` | Status widget. |
|
||||
| <a id="workitemwidgettypetest_reports"></a>`TEST_REPORTS` | Test Reports widget. |
|
||||
| <a id="workitemwidgettypetime_tracking"></a>`TIME_TRACKING` | Time Tracking widget. |
|
||||
| <a id="workitemwidgettypeweight"></a>`WEIGHT` | Weight widget. |
|
||||
|
||||
## Scalar types
|
||||
|
|
@ -34134,6 +34149,7 @@ Implementations:
|
|||
- [`WorkItemWidgetStartAndDueDate`](#workitemwidgetstartandduedate)
|
||||
- [`WorkItemWidgetStatus`](#workitemwidgetstatus)
|
||||
- [`WorkItemWidgetTestReports`](#workitemwidgettestreports)
|
||||
- [`WorkItemWidgetTimeTracking`](#workitemwidgettimetracking)
|
||||
- [`WorkItemWidgetWeight`](#workitemwidgetweight)
|
||||
|
||||
##### Fields
|
||||
|
|
|
|||
|
|
@ -555,7 +555,7 @@ listed in the descriptions of the relevant settings.
|
|||
| `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-Administrator users for groups, projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is `null` which means there is no restriction.[Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131203) in GitLab 16.4: cannot select levels that are set as `default_project_visibility` and `default_group_visibility`. |
|
||||
| `rsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `0` (no restriction). `-1` disables RSA keys. |
|
||||
| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes. |
|
||||
| `security_policy_global_group_approvers_enabled` | boolean | no | Whether to look up scan result policy approval groups globally or within project hierarchies. |
|
||||
| `security_policy_global_group_approvers_enabled` | boolean | no | Whether to look up merge request approval policy approval groups globally or within project hierarchies. |
|
||||
| `security_txt_content` | string | no | [Public security contact information](../administration/settings/security_contact_information.md). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433210) in GitLab 16.7. |
|
||||
| `service_access_tokens_expiration_enforced` | boolean | no | Flag to indicate if token expiry date can be optional for service account users |
|
||||
| `shared_runners_enabled` | boolean | no | (**If enabled, requires:** `shared_runners_text` and `shared_runners_minutes`) Enable shared runners for new projects. |
|
||||
|
|
|
|||
|
|
@ -360,7 +360,7 @@ After the initial rollout of Organizations, the following functionality will be
|
|||
1. Security policies at the Organization level.
|
||||
1. Vulnerability Report and Dependency List at the Organization level.
|
||||
1. Cascading Organization setting to enforce security scans.
|
||||
1. Scan result policies at the Organization level.
|
||||
1. Merge request approval policies at the Organization level.
|
||||
1. Compliance frameworks.
|
||||
1. [Support the agent for Kubernetes sharing at the Organization level](https://gitlab.com/gitlab-org/gitlab/-/issues/382731).
|
||||
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ The current architecture of synchronising policies from YAML to approval rules i
|
|||
### Goals
|
||||
|
||||
1. Reduce the calls to Gitaly and depend on reading from the database. The YAML data will be mirrored on a database table and a read-only ActiveRecord model allows us to build features without performance concerns.
|
||||
1. Remove duplicated columns related to scan result policies in approval_project_rules and approval_merge_request_rules
|
||||
1. Remove duplicated columns related to merge request approval policies in approval_project_rules and approval_merge_request_rules
|
||||
1. Change the current process in [UpdateOrchestrationPolicyConfiguration](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/workers/concerns/update_orchestration_policy_configuration.rb) not to delete and recreate all related records, but rather update/recreate only the affected records
|
||||
1. Reduce the average propagation duration of scan result policy changes
|
||||
1. Reduce the average propagation duration of merge request approval policy changes
|
||||
1. Reduce the number of queries to the database performed by workers from Security Policies
|
||||
|
||||
## Proposal
|
||||
|
|
@ -47,7 +47,7 @@ The current architecture of synchronising policies from YAML to approval rules i
|
|||
|
||||
Security policy project that are linked to a group or a project has a corresponding entry in `security_orchestration_policy_configurations` table. In order to read/query policies, the YAML has to be read from policy project which involves a RPC call to Gitaly. This does not perform well when there are huge number of projects/groups configured with security policies.
|
||||
|
||||
Scan result policy that are stored in the policy project as YAML file gets synchronised to approval rules through `approval_project_rules` table. Currently we are storing these fields related to scan result policy in the table:
|
||||
Merge request approval policy that are stored in the policy project as YAML file gets synchronised to approval rules through `approval_project_rules` table. Currently we are storing these fields related to merge request approval policy in the table:
|
||||
|
||||
- `scanners`
|
||||
- `vulnerabilities_allowed`
|
||||
|
|
@ -73,7 +73,7 @@ This worker is called whenever these events happen:
|
|||
- Protected branch is added/removed from a project
|
||||
- Policy is created/updated/deleted for a project
|
||||
|
||||
To avoid this, we have created the `scan_result_policies` table (`Security::ScanResultPolicyRead` model) which acts as a read model for scan result policies to avoid reading from policy project. But currently, we don't store all the required fields in the table, we only store `role_approvers` , `license_state` and `match_on_inclusion`.
|
||||
To avoid this, we have created the `scan_result_policies` table (`Security::ScanResultPolicyRead` model) which acts as a read model for merge request approval policies to avoid reading from policy project. But currently, we don't store all the required fields in the table, we only store `role_approvers` , `license_state` and `match_on_inclusion`.
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
|
|
|
|||
|
|
@ -280,8 +280,8 @@ See the [test engineering process](https://handbook.gitlab.com/handbook/engineer
|
|||
|
||||
1. You have confirmed that if this MR contains changes to processing or storing of credentials or tokens, authorization, and authentication methods, or other items described in [the security review guidelines](https://handbook.gitlab.com/handbook/security/product-security/application-security/appsec-reviews/#what-should-be-reviewed), you have added the `~security` label and you have `@`-mentioned `@gitlab-com/gl-security/appsec`.
|
||||
1. You have reviewed the documentation regarding [internal application security reviews](https://handbook.gitlab.com/handbook/security/product-security/application-security/appsec-reviews/#internal-application-security-reviews) for **when** and **how** to request a security review and requested a security review if this is warranted for this change.
|
||||
1. If there are security scan results that are blocking the MR (due to the [scan result policies](https://gitlab.com/gitlab-com/gl-security/security-policies)):
|
||||
- For true positive findings, they should be corrected before the merge request is merged. This will remove the AppSec approval required by the scan result policy.
|
||||
1. If there are security scan results that are blocking the MR (due to the [merge request approval policies](https://gitlab.com/gitlab-com/gl-security/security-policies)):
|
||||
- For true positive findings, they should be corrected before the merge request is merged. This will remove the AppSec approval required by the merge request approval policy.
|
||||
- For false positive findings, something that should be discussed for risk acceptance, or anything questionable, ping `@gitlab-com/gl-security/appsec`.
|
||||
|
||||
##### Deployment
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@ group: Security Policies
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Tutorial: Set up a scan result policy
|
||||
# Tutorial: Set up a merge request approval policy
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Ultimate
|
||||
**Offering:** SaaS, self-managed
|
||||
|
||||
This tutorial shows you how to create and configure a [scan result policy](../../user/application_security/policies/scan-result-policies.md). These policies can be set to take action based on scan results.
|
||||
This tutorial shows you how to create and configure a [merge request approval policy](../../user/application_security/policies/scan-result-policies.md). These policies can be set to take action based on scan results.
|
||||
For example, in this tutorial, you'll set up a policy that requires approval from two specified users if a vulnerability is detected in a merge request.
|
||||
|
||||
To set up a scan result policy:
|
||||
To set up a merge request approval policy:
|
||||
|
||||
1. [Create a test project](#create-a-test-project).
|
||||
1. [Add a scan result policy](#add-a-scan-result-policy).
|
||||
1. [Test the scan result policy](#test-the-scan-result-policy).
|
||||
1. [Add a merge request approval policy](#add-a-merge-request-approval-policy).
|
||||
1. [Test the merge request approval policy](#test-the-merge-request-approval-policy).
|
||||
|
||||
## Before you begin
|
||||
|
||||
|
|
@ -36,14 +36,14 @@ The namespace used for this tutorial must:
|
|||
1. Select **Create project**.
|
||||
1. Go to the newly created project and create [protected branches](../../user/project/protected_branches.md).
|
||||
|
||||
## Add a scan result policy
|
||||
## Add a merge request approval policy
|
||||
|
||||
Next, you'll add a scan result policy to your test project:
|
||||
Next, you'll add a merge request approval policy to your test project:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find the `sast-scan-result-policy` project.
|
||||
1. Select **Secure > Policies**.
|
||||
1. Select **New policy**.
|
||||
1. In **Scan result policy**, select **Select policy**.
|
||||
1. In **Merge request approval policy**, select **Select policy**.
|
||||
1. Complete the fields.
|
||||
- **Name**: `sast-scan-result-policy`
|
||||
- **Policy status**: **Enabled**
|
||||
|
|
@ -70,9 +70,9 @@ Next, you'll add a scan result policy to your test project:
|
|||
|
||||
You can see the list of policies added in the previous steps.
|
||||
|
||||
## Test the scan result policy
|
||||
## Test the merge request approval policy
|
||||
|
||||
Nice work, you've created a scan result policy. To test it, create some vulnerabilities and check the result:
|
||||
Nice work, you've created a merge request approval policy. To test it, create some vulnerabilities and check the result:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find the `sast-scan-result-policy` project.
|
||||
1. Select **Code > Repository**.
|
||||
|
|
@ -123,7 +123,7 @@ Nice work, you've created a scan result policy. To test it, create some vulnerab
|
|||
Wait for the pipeline to complete. This could be a few minutes.
|
||||
|
||||
The merge request security widget confirms that security scanning detected one potential
|
||||
vulnerability. As defined in the scan result policy, the merge request is blocked and waiting for
|
||||
vulnerability. As defined in the merge request approval policy, the merge request is blocked and waiting for
|
||||
approval.
|
||||
|
||||
You now know how to set up and use scan result policies to catch vulnerabilities!
|
||||
You now know how to set up and use merge request approval policies to catch vulnerabilities!
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ GitLab can check your application for security vulnerabilities and that it meets
|
|||
| [Set up dependency scanning](dependency_scanning.md) | Learn how to detect vulnerabilities in an application's dependencies. | **{star}** |
|
||||
| [Export Dependency List in SBOM format](export_sbom.md) | Learn how to export an application's dependencies to the CycloneDX SBOM format. | **{star}** |
|
||||
| [Create a compliance pipeline](compliance_pipeline/index.md) | Learn how to create compliance pipelines for your groups. | **{star}** |
|
||||
| [Set up a scan result policy](scan_result_policy/index.md) | Learn how to configure a scan result policy that takes action based on scan results. | **{star}** |
|
||||
| [Set up a merge request approval policy](scan_result_policy/index.md) | Learn how to configure a merge request approval policy that takes action based on scan results. | **{star}** |
|
||||
| [Set up a scan execution policy](scan_execution_policy/index.md) | Learn how to create a scan execution policy to enforce security scanning of your project. | **{star}** |
|
||||
| [Scan a Docker container for vulnerabilities](container_scanning/index.md) | Learn how to use container scanning templates to add container scanning to your projects. | **{star}** |
|
||||
| [Get started with GitLab application security](../user/application_security/get-started-security.md) | Follow recommended steps to set up security tools. | |
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ After you've gotten familiar with how scanning works, you can then choose to:
|
|||
- These scheduled jobs run independently from any other security scans you may have defined in a compliance framework pipeline or in the project's `.gitlab-ci.yml` file.
|
||||
- Running regular dependency and [container scans](container_scanning/index.md) surface newly-discovered vulnerabilities that already exist in your repository.
|
||||
- Scheduled scans are most useful for projects or important branches with low development activity where pipeline scans are infrequent.
|
||||
1. Create a [scan result policy](policies/index.md) to limit new vulnerabilities from being merged
|
||||
1. Create a [merge request approval policy](policies/index.md) to limit new vulnerabilities from being merged
|
||||
into your [default branch](../project/repository/branches/default.md).
|
||||
1. Enable other scan types such as [SAST](sast/index.md), [DAST](dast/index.md),
|
||||
[Fuzz testing](coverage_fuzzing/index.md), or [Container Scanning](container_scanning/index.md).
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ For more details, see [extension page](https://marketplace.visualstudio.com/item
|
|||
You can enforce an additional approval for merge requests that would introduce one of the following
|
||||
security issues:
|
||||
|
||||
- A security vulnerability. For more details, read [Scan result policies](policies/scan-result-policies.md).
|
||||
- A security vulnerability. For more details, read [Merge request approval policies](policies/scan-result-policies.md).
|
||||
|
||||
## Using private Maven repositories
|
||||
|
||||
|
|
|
|||
|
|
@ -317,11 +317,11 @@ The workaround is to amend your group or instance push rules to allow branches f
|
|||
Security Policies are designed to require approval when there are no results (no security report),
|
||||
as this ensures that no vulnerabilities are introduced. We cannot know if there are any
|
||||
vulnerabilities unless the scans enforced by the policy complete successfully and are evaluated.
|
||||
- For scan result policies, we require artifacts for each scanner defined in the policy for both the
|
||||
source and target branch. To ensure scan result policies capture the necessary results, confirm
|
||||
- For merge request approval policies, we require artifacts for each scanner defined in the policy for both the
|
||||
source and target branch. To ensure merge request approval policies capture the necessary results, confirm
|
||||
your scan execution is properly implemented and enforced. If using scan execution policies,
|
||||
enforcing on `all branches` often addresses this need.
|
||||
- Comparison in scan result policies depends on a successful and completed merge base pipeline. If the merge base pipeline is [skipped](../../../ci/pipelines/index.md#skip-a-pipeline), merge requests with the merge base pipeline are blocked.
|
||||
- Comparison in merge request approval policies depends on a successful and completed merge base pipeline. If the merge base pipeline is [skipped](../../../ci/pipelines/index.md#skip-a-pipeline), merge requests with the merge base pipeline are blocked.
|
||||
- When running scan execution policies based on a SAST action, ensure target repositories contain
|
||||
proper code files. SAST runs different analyzers
|
||||
[based on the types of files in the repository](../sast/index.md#supported-languages-and-frameworks),
|
||||
|
|
@ -333,7 +333,7 @@ The workaround is to amend your group or instance push rules to allow branches f
|
|||
is not applied for the latter. You can define policies to enforce rules generically on `default`
|
||||
branches regardless of the name used in the project or on `all protected branches` to address this
|
||||
issue.
|
||||
- Scan result policies created at the group or subgroup level can take some time to apply to all
|
||||
- Merge request approval policies created at the group or subgroup level can take some time to apply to all
|
||||
the merge requests in the group.
|
||||
- Scheduled scan execution policies run with a minimum 15 minute cadence. Learn more [about the schedule rule type](../policies/scan-execution-policies.md#schedule-rule-type).
|
||||
- When scheduling pipelines, keep in mind that CRON scheduling is based on UTC on GitLab SaaS and is
|
||||
|
|
@ -348,7 +348,7 @@ The workaround is to amend your group or instance push rules to allow branches f
|
|||
- When creating a Scan Result Policy, neither the array `severity_levels` nor the array
|
||||
`vulnerability_states` in the [`scan_finding` rule](../policies/scan-result-policies.md#scan_finding-rule-type)
|
||||
can be left empty. For a working rule, at least one entry must exist.
|
||||
- When configuring pipeline and scan result policies, it's important to remember that security scans
|
||||
- When configuring pipeline and merge request approval policies, it's important to remember that security scans
|
||||
performed in manual jobs are not verified to determine whether MR approval is required. When you
|
||||
run a manual job with security scans, it does not ensure approval even if vulnerabilities are
|
||||
introduced.
|
||||
|
|
|
|||
|
|
@ -4,25 +4,29 @@ group: Security Policies
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Scan result policies
|
||||
# Merge request approval policies (Previously: Scan result policies)
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Ultimate
|
||||
**Offering:** SaaS, Self-managed
|
||||
|
||||
> Group-level scan result policies [introduced](https://gitlab.com/groups/gitlab-org/-/epics/7622) in GitLab 15.6.
|
||||
|
||||
You can use scan result policies to take action based on scan results. For example, one type of scan
|
||||
result policy is a security approval policy that allows approval to be required based on the
|
||||
findings of one or more security scan jobs. Scan result policies are evaluated after a CI scanning job is fully executed and both vulnerability and license type policies are evaluated based on the job artifact reports that are published in the completed pipeline.
|
||||
> - Group-level scan result policies [introduced](https://gitlab.com/groups/gitlab-org/-/epics/7622) in GitLab 15.6.
|
||||
> - Scan result policies feature was renamed to merge request approval policies in GitLab 16.9.
|
||||
|
||||
NOTE:
|
||||
Scan result policies are applicable only to [protected](../../project/protected_branches.md) target branches.
|
||||
Scan result policies feature was renamed to merge request approval policies in GitLab 16.9.
|
||||
|
||||
You can use merge request approval policies to enforce project level settings and create approval rules based on scan results. For example, one type of scan
|
||||
result policy is a security approval policy that allows approval to be required based on the
|
||||
findings of one or more security scan jobs. Merge request approval policies are evaluated after a CI scanning job is fully executed and both vulnerability and license type policies are evaluated based on the job artifact reports that are published in the completed pipeline.
|
||||
|
||||
NOTE:
|
||||
Merge request approval policies are applicable only to [protected](../../project/protected_branches.md) target branches.
|
||||
|
||||
NOTE:
|
||||
When a protected branch is created or deleted, the policy approval rules synchronize, with a delay of 1 minute.
|
||||
|
||||
The following video gives you an overview of GitLab scan result policies:
|
||||
The following video gives you an overview of GitLab merge request approval policies (previously scan result policies):
|
||||
|
||||
<div class="video-fallback">
|
||||
See the video: <a href="https://youtu.be/w5I9gcUgr9U">Overview of GitLab Scan Result Policies</a>.
|
||||
|
|
@ -34,11 +38,11 @@ The following video gives you an overview of GitLab scan result policies:
|
|||
## Requirements and limitations
|
||||
|
||||
- You must add the respective [security scanning tools](../index.md#application-coverage).
|
||||
Otherwise, scan result policies do not have any effect.
|
||||
- The maximum number of scan result policies is five per security policy project.
|
||||
Otherwise, merge request approval policies cannot get evaluated and the corresonding approvals stay required.
|
||||
- The maximum number of merge request approval policies is five per security policy project.
|
||||
- Each policy can have a maximum of five rules.
|
||||
- All configured scanners must be present in the merge request's latest pipeline. If not, approvals are required even if some vulnerability criteria have not been met.
|
||||
- Scan result policies evaluate findings and determine approval requirements based on the job artifact reports published in a completed pipeline. However, scan result policies do not check the integrity or authenticity of the scan results generated in the artifact reports.
|
||||
- Merge request approval policies evaluate findings and determine approval requirements based on the job artifact reports published in a completed pipeline. However, merge request approval policies do not check the integrity or authenticity of the scan results generated in the artifact reports.
|
||||
|
||||
## Merge request with multiple pipelines
|
||||
|
||||
|
|
@ -49,12 +53,12 @@ A project can have multiple pipeline types configured. A single commit can initi
|
|||
pipelines, each of which may contain a security scan.
|
||||
|
||||
- In GitLab 16.3 and later, the results of all completed pipelines for the latest commit in
|
||||
the merge request's source and target branch are evaluated and used to enforce the scan result policy.
|
||||
the merge request's source and target branch are evaluated and used to enforce the merge request approval policy.
|
||||
Parent-child pipelines and on-demand DAST pipelines are not considered.
|
||||
- In GitLab 16.2 and earlier, only the results of the latest completed pipeline were evaluated
|
||||
when enforcing scan result policies.
|
||||
when enforcing merge request approval policies.
|
||||
|
||||
## Scan result policy editor
|
||||
## Merge request approval policy editor
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77814) in GitLab 14.8.
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/369473) in GitLab 15.6.
|
||||
|
|
@ -76,23 +80,26 @@ before the policy changes take effect.
|
|||
The [policy editor](index.md#policy-editor) supports YAML mode and rule mode.
|
||||
|
||||
NOTE:
|
||||
Propagating scan result policies created for groups with a large number of projects take a while to complete.
|
||||
Propagating merge request approval policies created for groups with a large number of projects take a while to complete.
|
||||
|
||||
## Scan result policies schema
|
||||
## Merge request approval policies schema
|
||||
|
||||
The YAML file with scan result policies consists of an array of objects matching the scan result
|
||||
policy schema nested under the `scan_result_policy` key. You can configure a maximum of five
|
||||
policies under the `scan_result_policy` key.
|
||||
The YAML file with merge request approval policies consists of an array of objects matching the merge request approval
|
||||
policy schema nested under the `approval_policy` key. You can configure a maximum of five policies under the `approval_policy` key.
|
||||
|
||||
NOTE:
|
||||
Merge request approval policies were defined under the `scan_result_policy` key. Until GitLab 17.0, policies can be
|
||||
defined under both keys. Starting from GitLab 17.0, only `approval_policy` key is supported.
|
||||
|
||||
When you save a new policy, GitLab validates its contents against [this JSON schema](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/validators/json_schemas/security_orchestration_policy.json).
|
||||
If you're not familiar with how to read [JSON schemas](https://json-schema.org/),
|
||||
the following sections and tables provide an alternative.
|
||||
|
||||
| Field | Type | Required | Possible values | Description |
|
||||
|----------------------|-------------------------------|----------|-----------------|-------------------------------------------|
|
||||
| `scan_result_policy` | `array` of Scan Result Policy | true | | List of scan result policies (maximum 5). |
|
||||
| Field | Type | Required | Possible values | Description |
|
||||
|-------------------|-------------------------------|----------|-----------------|-------------------------------------------|
|
||||
| `approval_policy` | `array` of Merge Request Approval Policy | true | | List of merge request approval policies (maximum 5). |
|
||||
|
||||
## Scan result policy schema
|
||||
## Merge request approval policy schema
|
||||
|
||||
> - The `approval_settings` fields were [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418752) in GitLab 16.4 [with flags](../../../administration/feature_flags.md) named `scan_result_policies_block_unprotecting_branches`, `scan_result_any_merge_request`, or `scan_result_policies_block_force_push`. See the `approval_settings` section below for more information.
|
||||
|
||||
|
|
@ -107,8 +114,8 @@ the following sections and tables provide an alternative.
|
|||
|
||||
## `scan_finding` rule type
|
||||
|
||||
> - The scan result policy field `vulnerability_attributes` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123052) in GitLab 16.2 [with a flag](../../../administration/feature_flags.md) named `enforce_vulnerability_attributes_rules`. [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/418784) in GitLab 16.3. Feature flag removed.
|
||||
> - The scan result policy field `vulnerability_age` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123956) in GitLab 16.2.
|
||||
> - The merge request approval policy field `vulnerability_attributes` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123052) in GitLab 16.2 [with a flag](../../../administration/feature_flags.md) named `enforce_vulnerability_attributes_rules`. [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/418784) in GitLab 16.3. Feature flag removed.
|
||||
> - The merge request approval policy field `vulnerability_age` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123956) in GitLab 16.2.
|
||||
> - The `branch_exceptions` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418741) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133753) in GitLab 16.5. Feature flag removed.
|
||||
|
||||
This rule enforces the defined actions based on security scan findings.
|
||||
|
|
@ -206,14 +213,14 @@ The settings set in the policy overwrite settings in the project.
|
|||
| `require_password_to_approve` | `boolean` | false | `true`, `false` | `Any merge request` | When enabled, there will be password confirmation on approvals. Password confirmation adds an extra layer of security. |
|
||||
| `prevent_pushing_and_force_pushing` | `boolean` | false | `true`, `false` | All | When enabled, prevents users from pushing and force pushing to a protected branch if that branch is included in the security policy. This ensures users do not bypass the merge request process to add vulnerable code to a branch. |
|
||||
|
||||
## Example security scan result policies project
|
||||
## Example security merge request approval policies project
|
||||
|
||||
You can use this example in a `.gitlab/security-policies/policy.yml` file stored in a
|
||||
[security policy project](index.md#security-policy-project):
|
||||
|
||||
```yaml
|
||||
---
|
||||
scan_result_policy:
|
||||
approval_policy:
|
||||
- name: critical vulnerability CS approvals
|
||||
description: critical severity level only for container scanning
|
||||
enabled: true
|
||||
|
|
@ -269,13 +276,13 @@ In this example:
|
|||
- Every MR that contains more than one preexisting `low` or `unknown` vulnerability older than 30 days identified by
|
||||
container scanning requires one approval from a project member with the Owner role.
|
||||
|
||||
## Example for Scan Result Policy editor
|
||||
## Example for Merge Request Approval Policy editor
|
||||
|
||||
You can use this example in the YAML mode of the [Scan Result Policy editor](#scan-result-policy-editor).
|
||||
You can use this example in the YAML mode of the [Merge Request Approval Policy editor](#merge-request-approval-policy-editor).
|
||||
It corresponds to a single object from the previous example:
|
||||
|
||||
```yaml
|
||||
type: scan_result_policy
|
||||
type: approval_policy
|
||||
name: critical vulnerability CS approvals
|
||||
description: critical severity level only for container scanning
|
||||
enabled: true
|
||||
|
|
@ -297,23 +304,23 @@ actions:
|
|||
- adalberto.dare
|
||||
```
|
||||
|
||||
## Understanding scan result policy approvals
|
||||
## Understanding merge request approval policy approvals
|
||||
|
||||
> - The branch comparison logic for `scan_finding` was [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/428518) in GitLab 16.8 [with a flag](../../../administration/feature_flags.md) named `scan_result_policy_merge_base_pipeline`. Disabled by default.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/435297) in GitLab 16.9. Feature flag `scan_result_policy_merge_base_pipeline` removed.
|
||||
|
||||
### Scope of scan result policy comparison
|
||||
### Scope of merge request approval policy comparison
|
||||
|
||||
- To determine when approval is required on a merge request, we compare completed pipelines for each supported pipeline source for the source and target branch (for example, `feature`/`main`). This ensures the most comprehensive evaluation of scan results.
|
||||
- For the source branch, the comparison pipeline is its latest completed `HEAD` pipeline.
|
||||
- For the target branch, we compare to a common ancestor's latest completed pipeline.
|
||||
- Scan result policies considers all supported pipeline sources (based on the [`CI_PIPELINE_SOURCE` variable](../../../ci/variables/predefined_variables.md)) when comparing results from both the source and target branches when determining if a merge request requires approval. Pipeline sources `webide` and `parent_pipeline` are not supported.
|
||||
- Merge request approval policies considers all supported pipeline sources (based on the [`CI_PIPELINE_SOURCE` variable](../../../ci/variables/predefined_variables.md)) when comparing results from both the source and target branches when determining if a merge request requires approval. Pipeline sources `webide` and `parent_pipeline` are not supported.
|
||||
|
||||
### Accepting risk and ignoring vulnerabilities in future merge requests
|
||||
|
||||
For scan result policies that are scoped to `newly_detected` findings, it's important to understand the implications of this vulnerability state. A finding is considered `newly_detected` if it exists on the merge request's branch but not on the default branch. When a merge request whose branch contains `newly_detected` findings is approved and merged, approvers are "accepting the risk" of those vulnerabilities. If one or more of the same vulnerabilities were detected after this time, their status would be `previously_detected` and so not be out of scope of a policy aimed at `newly_detected` findings. For example:
|
||||
For merge request approval policies that are scoped to `newly_detected` findings, it's important to understand the implications of this vulnerability state. A finding is considered `newly_detected` if it exists on the merge request's branch but not on the default branch. When a merge request whose branch contains `newly_detected` findings is approved and merged, approvers are "accepting the risk" of those vulnerabilities. If one or more of the same vulnerabilities were detected after this time, their status would be `previously_detected` and so not be out of scope of a policy aimed at `newly_detected` findings. For example:
|
||||
|
||||
- A scan result policy is created to block critical SAST findings. If a SAST finding for CVE-1234 is approved, future merge requests with the same violation will not require approval in the project.
|
||||
- A merge request approval policy is created to block critical SAST findings. If a SAST finding for CVE-1234 is approved, future merge requests with the same violation will not require approval in the project.
|
||||
|
||||
When using license approval policies, the combination of project, component (dependency), and license are considered in the evaluation. If a license is approved as an exception, future merge requests don't require approval for the same combination of project, component (dependency), and license. The component's version is not be considered in this case. If a previously approved package is updated to a new version, approvers will not need to re-approve. For example:
|
||||
|
||||
|
|
@ -321,17 +328,17 @@ When using license approval policies, the combination of project, component (dep
|
|||
|
||||
### Multiple approvals
|
||||
|
||||
There are several situations where the scan result policy requires an additional approval step. For example:
|
||||
There are several situations where the merge request approval policy requires an additional approval step. For example:
|
||||
|
||||
- The number of security jobs is reduced in the working branch and no longer matches the number of
|
||||
security jobs in the target branch. Users can't skip the Scanning Result Policies by removing
|
||||
scanning jobs from the CI/CD configuration. Only the security scans that are configured in the
|
||||
scan result policy rules are checked for removal.
|
||||
merge request approval policy rules are checked for removal.
|
||||
|
||||
For example, consider a situation where the default branch pipeline has four security scans:
|
||||
`sast`, `secret_detection`, `container_scanning`, and `dependency_scanning`. A scan result
|
||||
`sast`, `secret_detection`, `container_scanning`, and `dependency_scanning`. A merge request approval
|
||||
policy enforces two scanners: `container_scanning` and `dependency_scanning`. If an MR removes a
|
||||
scan that is configured in scan result policy, `container_scanning` for example, an additional
|
||||
scan that is configured in merge request approval policy, `container_scanning` for example, an additional
|
||||
approval is required.
|
||||
- Someone stops a pipeline security job, and users can't skip the security scan.
|
||||
- A job in a merge request fails and is configured with `allow_failure: false`. As a result, the pipeline is in a blocked state.
|
||||
|
|
@ -339,9 +346,9 @@ There are several situations where the scan result policy requires an additional
|
|||
|
||||
### Managing scan findings used to evaluate approval requirements
|
||||
|
||||
Scan result policies evaluate the artifact reports generated by scanners in your pipelines after the pipeline has completed. Scan result policies focus on evaluating the results and determining approvals based on the scan result findings to identify potential risks, block merge requests, and require approval.
|
||||
Merge request approval policies evaluate the artifact reports generated by scanners in your pipelines after the pipeline has completed. Merge request approval policies focus on evaluating the results and determining approvals based on the scan result findings to identify potential risks, block merge requests, and require approval.
|
||||
|
||||
Scan result policies do not extend beyond that scope to reach into artifact files or scanners. Instead, we trust the results from artifact reports. This gives teams flexibility in managing their scan execution and supply chain, and customizing scan results generated in artifact reports (for example, to filter out false positives) if needed.
|
||||
Merge request approval policies do not extend beyond that scope to reach into artifact files or scanners. Instead, we trust the results from artifact reports. This gives teams flexibility in managing their scan execution and supply chain, and customizing scan results generated in artifact reports (for example, to filter out false positives) if needed.
|
||||
|
||||
Lock file tampering, for example, is outside of the scope of security policy management, but may be mitigated through use of [Code owners](../../project/codeowners/index.md#codeowners-file) or [external status checks](../../project/merge_requests/status_checks.md). For more information, see [issue 433029](https://gitlab.com/gitlab-org/gitlab/-/issues/433029).
|
||||
|
||||
|
|
@ -352,7 +359,7 @@ Lock file tampering, for example, is outside of the scope of security policy man
|
|||
We have identified in [epic 11020](https://gitlab.com/groups/gitlab-org/-/epics/11020) common areas of confusion in scan result findings that need to be addressed. Below are a few of the known issues:
|
||||
|
||||
- When using `newly_detected`, some findings may require approval when they are not introduced by the merge request (such as a new CVE on a related dependency). We currently use `main tip` of the target branch for comparison. In the future, we plan to use `merge base` for `newly_detected` policies (see [issue 428518](https://gitlab.com/gitlab-org/gitlab/-/issues/428518)).
|
||||
- Findings or errors that cause approval to be required on a scan result policy may not be evident in the Security MR Widget. By using `merge base` in [issue 428518](https://gitlab.com/gitlab-org/gitlab/-/issues/428518) some cases will be addressed. We will additionally be [displaying more granular details](https://gitlab.com/groups/gitlab-org/-/epics/11185) about what caused security policy violations.
|
||||
- Findings or errors that cause approval to be required on a merge request approval policy may not be evident in the Security MR Widget. By using `merge base` in [issue 428518](https://gitlab.com/gitlab-org/gitlab/-/issues/428518) some cases will be addressed. We will additionally be [displaying more granular details](https://gitlab.com/groups/gitlab-org/-/epics/11185) about what caused security policy violations.
|
||||
- Security policy violations are distinct compared to findings displayed in the MR widgets. Some violations may not be present in the MR widget. We are working to harmonize our features in [epic 11020](https://gitlab.com/groups/gitlab-org/-/epics/11020) and to display policy violations explicitly in merge requests in [epic 11185](https://gitlab.com/groups/gitlab-org/-/epics/11185).
|
||||
|
||||
## Experimental features
|
||||
|
|
@ -403,7 +410,7 @@ You can refine a security policy's scope to:
|
|||
|
||||
```yaml
|
||||
---
|
||||
scan_result_policy:
|
||||
approval_policy:
|
||||
- name: critical vulnerability CS approvals
|
||||
description: critical severity level only for container scanning
|
||||
enabled: true
|
||||
|
|
@ -437,15 +444,15 @@ scan_result_policy:
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
### Merge request rules widget shows a scan result policy is invalid or duplicated
|
||||
### Merge request rules widget shows a merge request approval policy is invalid or duplicated
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Ultimate
|
||||
**Offering:** Self-managed
|
||||
|
||||
On GitLab self-managed from 15.0 to 16.4, the most likely cause is that the project was exported from a group and imported into another, and had scan result policy rules. These rules are stored in a separate project to the one that was exported. As a result, the project contains policy rules that reference entities that don't exist in the imported project's group. The result is policy rules that are invalid, duplicated, or both.
|
||||
On GitLab self-managed from 15.0 to 16.4, the most likely cause is that the project was exported from a group and imported into another, and had merge request approval policy rules. These rules are stored in a separate project to the one that was exported. As a result, the project contains policy rules that reference entities that don't exist in the imported project's group. The result is policy rules that are invalid, duplicated, or both.
|
||||
|
||||
To remove all invalid scan result policy rules from a GitLab instance, an administrator can run the following script in the [Rails console](../../../administration/operations/rails_console.md).
|
||||
To remove all invalid merge request approval policy rules from a GitLab instance, an administrator can run the following script in the [Rails console](../../../administration/operations/rails_console.md).
|
||||
|
||||
```ruby
|
||||
Project.joins(:approval_rules).where(approval_rules: { report_type: %i[scan_finding license_scanning] }).where.not(approval_rules: { security_orchestration_policy_configuration_id: nil }).find_in_batches.flat_map do |batch|
|
||||
|
|
@ -468,7 +475,7 @@ end
|
|||
|
||||
### Debugging security approval policy approvals
|
||||
|
||||
GitLab SaaS users may submit a [support ticket](https://about.gitlab.com/support/) titled "Scan result approval policy debugging". Provide the following details:
|
||||
GitLab SaaS users may submit a [support ticket](https://about.gitlab.com/support/) titled "Merge request approval policy debugging". Provide the following details:
|
||||
|
||||
- Group path, project path and optionally merge request ID
|
||||
- Severity
|
||||
|
|
@ -491,4 +498,4 @@ Support teams will investigate [logs](https://log.gprd.gitlab.net/) (`pubsub-sid
|
|||
|
||||
Common failure reasons:
|
||||
|
||||
- Scanner removed by MR: Scan result policy expect that the scanners defined in the policy are present and that they successfully produce an artifact for comparison.
|
||||
- Scanner removed by MR: Merge request approval policy expects that the scanners defined in the policy are present and that they successfully produce an artifact for comparison.
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ Prerequisites:
|
|||
To view a group's contacts:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer contacts**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
|
||||

|
||||
|
||||
|
|
@ -70,7 +70,7 @@ Prerequisites:
|
|||
To create a contact:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer contacts**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
1. Select **New contact**.
|
||||
1. Complete all required fields.
|
||||
1. Select **Create new contact**.
|
||||
|
|
@ -87,7 +87,7 @@ Prerequisites:
|
|||
To edit an existing contact:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer contacts**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
1. Next to the contact you wish to edit, select **Edit** (**{pencil}**).
|
||||
1. Edit the required fields.
|
||||
1. Select **Save changes**.
|
||||
|
|
@ -105,7 +105,7 @@ Each contact can be in one of two states:
|
|||
To change the state of a contact:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer contacts**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
1. Next to the contact you wish to edit, select **Edit** (**{pencil}**).
|
||||
1. Select or clear the **Active** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
|
@ -121,7 +121,8 @@ Prerequisites:
|
|||
To view a group's organizations:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer organizations**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
1. In the upper right, select **Organizations**.
|
||||
|
||||

|
||||
|
||||
|
|
@ -134,7 +135,8 @@ Prerequisites:
|
|||
To create an organization:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer organizations**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
1. In the upper right, select **Organizations**.
|
||||
1. Select **New organization**.
|
||||
1. Complete all required fields.
|
||||
1. Select **Create new organization**.
|
||||
|
|
@ -151,7 +153,8 @@ Prerequisites:
|
|||
To edit an existing organization:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer organizations**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
1. In the upper right, select **Organizations**.
|
||||
1. Next to the organization you wish to edit, select **Edit** (**{pencil}**).
|
||||
1. Edit the required fields.
|
||||
1. Select **Save changes**.
|
||||
|
|
@ -173,7 +176,7 @@ Prerequisites:
|
|||
To view a contact's issues, select a contact from the issue sidebar, or:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer contacts**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
1. Next to the contact whose issues you wish to view, select **View issues** (**{issues}**).
|
||||
|
||||
### View issues linked to an organization
|
||||
|
|
@ -185,7 +188,8 @@ Prerequisites:
|
|||
To view an organization's issues:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
1. Select **Plan > Customer organizations**.
|
||||
1. Select **Plan > Customer relations**.
|
||||
1. In the upper right, select **Organizations**.
|
||||
1. Next to the organization whose issues you wish to view, select **View issues** (**{issues}**).
|
||||
|
||||
### View contacts linked to an issue
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ Whenever an approval rule cannot be satisfied, the rule is displayed as **Auto a
|
|||
- The number of required approvals is more than the number of eligible approvers.
|
||||
|
||||
These rules are automatically approved to unblock their respective merge requests, unless they were
|
||||
created through a [scan result policy](../../../application_security/policies/scan-result-policies.md).
|
||||
Invalid approval rules created through a scan result policy are presented with
|
||||
created through a [merge request approval policy](../../../application_security/policies/scan-result-policies.md).
|
||||
Invalid approval rules created through a merge request approval policy are presented with
|
||||
**Action required** and are not automatically approved, blocking their respective merge requests.
|
||||
|
||||
## Related topics
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ DETAILS:
|
|||
> - Bot comment for approvals [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/411656) in GitLab 16.2 [with a flag](../../../../administration/feature_flags.md) named `security_policy_approval_notification`. Enabled by default.
|
||||
> - Bot comment for approvals [generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130827) in GitLab 16.3. Feature flag `security_policy_approval_notification` removed.
|
||||
|
||||
You can use [scan result policies](../../../application_security/policies/scan-result-policies.md#scan-result-policy-editor) to define security approvals based on the status of vulnerabilities in the merge request and the default branch.
|
||||
You can use [merge request approval policies](../../../application_security/policies/scan-result-policies.md#merge-request-approval-policy-editor) to define security approvals based on the status of vulnerabilities in the merge request and the default branch.
|
||||
Details for each security policy is shown in the Security Approvals section of your Merge Request configuration.
|
||||
|
||||
The security approval rules are applied to all merge requests until the pipeline is complete. The application of the
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ When a branch is protected, the default behavior enforces these restrictions on
|
|||
for that branch at the project level are ignored. All other protections continue
|
||||
to use project level settings.
|
||||
|
||||
You can implement a [scan result policy](../application_security/policies/scan-result-policies.md#approval_settings)
|
||||
You can implement a [merge request approval policy](../application_security/policies/scan-result-policies.md#approval_settings)
|
||||
to prevent protected branches being unprotected or deleted.
|
||||
|
||||
### When a branch matches multiple rules
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ module Gitlab
|
|||
linked_items: 'Linked items',
|
||||
color: 'Color',
|
||||
rolledup_dates: 'Rolledup dates',
|
||||
participants: 'Participants'
|
||||
participants: 'Participants',
|
||||
time_tracking: 'Time tracking'
|
||||
}.freeze
|
||||
|
||||
WIDGETS_FOR_TYPE = {
|
||||
|
|
@ -44,7 +45,8 @@ module Gitlab
|
|||
:current_user_todos,
|
||||
:award_emoji,
|
||||
:linked_items,
|
||||
:participants
|
||||
:participants,
|
||||
:time_tracking
|
||||
],
|
||||
incident: [
|
||||
:assignees,
|
||||
|
|
@ -55,7 +57,8 @@ module Gitlab
|
|||
:current_user_todos,
|
||||
:award_emoji,
|
||||
:linked_items,
|
||||
:participants
|
||||
:participants,
|
||||
:time_tracking
|
||||
],
|
||||
test_case: [
|
||||
:description,
|
||||
|
|
@ -64,7 +67,8 @@ module Gitlab
|
|||
:current_user_todos,
|
||||
:award_emoji,
|
||||
:linked_items,
|
||||
:participants
|
||||
:participants,
|
||||
:time_tracking
|
||||
],
|
||||
requirement: [
|
||||
:description,
|
||||
|
|
@ -76,7 +80,8 @@ module Gitlab
|
|||
:current_user_todos,
|
||||
:award_emoji,
|
||||
:linked_items,
|
||||
:participants
|
||||
:participants,
|
||||
:time_tracking
|
||||
],
|
||||
task: [
|
||||
:assignees,
|
||||
|
|
@ -92,7 +97,8 @@ module Gitlab
|
|||
:current_user_todos,
|
||||
:award_emoji,
|
||||
:linked_items,
|
||||
:participants
|
||||
:participants,
|
||||
:time_tracking
|
||||
],
|
||||
objective: [
|
||||
:assignees,
|
||||
|
|
@ -139,7 +145,8 @@ module Gitlab
|
|||
:linked_items,
|
||||
:color,
|
||||
:rolledup_dates,
|
||||
:participants
|
||||
:participants,
|
||||
:time_tracking
|
||||
],
|
||||
ticket: [
|
||||
:assignees,
|
||||
|
|
@ -156,7 +163,8 @@ module Gitlab
|
|||
:current_user_todos,
|
||||
:award_emoji,
|
||||
:linked_items,
|
||||
:participants
|
||||
:participants,
|
||||
:time_tracking
|
||||
]
|
||||
}.freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -69,12 +69,16 @@ module Gitlab
|
|||
|
||||
def print_operation_definition(op, indent: "")
|
||||
@in_operation = true
|
||||
out = +"#{indent}#{op.operation_type}"
|
||||
out << " #{op.name}" if op.name
|
||||
print_string("#{indent}#{op.operation_type}")
|
||||
print_string(" #{op.name}") if op.name
|
||||
|
||||
# Do these first, so that we detect any skipped arguments
|
||||
dirs = print_directives(op.directives)
|
||||
sels = print_selections(op.selections, indent: indent)
|
||||
# Do these on a temp instance, so that we detect any skipped arguments
|
||||
# without actually printing the output to the current buffer
|
||||
temp_printer = self.class.new
|
||||
op.directives.each { |d| temp_printer.print(d) }
|
||||
op.selections.each { |s| temp_printer.print(s) }
|
||||
@skipped_arguments |= temp_printer.skipped_arguments
|
||||
@printed_arguments |= temp_printer.printed_arguments
|
||||
|
||||
# remove variable definitions only used in skipped (client) fields
|
||||
vars = op.variables.reject do |v|
|
||||
|
|
@ -82,10 +86,16 @@ module Gitlab
|
|||
end
|
||||
|
||||
if vars.any?
|
||||
out << "(#{vars.map { |v| print_variable_definition(v) }.join(", ")})"
|
||||
print_string("(")
|
||||
vars.each_with_index do |v, i|
|
||||
print_variable_definition(v)
|
||||
print_string(", ") if i < vars.size - 1
|
||||
end
|
||||
print_string(")")
|
||||
end
|
||||
|
||||
out + dirs + sels
|
||||
print_directives(op.directives)
|
||||
print_selections(op.selections, indent: indent)
|
||||
ensure
|
||||
@in_operation = false
|
||||
end
|
||||
|
|
@ -94,24 +104,21 @@ module Gitlab
|
|||
if skips? &&
|
||||
(field.directives.any? { |d| d.name == 'client' || d.name == 'persist' } || field.name == '__persist')
|
||||
skipped = self.class.new(false)
|
||||
|
||||
skipped.print_node(field)
|
||||
skipped.print(field)
|
||||
@skipped_fragments |= skipped.used_fragments
|
||||
@skipped_arguments |= skipped.printed_arguments
|
||||
|
||||
return ''
|
||||
return
|
||||
end
|
||||
|
||||
ret = super
|
||||
super
|
||||
|
||||
@fields_printed += 1 if @in_operation && ret != ''
|
||||
|
||||
ret
|
||||
@fields_printed += 1 if @in_operation
|
||||
end
|
||||
|
||||
def print_fragment_definition(fragment_def, indent: "")
|
||||
if skips? && @skipped_fragments.include?(fragment_def.name) && !@used_fragments.include?(fragment_def.name)
|
||||
return ''
|
||||
return
|
||||
end
|
||||
|
||||
super
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ module Gitlab
|
|||
types Issue, MergeRequest
|
||||
condition do
|
||||
quick_action_target.supports_time_tracking? &&
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
|
||||
end
|
||||
parse_params do |raw_duration|
|
||||
Gitlab::TimeTrackingFormatter.parse(raw_duration, keep_zero: true)
|
||||
|
|
@ -214,7 +214,7 @@ module Gitlab
|
|||
types Issue, MergeRequest
|
||||
condition do
|
||||
quick_action_target.persisted? &&
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
|
||||
end
|
||||
command :remove_estimate, :remove_time_estimate do
|
||||
@updates[:time_estimate] = 0
|
||||
|
|
@ -225,7 +225,7 @@ module Gitlab
|
|||
execution_message { _('Removed spent time.') }
|
||||
condition do
|
||||
quick_action_target.persisted? &&
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
|
||||
end
|
||||
types Issue, MergeRequest
|
||||
command :remove_time_spent do
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ module Sidebars
|
|||
override :configure_menu_items
|
||||
def configure_menu_items
|
||||
add_item(contacts_menu_item) if can_read_contact?
|
||||
add_item(organizations_menu_item) if can_read_organization?
|
||||
|
||||
true
|
||||
end
|
||||
|
|
@ -26,7 +25,7 @@ module Sidebars
|
|||
def render?
|
||||
return false unless context.group.root?
|
||||
|
||||
can_read_contact? || can_read_organization?
|
||||
can_read_contact?
|
||||
end
|
||||
|
||||
override :serialize_as_menu_item_args
|
||||
|
|
@ -38,31 +37,17 @@ module Sidebars
|
|||
|
||||
def contacts_menu_item
|
||||
::Sidebars::MenuItem.new(
|
||||
title: context.is_super_sidebar ? _('Customer contacts') : _('Contacts'),
|
||||
title: context.is_super_sidebar ? _('Customer relations') : _('Contacts'),
|
||||
link: group_crm_contacts_path(context.group),
|
||||
super_sidebar_parent: ::Sidebars::Groups::SuperSidebarMenus::PlanMenu,
|
||||
active_routes: { controller: 'groups/crm/contacts' },
|
||||
active_routes: { controller: %w[groups/crm/contacts groups/crm/organizations] },
|
||||
item_id: :crm_contacts
|
||||
)
|
||||
end
|
||||
|
||||
def organizations_menu_item
|
||||
::Sidebars::MenuItem.new(
|
||||
title: context.is_super_sidebar ? _('Customer organizations') : _('Organizations'),
|
||||
link: group_crm_organizations_path(context.group),
|
||||
super_sidebar_parent: ::Sidebars::Groups::SuperSidebarMenus::PlanMenu,
|
||||
active_routes: { controller: 'groups/crm/organizations' },
|
||||
item_id: :crm_organizations
|
||||
)
|
||||
end
|
||||
|
||||
def can_read_contact?
|
||||
can?(context.current_user, :read_crm_contact, context.group)
|
||||
end
|
||||
|
||||
def can_read_organization?
|
||||
can?(context.current_user, :read_crm_organization, context.group)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@ module Sidebars
|
|||
:milestones,
|
||||
:iterations,
|
||||
:group_wiki,
|
||||
:crm_contacts,
|
||||
:crm_organizations
|
||||
:crm_contacts
|
||||
].each { |id| add_item(::Sidebars::NilMenuItem.new(item_id: id)) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15027,6 +15027,9 @@ msgstr ""
|
|||
msgid "Crm|Contact has been updated."
|
||||
msgstr ""
|
||||
|
||||
msgid "Crm|Contacts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Crm|Customer relations contacts"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15066,6 +15069,9 @@ msgstr ""
|
|||
msgid "Crm|Organization has been updated."
|
||||
msgstr ""
|
||||
|
||||
msgid "Crm|Organizations"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cron time zone"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15195,15 +15201,9 @@ msgstr ""
|
|||
msgid "Custom range"
|
||||
msgstr ""
|
||||
|
||||
msgid "Customer contacts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Customer experience improvement and third-party offers"
|
||||
msgstr ""
|
||||
|
||||
msgid "Customer organizations"
|
||||
msgstr ""
|
||||
|
||||
msgid "Customer relations"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -22,5 +22,10 @@ else
|
|||
gdk tail $log &>${CI_BUILDS_DIR}/gdk.${log}.log &
|
||||
done
|
||||
|
||||
# TODO: Check if we can reconfigure gitlab to write logs directly to `BUILDS_DIR`
|
||||
for logfile in ${HOME}/gitlab-development-kit/gitlab/log/*.log; do
|
||||
tail -f $logfile &>${CI_BUILDS_DIR}/$(basename $logfile) &
|
||||
done
|
||||
|
||||
exec "$@"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ describe('Customer relations contacts root app', () => {
|
|||
let wrapper;
|
||||
let fakeApollo;
|
||||
|
||||
const findOrganizationsLink = () => wrapper.findByTestId('organizations-link');
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findNewContactButton = () => wrapper.findByTestId('new-contact-button');
|
||||
const findTable = () => wrapper.findComponent(PaginatedTableWithSearchAndTabs);
|
||||
|
|
@ -26,6 +27,7 @@ describe('Customer relations contacts root app', () => {
|
|||
queryHandler = successQueryHandler,
|
||||
countQueryHandler = successCountQueryHandler,
|
||||
canAdminCrmContact = true,
|
||||
canReadCrmOrganization = true,
|
||||
textQuery = null,
|
||||
} = {}) => {
|
||||
fakeApollo = createMockApollo([
|
||||
|
|
@ -37,7 +39,9 @@ describe('Customer relations contacts root app', () => {
|
|||
groupFullPath: 'flightjs',
|
||||
groupId: 26,
|
||||
groupIssuesPath: '/issues',
|
||||
groupOrganizationsPath: '/organizations',
|
||||
canAdminCrmContact,
|
||||
canReadCrmOrganization,
|
||||
textQuery,
|
||||
},
|
||||
apolloProvider: fakeApollo,
|
||||
|
|
@ -75,6 +79,20 @@ describe('Customer relations contacts root app', () => {
|
|||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('organizations link', () => {
|
||||
it('renders when canReadCrmOrganization is true', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findOrganizationsLink().attributes('href')).toBe('/organizations');
|
||||
});
|
||||
|
||||
it('does not render when canReadCrmOrganization is false', () => {
|
||||
mountComponent({ canReadCrmOrganization: false });
|
||||
|
||||
expect(findOrganizationsLink().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('new contact button', () => {
|
||||
it('should exist when user has permission', () => {
|
||||
mountComponent();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ describe('Customer relations organizations root app', () => {
|
|||
let wrapper;
|
||||
let fakeApollo;
|
||||
|
||||
const findContactsLink = () => wrapper.findByTestId('contacts-link');
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findNewOrganizationButton = () => wrapper.findByTestId('new-organization-button');
|
||||
const findTable = () => wrapper.findComponent(PaginatedTableWithSearchAndTabs);
|
||||
|
|
@ -31,6 +32,7 @@ describe('Customer relations organizations root app', () => {
|
|||
queryHandler = successQueryHandler,
|
||||
countQueryHandler = successCountQueryHandler,
|
||||
canAdminCrmOrganization = true,
|
||||
canReadCrmContact = true,
|
||||
textQuery = null,
|
||||
} = {}) => {
|
||||
fakeApollo = createMockApollo([
|
||||
|
|
@ -40,6 +42,8 @@ describe('Customer relations organizations root app', () => {
|
|||
wrapper = shallowMountExtended(OrganizationsRoot, {
|
||||
provide: {
|
||||
canAdminCrmOrganization,
|
||||
canReadCrmContact,
|
||||
groupContactsPath: '/contacts',
|
||||
groupFullPath: 'flightjs',
|
||||
groupIssuesPath: '/issues',
|
||||
textQuery,
|
||||
|
|
@ -79,6 +83,20 @@ describe('Customer relations organizations root app', () => {
|
|||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('contacts link', () => {
|
||||
it('renders when canReadContact is true', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findContactsLink().attributes('href')).toBe('/contacts');
|
||||
});
|
||||
|
||||
it('does not render when canReadContact is false', () => {
|
||||
mountComponent({ canReadCrmContact: false });
|
||||
|
||||
expect(findContactsLink().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('new organization button', () => {
|
||||
it('should exist when user has permission', () => {
|
||||
mountComponent();
|
||||
|
|
|
|||
|
|
@ -196,6 +196,39 @@ RSpec.describe GitlabSchema do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.resolve_type' do
|
||||
let(:object) { build(:user) }
|
||||
|
||||
let(:object_type) { Class.new(Types::BaseObject) }
|
||||
let(:union_type) { Class.new(Types::BaseUnion) }
|
||||
|
||||
it 'returns the type for object types' do
|
||||
expect(described_class.resolve_type(object_type, object, {})).to eq([object_type, object])
|
||||
end
|
||||
|
||||
it 'raises an exception for non-object types' do
|
||||
expect { described_class.resolve_type(union_type, object, {}) }.to raise_error(GraphQL::RequiredImplementationMissingError)
|
||||
end
|
||||
|
||||
context 'when accepts is defined' do
|
||||
let(:object_type) do
|
||||
Class.new(Types::BaseObject) do
|
||||
accepts User
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the type if the object is accepted' do
|
||||
expect(described_class.resolve_type(object_type, object, {})).to eq([object_type, object])
|
||||
end
|
||||
|
||||
it 'returns nil when object is not accepted' do
|
||||
project = build(:project)
|
||||
|
||||
expect(described_class.resolve_type(object_type, project, {})).to eq([nil, project])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validate_max_errors' do
|
||||
it 'reports at most 5 errors' do
|
||||
query = <<~GQL
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ RSpec.describe Mutations::CustomEmoji::Destroy do
|
|||
context 'field tests' do
|
||||
subject { described_class }
|
||||
|
||||
it { is_expected.to have_graphql_arguments(:id) }
|
||||
it { is_expected.to have_graphql_arguments(:clientMutationId, :id) }
|
||||
it { is_expected.to have_graphql_field(:custom_emoji) }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -159,4 +159,82 @@ RSpec.describe GitlabSchema.types['Group'], feature_category: :groups_and_projec
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'emailsDisabled' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
group(fullPath: "#{group.full_path}") {
|
||||
emailsDisabled
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
subject(:result) do
|
||||
result = GitlabSchema.execute(query).as_json
|
||||
result.dig('data', 'group', 'emailsDisabled')
|
||||
end
|
||||
|
||||
it 'is not a deprecated field' do
|
||||
expect(described_class.fields['emailsDisabled'].deprecation).to be_nil
|
||||
end
|
||||
|
||||
describe 'when emails_enabled is true' do
|
||||
before do
|
||||
group.update!(emails_enabled: true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
|
||||
describe 'when emails_enabled is false' do
|
||||
before do
|
||||
group.update!(emails_enabled: false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'emailsEnabled' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
group(fullPath: "#{group.full_path}") {
|
||||
emailsEnabled
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
subject(:result) do
|
||||
result = GitlabSchema.execute(query).as_json
|
||||
result.dig('data', 'group', 'emailsEnabled')
|
||||
end
|
||||
|
||||
it 'is not a deprecated field' do
|
||||
expect(described_class.fields['emailsEnabled'].deprecation).to be_nil
|
||||
end
|
||||
|
||||
describe 'when emails_enabled is true' do
|
||||
before do
|
||||
group.update!(emails_enabled: true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
describe 'when emails_enabled is false' do
|
||||
before do
|
||||
group.update!(emails_enabled: false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ RSpec.describe Types::WorkItems::WidgetInterface, feature_category: :team_planni
|
|||
WorkItems::Widgets::CurrentUserTodos | Types::WorkItems::Widgets::CurrentUserTodosType
|
||||
WorkItems::Widgets::AwardEmoji | Types::WorkItems::Widgets::AwardEmojiType
|
||||
WorkItems::Widgets::LinkedItems | Types::WorkItems::Widgets::LinkedItemsType
|
||||
WorkItems::Widgets::LinkedItems | Types::WorkItems::Widgets::LinkedItemsType
|
||||
WorkItems::Widgets::StartAndDueDate | Types::WorkItems::Widgets::StartAndDueDateType
|
||||
WorkItems::Widgets::Milestone | Types::WorkItems::Widgets::MilestoneType
|
||||
WorkItems::Widgets::Participants | Types::WorkItems::Widgets::ParticipantsType
|
||||
WorkItems::Widgets::TimeTracking | Types::WorkItems::Widgets::TimeTrackingType
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Types::WorkItems::Widgets::TimeTrackingType, feature_category: :team_planning do
|
||||
it 'exposes the expected fields' do
|
||||
expected_fields = %i[time_estimate total_time_spent timelogs type]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
end
|
||||
|
|
@ -343,7 +343,7 @@ RSpec.describe Gitlab::Graphql::Queries do
|
|||
it_behaves_like 'an invalid GraphQL query for the blog schema' do
|
||||
let(:errors) do
|
||||
contain_exactly(
|
||||
have_attributes(message: include('Parse error'))
|
||||
have_attributes(message: include('Expected LCURLY, actual: RCURLY ("}") at [1, 7]'))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ RSpec.describe Sidebars::Groups::SuperSidebarMenus::PlanMenu, feature_category:
|
|||
:milestones,
|
||||
:iterations,
|
||||
:group_wiki,
|
||||
:crm_contacts,
|
||||
:crm_organizations
|
||||
:crm_contacts
|
||||
])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AddTimeTrackingWidgetDefinitionToWorkItemTypes, :migration, feature_category: :team_planning do
|
||||
it_behaves_like 'migration that adds widget to work items definitions',
|
||||
widget_name: described_class::WIDGET_NAME,
|
||||
work_item_types: described_class::WORK_ITEM_TYPES
|
||||
end
|
||||
|
|
@ -142,6 +142,17 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#supports_time_tracking?' do
|
||||
Gitlab::DatabaseImporters::WorkItems::BaseTypeImporter::WIDGETS_FOR_TYPE.each_pair do |base_type, widgets|
|
||||
specify do
|
||||
work_item = build(:work_item, base_type)
|
||||
supports_time_tracking = widgets.include?(:time_tracking)
|
||||
|
||||
expect(work_item.supports_time_tracking?).to eq(supports_time_tracking)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#supported_quick_action_commands' do
|
||||
let(:work_item) { build(:work_item, :task) }
|
||||
|
||||
|
|
|
|||
|
|
@ -186,6 +186,25 @@ RSpec.describe WorkItems::Type, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#supports_time_tracking??' do
|
||||
let_it_be_with_reload(:work_item_type) { create(:work_item_type) }
|
||||
let_it_be_with_reload(:widget_definition) do
|
||||
create(:widget_definition, work_item_type: work_item_type, widget_type: :time_tracking)
|
||||
end
|
||||
|
||||
subject(:supports_time_tracking) { work_item_type.supports_time_tracking? }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
|
||||
context 'when the time tracking widget is not supported' do
|
||||
before do
|
||||
widget_definition.update!(disabled: true)
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#default_issue?' do
|
||||
context 'when work item type is default Issue' do
|
||||
let(:work_item_type) { build(:work_item_type, name: described_class::TYPE_NAMES[:issue]) }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
|
|||
::WorkItems::Widgets::CurrentUserTodos,
|
||||
::WorkItems::Widgets::AwardEmoji,
|
||||
::WorkItems::Widgets::LinkedItems,
|
||||
::WorkItems::Widgets::Participants
|
||||
::WorkItems::Widgets::Participants,
|
||||
::WorkItems::Widgets::TimeTracking
|
||||
]
|
||||
|
||||
if Gitlab.ee?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItems::Widgets::TimeTracking, feature_category: :team_planning do
|
||||
let_it_be(:work_item) { create(:work_item, :epic, time_estimate: 12.hours) }
|
||||
let_it_be(:timelog1) { create(:timelog, issue: work_item, time_spent: 1.hour.to_i) }
|
||||
let_it_be(:timelog2) { create(:timelog, issue: work_item, time_spent: 2.hours.to_i) }
|
||||
|
||||
describe '.quick_action_params' do
|
||||
subject { described_class.quick_action_params }
|
||||
|
||||
it { is_expected.to contain_exactly(:time_estimate, :spend_time) }
|
||||
end
|
||||
|
||||
describe '.quick_action_commands' do
|
||||
subject { described_class.quick_action_commands }
|
||||
|
||||
it 'lists all available quick actions' do
|
||||
is_expected.to contain_exactly(
|
||||
:estimate, :estimate_time, :remove_estimate,
|
||||
:remove_time_estimate, :remove_time_spent, :spend, :spend_time, :spent
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.type' do
|
||||
it { expect(described_class.type).to eq(:time_tracking) }
|
||||
end
|
||||
|
||||
describe '#type' do
|
||||
it { expect(described_class.new(work_item).type).to eq(:time_tracking) }
|
||||
end
|
||||
|
||||
describe 'time tracking data' do
|
||||
it { expect(described_class.new(work_item).time_estimate).to eq(work_item.time_estimate) }
|
||||
it { expect(described_class.new(work_item).total_time_spent).to eq(3.hours) }
|
||||
it { expect(described_class.new(work_item).timelogs.map(&:id)).to match_array([timelog1.id, timelog2.id]) }
|
||||
end
|
||||
end
|
||||
|
|
@ -1582,6 +1582,104 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with time tracking widget input' do
|
||||
shared_examples 'mutation updating work item with time tracking data' do
|
||||
it 'updates time tracking' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
mutation_work_item.reload
|
||||
end.to change { mutation_work_item.time_estimate }.from(0).to(12.hours.to_i).and(
|
||||
change { mutation_work_item.total_time_spent }.from(0).to(2.hours.to_i)
|
||||
)
|
||||
|
||||
expect(mutation_response['workItem']['widgets']).to include(
|
||||
'timeEstimate' => 12.hours.to_i,
|
||||
'totalTimeSpent' => 2.hours.to_i,
|
||||
'timelogs' => {
|
||||
'nodes' => [
|
||||
{
|
||||
'timeSpent' => 2.hours.to_i
|
||||
}
|
||||
]
|
||||
},
|
||||
'type' => 'TIME_TRACKING'
|
||||
)
|
||||
|
||||
expect(mutation_response['workItem']['widgets']).to include(
|
||||
'description' => 'some description',
|
||||
'type' => 'DESCRIPTION'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
let(:fields) do
|
||||
<<~FIELDS
|
||||
workItem {
|
||||
widgets {
|
||||
... on WorkItemWidgetTimeTracking {
|
||||
type
|
||||
timeEstimate
|
||||
totalTimeSpent
|
||||
timelogs {
|
||||
nodes {
|
||||
timeSpent
|
||||
}
|
||||
}
|
||||
}
|
||||
... on WorkItemWidgetDescription {
|
||||
type
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
FIELDS
|
||||
end
|
||||
|
||||
let_it_be(:group_work_item) { create(:work_item, :task, :group_level, namespace: group) }
|
||||
|
||||
context 'when adding time estimate and time spent' do
|
||||
context 'with quick action' do
|
||||
let(:input) { { 'descriptionWidget' => { 'description' => "some description\n\n/estimate 12h\n/spend 2h" } } }
|
||||
|
||||
it_behaves_like 'mutation updating work item with time tracking data'
|
||||
end
|
||||
|
||||
context 'when work item belongs to the group' do
|
||||
let(:mutation_work_item) { group_work_item }
|
||||
let(:input) { { 'descriptionWidget' => { 'description' => "some description\n\n/estimate 12h\n/spend 2h" } } }
|
||||
|
||||
it_behaves_like 'mutation updating work item with time tracking data'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the work item type does not support time tracking widget' do
|
||||
let_it_be(:work_item) { create(:work_item, :task, project: project) }
|
||||
|
||||
let(:input) { { 'descriptionWidget' => { 'description' => "some description\n\n/estimate 12h\n/spend 2h" } } }
|
||||
|
||||
before do
|
||||
WorkItems::Type.default_by_type(:task).widget_definitions
|
||||
.find_by_widget_type(:time_tracking).update!(disabled: true)
|
||||
end
|
||||
|
||||
it 'ignores the quick action' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
work_item.reload
|
||||
end.not_to change { work_item.time_estimate }
|
||||
|
||||
expect(mutation_response['workItem']['widgets']).to include(
|
||||
'description' => "some description",
|
||||
'type' => 'DESCRIPTION'
|
||||
)
|
||||
expect(mutation_response['workItem']['widgets']).not_to include(
|
||||
'type' => 'TIME_TRACKING'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unsupported widget input is sent' do
|
||||
let_it_be(:work_item) { create(:work_item, :test_case, project: project) }
|
||||
|
||||
|
|
|
|||
|
|
@ -650,7 +650,7 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
|
|||
|
||||
let(:first_param) { 1 }
|
||||
let(:all_records) { [link1, link2] }
|
||||
let(:data_path) { ['workItem', 'widgets', 'linkedItems', -2] }
|
||||
let(:data_path) { ['workItem', 'widgets', 'linkedItems', -3] }
|
||||
|
||||
def widget_fields(args)
|
||||
query_graphql_field(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WorkItems::Callbacks::TimeTracking, feature_category: :team_planning do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :private, group: group) }
|
||||
let_it_be(:reporter) do
|
||||
create(:user).tap { |u| group.add_reporter(u) }
|
||||
end
|
||||
|
||||
let_it_be(:guest) do
|
||||
create(:user).tap { |u| group.add_guest(u) }
|
||||
end
|
||||
|
||||
let(:current_user) { reporter }
|
||||
let(:params) do
|
||||
{
|
||||
time_estimate: 12.hours.to_i,
|
||||
spend_time: {
|
||||
duration: 2.hours.to_i,
|
||||
user_id: current_user.id,
|
||||
spent_at: Date.today
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:callback) { described_class.new(issuable: issuable, current_user: current_user, params: params) }
|
||||
|
||||
describe '#after_initialize' do
|
||||
shared_examples 'sets work item time tracking data' do
|
||||
it 'correctly sets time tracking data', :aggregate_failures do
|
||||
callback.after_initialize
|
||||
|
||||
expect(issuable.time_spent).to eq(2.hours.to_i)
|
||||
expect(issuable.time_estimate).to eq(12.hours.to_i)
|
||||
expect(issuable.timelogs.last.time_spent).to eq(2.hours.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'does not set work item time tracking data' do
|
||||
it 'does not change work item time tracking data', :aggregate_failures do
|
||||
callback.after_initialize
|
||||
|
||||
if issuable.persisted?
|
||||
expect(issuable.time_estimate).to eq(2.hours.to_i)
|
||||
expect(issuable.total_time_spent).to eq(3.hours.to_i)
|
||||
expect(issuable.timelogs.last.time_spent).to eq(3.hours.to_i)
|
||||
else
|
||||
expect(issuable.time_estimate).to eq(0)
|
||||
expect(issuable.time_spent).to eq(nil)
|
||||
expect(issuable.timelogs).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when at project level' do
|
||||
let(:issuable) { project_work_item }
|
||||
|
||||
context 'and work item is not persisted' do
|
||||
let(:project_work_item) { build(:work_item, :incident, project: project) }
|
||||
|
||||
it_behaves_like 'sets work item time tracking data'
|
||||
|
||||
context 'when time tracking param is not present' do
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'does not set work item time tracking data'
|
||||
end
|
||||
|
||||
context 'when widget does not exist in new type' do
|
||||
before do
|
||||
allow(callback).to receive(:excluded_in_new_type?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not set work item time tracking data'
|
||||
end
|
||||
end
|
||||
|
||||
context 'and work item is persisted' do
|
||||
let_it_be_with_reload(:project_work_item) do
|
||||
create(:work_item, :task, project: project, time_estimate: 2.hours.to_i)
|
||||
end
|
||||
|
||||
let_it_be(:timelog) { create(:timelog, issue: project_work_item, time_spent: 3.hours.to_i) }
|
||||
|
||||
it_behaves_like 'sets work item time tracking data'
|
||||
|
||||
context 'when time tracking param is not present' do
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'does not set work item time tracking data'
|
||||
end
|
||||
|
||||
context 'when widget does not exist in new type' do
|
||||
before do
|
||||
allow(callback).to receive(:excluded_in_new_type?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not set work item time tracking data'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when at group level' do
|
||||
let(:issuable) { group_work_item }
|
||||
|
||||
context 'and work item is not persisted' do
|
||||
let(:group_work_item) { build(:work_item, :task, :group_level, namespace: group) }
|
||||
|
||||
it_behaves_like 'sets work item time tracking data'
|
||||
|
||||
context 'when time tracking param is not present' do
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'does not set work item time tracking data'
|
||||
end
|
||||
end
|
||||
|
||||
context 'and work item is persisted' do
|
||||
let_it_be_with_reload(:group_work_item) do
|
||||
create(:work_item, :task, :group_level, namespace: group, time_estimate: 2.hours.to_i)
|
||||
end
|
||||
|
||||
let_it_be(:timelog) { create(:timelog, issue: group_work_item, time_spent: 3.hours.to_i) }
|
||||
|
||||
it_behaves_like 'sets work item time tracking data'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -59,12 +59,7 @@ module NavbarStructureHelper
|
|||
insert_after_sub_nav_item(
|
||||
after,
|
||||
within: _('Plan'),
|
||||
new_sub_nav_item_name: _("Customer contacts")
|
||||
)
|
||||
insert_after_sub_nav_item(
|
||||
_("Customer contacts"),
|
||||
within: _('Plan'),
|
||||
new_sub_nav_item_name: _("Customer organizations")
|
||||
new_sub_nav_item_name: _("Customer relations")
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ rspec:
|
|||
parallel:
|
||||
matrix:
|
||||
- RUBY_VERSION: ["3.0", "3.1", "3.2"]
|
||||
REDIS_VERSION: ["6.0", "6.2", "7.0"]
|
||||
|
||||
.with_redis:
|
||||
services:
|
||||
- redis:6.0-alpine
|
||||
- redis:${REDIS_VERSION}-alpine
|
||||
variables:
|
||||
REDIS_URL: "redis://redis"
|
||||
|
|
|
|||
Loading…
Reference in New Issue