Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-02-02 03:11:42 +00:00
parent 37b8d1f7b5
commit e8360356fb
78 changed files with 1038 additions and 205 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -205,6 +205,7 @@
"WorkItemWidgetStartAndDueDate",
"WorkItemWidgetStatus",
"WorkItemWidgetTestReports",
"WorkItemWidgetTimeTracking",
"WorkItemWidgetWeight"
],
"WorkItemWidgetDefinition": [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
a1df0ace59e3eecb27d4911c7169b59d2ad3407736b66a9c4c485bf9c793e026

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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**.
![Contacts list](crm_contacts_v14_10.png)
@ -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**.
![Organizations list](crm_organizations_v14_10.png)
@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,8 +23,7 @@ RSpec.describe Sidebars::Groups::SuperSidebarMenus::PlanMenu, feature_category:
:milestones,
:iterations,
:group_wiki,
:crm_contacts,
:crm_organizations
:crm_contacts
])
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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