Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-11 12:08:16 +00:00
parent 43e40e8daa
commit 2761b4465b
89 changed files with 707 additions and 296 deletions

View File

@ -29,7 +29,6 @@ Performance/MethodObjectAsBlock:
- 'ee/app/services/security/findings/cleanup_service.rb'
- 'ee/app/services/security/ingestion/ingest_reports_service.rb'
- 'ee/app/services/security/ingestion/tasks/ingest_vulnerability_statistics.rb'
- 'ee/app/services/security/store_findings_metadata_service.rb'
- 'ee/app/services/security/store_grouped_scans_service.rb'
- 'ee/lib/ee/container_registry/client.rb'
- 'ee/lib/ee/gitlab/ci/config_ee.rb'

View File

@ -293,7 +293,6 @@ RSpec/ExpectChange:
- 'ee/spec/services/security/orchestration/assign_service_spec.rb'
- 'ee/spec/services/security/override_uuids_service_spec.rb'
- 'ee/spec/services/security/security_orchestration_policies/sync_opened_merge_requests_service_spec.rb'
- 'ee/spec/services/security/store_findings_metadata_service_spec.rb'
- 'ee/spec/services/security/store_scan_service_spec.rb'
- 'ee/spec/services/start_pull_mirroring_service_spec.rb'
- 'ee/spec/services/status_page/mark_for_publication_service_spec.rb'

View File

@ -166,7 +166,7 @@ gem 'seed-fu', '~> 2.3.7'
gem 'elasticsearch-model', '~> 7.2'
gem 'elasticsearch-rails', '~> 7.2', require: 'elasticsearch/rails/instrumentation'
gem 'elasticsearch-api', '7.13.3'
gem 'aws-sdk-core', '~> 3.166.0'
gem 'aws-sdk-core', '~> 3.167.0'
gem 'aws-sdk-cloudformation', '~> 1'
gem 'aws-sdk-s3', '~> 1.117.1'
gem 'faraday_middleware-aws-sigv4', '~>0.3.0'

View File

@ -33,9 +33,9 @@
{"name":"awesome_print","version":"1.9.2","platform":"ruby","checksum":"e99b32b704acff16d768b3468680793ced40bfdc4537eb07e06a4be11133786e"},
{"name":"awrence","version":"1.1.1","platform":"ruby","checksum":"9be584c97408ed92d5e1ca11740853646fe270de675f2f8dd44e8233226dfc97"},
{"name":"aws-eventstream","version":"1.2.0","platform":"ruby","checksum":"ffa53482c92880b001ff2fb06919b9bb82fd847cbb0fa244985d2ebb6dd0d1df"},
{"name":"aws-partitions","version":"1.651.0","platform":"ruby","checksum":"61f354049eb2c10bf0aa96b115f7443d181d79ec5508f7a34b8724c4cfa95dda"},
{"name":"aws-partitions","version":"1.658.0","platform":"ruby","checksum":"bba2e21fc87c4e68c7ba5c09e3cd2b81d59ca86111ab236eaf9c5a8ae207b3fc"},
{"name":"aws-sdk-cloudformation","version":"1.41.0","platform":"ruby","checksum":"31e47539719734413671edf9b1a31f8673fbf9688549f50c41affabbcb1c6b26"},
{"name":"aws-sdk-core","version":"3.166.0","platform":"ruby","checksum":"827b82a31f13007fbd3ce78801949019ad3b6fa0c658270d5caa6095cb4945fa"},
{"name":"aws-sdk-core","version":"3.167.0","platform":"ruby","checksum":"d371856ad86f8bff08928059ee09b7cb9bca8ebf36bf5081f12424e4f491b624"},
{"name":"aws-sdk-kms","version":"1.59.0","platform":"ruby","checksum":"6c002ebf8e404625c8338ca12ae69b1329399f9dc1b0ebca474e00ff06700153"},
{"name":"aws-sdk-s3","version":"1.117.1","platform":"ruby","checksum":"76f6dac5baeb2b78616eb34c6af650c1b7a15c1078b169d1b27e8421904c509d"},
{"name":"aws-sigv4","version":"1.5.1","platform":"ruby","checksum":"d68c87fff4ee843b4b92b23c7f31f957f254ec6eb064181f7119124aab8b8bb4"},

View File

@ -185,11 +185,11 @@ GEM
awesome_print (1.9.2)
awrence (1.1.1)
aws-eventstream (1.2.0)
aws-partitions (1.651.0)
aws-partitions (1.658.0)
aws-sdk-cloudformation (1.41.0)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sigv4 (~> 1.1)
aws-sdk-core (3.166.0)
aws-sdk-core (3.167.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
@ -1590,7 +1590,7 @@ DEPENDENCIES
autoprefixer-rails (= 10.2.5.1)
awesome_print
aws-sdk-cloudformation (~> 1)
aws-sdk-core (~> 3.166.0)
aws-sdk-core (~> 3.167.0)
aws-sdk-s3 (~> 1.117.1)
babosa (~> 1.0.4)
base32 (~> 0.3.0)
@ -1869,4 +1869,4 @@ DEPENDENCIES
yajl-ruby (~> 1.4.3)
BUNDLED WITH
2.3.24
2.3.25

View File

@ -612,7 +612,7 @@ export default {
>
<alert-settings-form-help-block
:message="viewCredentialsHelpMsg"
link="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
link="https://docs.gitlab.com/ee/operations/incident_management/integrations.html#configuration"
/>
<gl-form-group id="integration-webhook">

View File

@ -92,6 +92,9 @@ export default {
disableModalSubmit() {
return this.deleteConfirmText !== this.agent.name;
},
containerTabIndex() {
return this.canAdminCluster ? -1 : 0;
},
},
methods: {
async deleteAgent() {
@ -156,8 +159,8 @@ export default {
<div>
<div
v-gl-tooltip="deleteButtonTooltip"
class="gl-display-inline-block"
tabindex="-1"
:tabindex="containerTabIndex"
class="cluster-button-container gl-rounded-base gl-display-inline-block"
data-testid="delete-agent-button-tooltip"
>
<gl-button

View File

@ -132,7 +132,7 @@
"$ref": "#/definitions/exists"
},
"variables": {
"$ref": "#/definitions/variables"
"$ref": "#/definitions/rulesVariables"
},
"when": {
"type": "string",
@ -690,7 +690,7 @@
"$ref": "#/definitions/exists"
},
"variables": {
"$ref": "#/definitions/variables"
"$ref": "#/definitions/rulesVariables"
},
"when": {
"$ref": "#/definitions/when"
@ -744,6 +744,10 @@
"description": {
"type": "string",
"markdownDescription": "Explains what the variable is used for, what the acceptable values are. Variables with `description` are prefilled when running a pipeline manually. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variablesdescription)."
},
"expand": {
"type": "boolean",
"markdownDescription": "If the variable is expandable or not. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variablesexpand)."
}
},
"additionalProperties": false
@ -753,6 +757,49 @@
"additionalProperties": false
}
},
"jobVariables": {
"markdownDescription": "Defines variables for a job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variables).",
"type": "object",
"patternProperties": {
".*": {
"oneOf": [
{
"type": [
"string",
"number"
]
},
{
"type": "object",
"properties": {
"value": {
"type": "string"
},
"expand": {
"type": "boolean",
"markdownDescription": "Defines if the variable is expandable or not. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variablesexpand)."
}
},
"additionalProperties": false
}
]
},
"additionalProperties": false
}
},
"rulesVariables": {
"markdownDescription": "Defines variables for a rule result. [Learn More](https://docs.gitlab.com/ee/ci/yaml/index.html#rulesvariables).",
"type": "object",
"patternProperties": {
".*": {
"type": [
"string",
"number"
]
},
"additionalProperties": false
}
},
"if": {
"type": "string",
"markdownDescription": "Expression to evaluate whether additional attributes should be provided to the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rulesif)."
@ -795,19 +842,6 @@
"type": "string"
}
},
"variables": {
"markdownDescription": "Defines environment variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variables).",
"type": "object",
"patternProperties": {
".*": {
"type": [
"string",
"number"
]
},
"additionalProperties": false
}
},
"timeout": {
"type": "string",
"markdownDescription": "Allows you to configure a timeout for a specific job (e.g. `1 minute`, `1h 30m 12s`). [Learn More](https://docs.gitlab.com/ee/ci/yaml/index.html#timeout).",
@ -1170,7 +1204,7 @@
"$ref": "#/definitions/rules"
},
"variables": {
"$ref": "#/definitions/variables"
"$ref": "#/definitions/jobVariables"
},
"cache": {
"$ref": "#/definitions/cache"

View File

@ -9,7 +9,7 @@ Vue.use(VueRouter);
export function createRouter(fullPath) {
return new VueRouter({
routes,
routes: routes(),
mode: 'history',
base: joinPaths(fullPath, '-', 'work_items'),
});

View File

@ -1,13 +1,22 @@
export const routes = [
{
path: '/new',
name: 'createWorkItem',
component: () => import('../pages/create_work_item.vue'),
},
{
path: '/:id',
name: 'workItem',
component: () => import('../pages/work_item_root.vue'),
props: true,
},
];
function getRoutes() {
const routes = [
{
path: '/:id',
name: 'workItem',
component: () => import('../pages/work_item_root.vue'),
props: true,
},
];
if (gon.features?.workItemsMvc2) {
routes.unshift({
path: '/new',
name: 'createWorkItem',
component: () => import('../pages/create_work_item.vue'),
});
}
return routes;
}
export const routes = getRoutes;

View File

@ -20,3 +20,7 @@
min-height: 372px;
}
}
.cluster-button-container:focus-within {
@include gl-focus;
}

View File

@ -88,6 +88,10 @@ module Integrations
param_values = return_value[:integration]
if param_values.is_a?(ActionController::Parameters)
if action_name == 'update' && integration.chat? && param_values['webhook'] == BaseChatNotification::SECRET_MASK
param_values.delete('webhook')
end
integration.secret_fields.each do |param|
param_values.delete(param) if param_values[param].blank?
end

View File

@ -19,9 +19,7 @@ module RepositoryStorageMovable
inclusion: { in: ->(_) { Gitlab.config.repositories.storages.keys } }
validate :container_repository_writable, on: :create
default_value_for(:destination_storage_name, allows_nil: false) do
Repository.pick_storage_shard
end
attribute :destination_storage_name, default: -> { Repository.pick_storage_shard }
state_machine initial: :initial do
event :schedule do

View File

@ -589,6 +589,10 @@ class Integration < ApplicationRecord
false
end
def chat?
category == :chat
end
private
# Ancestors sorted by hierarchy depth in bottom-top order.

View File

@ -22,6 +22,8 @@ module Integrations
MATCH_ALL_LABELS = 'match_all'
].freeze
SECRET_MASK = '************'
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel, :branches_to_be_notified, :labels_to_be_notified, :labels_to_be_notified_behavior
@ -71,7 +73,7 @@ module Integrations
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}", required: true }.freeze,
{ type: 'text', name: 'webhook', help: "#{webhook_help}", required: true }.freeze,
{ type: 'text', name: 'username', placeholder: 'GitLab-integration' }.freeze,
{ type: 'checkbox', name: 'notify_only_broken_pipelines', help: 'Do not send notifications for successful pipelines.' }.freeze,
{
@ -147,7 +149,7 @@ module Integrations
raise NotImplementedError
end
def webhook_placeholder
def webhook_help
raise NotImplementedError
end

View File

@ -10,8 +10,7 @@ module Integrations
field :webhook,
section: SECTION_TYPE_CONNECTION,
placeholder: 'https://discordapp.com/api/webhooks/…',
help: 'URL to the webhook for the Discord channel.',
help: 'e.g. https://discordapp.com/api/webhooks/…',
required: true
field :notify_only_broken_pipelines,

View File

@ -22,10 +22,6 @@ module Integrations
def default_channel_placeholder
end
def webhook_placeholder
'https://chat.googleapis.com/v1/spaces…'
end
def self.supported_events
%w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
@ -33,7 +29,7 @@ module Integrations
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}" },
{ type: 'text', name: 'webhook', help: 'https://chat.googleapis.com/v1/spaces…' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{
type: 'select',

View File

@ -25,7 +25,7 @@ module Integrations
'my-channel'
end
def webhook_placeholder
def webhook_help
'http://mattermost.example.com/hooks/'
end

View File

@ -18,10 +18,6 @@ module Integrations
'<p>Use this service to send notifications about events in GitLab projects to your Microsoft Teams channels. <a href="https://docs.gitlab.com/ee/user/project/integrations/microsoft_teams.html" target="_blank" rel="noopener noreferrer">How do I configure this integration?</a></p>'
end
def webhook_placeholder
'https://outlook.office.com/webhook/…'
end
def default_channel_placeholder
end
@ -32,7 +28,7 @@ module Integrations
def default_fields
[
{ type: 'text', section: SECTION_TYPE_CONNECTION, name: 'webhook', required: true, placeholder: "#{webhook_placeholder}" },
{ type: 'text', section: SECTION_TYPE_CONNECTION, name: 'webhook', help: 'https://outlook.office.com/webhook/…', required: true },
{
type: 'checkbox',
section: SECTION_TYPE_CONFIGURATION,

View File

@ -36,7 +36,7 @@ module Integrations
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "https://api.pumble.com/workspaces/x/...", required: true },
{ type: 'text', name: 'webhook', help: 'https://api.pumble.com/workspaces/x/...', required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{
type: 'select',

View File

@ -16,8 +16,8 @@ module Integrations
'slack'
end
override :webhook_placeholder
def webhook_placeholder
override :webhook_help
def webhook_help
'https://hooks.slack.com/services/…'
end
end

View File

@ -29,7 +29,7 @@ module Integrations
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "https://yourcircuit.com/rest/v2/webhooks/incoming/…", required: true },
{ type: 'text', name: 'webhook', help: 'https://yourcircuit.com/rest/v2/webhooks/incoming/…', required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{
type: 'select',

View File

@ -29,7 +29,7 @@ module Integrations
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "https://api.ciscospark.com/v1/webhooks/incoming/...", required: true },
{ type: 'text', name: 'webhook', help: 'https://api.ciscospark.com/v1/webhooks/incoming/...', required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{
type: 'select',

View File

@ -736,6 +736,10 @@ class ProjectPolicy < BasePolicy
enable :read_work_item
end
rule { can?(:read_merge_request) }.policy do
enable :read_vulnerability_merge_request_link
end
rule { can?(:developer_access) }.policy do
enable :read_security_configuration
end

View File

@ -3,12 +3,25 @@
class DetailedStatusEntity < Grape::Entity
include RequestAwareEntity
expose :icon, :text, :label, :group
expose :status_tooltip, as: :tooltip
expose :has_details?, as: :has_details
expose :details_path
expose :icon, documentation: { type: 'string', example: 'status_success' }
expose :text, documentation: { type: 'string', example: 'passed' }
expose :label, documentation: { type: 'string', example: 'passed' }
expose :group, documentation: { type: 'string', example: 'success' }
expose :status_tooltip, as: :tooltip, documentation: { type: 'string', example: 'passed' }
expose :has_details?, as: :has_details, documentation: { type: 'boolean', example: true }
expose :details_path, documentation: { type: 'string', example: '/test-group/test-project/-/pipelines/287' }
expose :illustration do |status|
expose :illustration, documentation: {
type: 'object',
example: <<~JSON
{
"image": "illustrations/job_not_triggered.svg",
"size": "svg-306",
"title": "This job has not been triggered yet",
"content": "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
}
JSON
} do |status|
illustration = {
image: ActionController::Base.helpers.image_path(status.illustration[:image])
}
@ -19,15 +32,17 @@ class DetailedStatusEntity < Grape::Entity
# ignored
end
expose :favicon do |status|
expose :favicon,
documentation: { type: 'string',
example: '/assets/ci_favicons/favicon_status_success.png' } do |status|
Gitlab::Favicon.status_overlay(status.favicon)
end
expose :action, if: -> (status, _) { status.has_action? } do
expose :action_icon, as: :icon
expose :action_title, as: :title
expose :action_path, as: :path
expose :action_method, as: :method
expose :action_button_title, as: :button_title
expose :action_icon, as: :icon, documentation: { type: 'string', example: 'cancel' }
expose :action_title, as: :title, documentation: { type: 'string', example: 'Cancel' }
expose :action_path, as: :path, documentation: { type: 'string', example: '/namespace1/project1/-/jobs/2/cancel' }
expose :action_method, as: :method, documentation: { type: 'string', example: 'post' }
expose :action_button_title, as: :button_title, documentation: { type: 'string', example: 'Cancel this job' }
end
end

View File

@ -22,6 +22,8 @@ module Integrations
'true'
elsif field[:type] == 'checkbox'
ActiveRecord::Type::Boolean.new.deserialize(value).to_s
elsif field[:name] == 'webhook' && integration.chat?
BaseChatNotification::SECRET_MASK if value.present?
else
value
end

View File

@ -3,15 +3,20 @@
class TestCaseEntity < Grape::Entity
include API::Helpers::RelatedResourcesHelpers
expose :status
expose :name, default: "(No name)"
expose :classname
expose :file
expose :execution_time
expose :system_output
expose :stack_trace
expose :recent_failures
expose :attachment_url, if: -> (*) { can_read_screenshots? } do |test_case|
expose :status, documentation: { type: 'string', example: 'success' }
expose :name, default: "(No name)",
documentation: { type: 'string', example: 'Security Reports can create an auto-remediation MR' }
expose :classname, documentation: { type: 'string', example: 'vulnerability_management_spec' }
expose :file, documentation: { type: 'string', example: './spec/test_spec.rb' }
expose :execution_time, documentation: { type: 'integer', example: 180 }
expose :system_output, documentation: { type: 'string', example: 'Failure/Error: is_expected.to eq(3)' }
expose :stack_trace, documentation: { type: 'string', example: 'Failure/Error: is_expected.to eq(3)' }
expose :recent_failures, documentation: { example: { count: 3, base_branch: 'develop' } }
expose(
:attachment_url,
if: -> (*) { can_read_screenshots? },
documentation: { type: 'string', example: 'http://localhost/namespace1/project1/-/jobs/1/artifacts/file/some/path.png' }
) do |test_case|
expose_url(test_case.attachment_url)
end

View File

@ -1,15 +1,15 @@
# frozen_string_literal: true
class TestReportEntity < Grape::Entity
expose :total_time
expose :total_count
expose :total_time, documentation: { type: 'integer', example: 180 }
expose :total_count, documentation: { type: 'integer', example: 1 }
expose :success_count
expose :failed_count
expose :skipped_count
expose :error_count
expose :success_count, documentation: { type: 'integer', example: 1 }
expose :failed_count, documentation: { type: 'integer', example: 0 }
expose :skipped_count, documentation: { type: 'integer', example: 0 }
expose :error_count, documentation: { type: 'integer', example: 0 }
expose :test_suites, using: TestSuiteEntity do |report|
expose :test_suites, using: TestSuiteEntity, documentation: { is_array: true } do |report|
report.test_suites.values
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class TestReportSummaryEntity < Grape::Entity
expose :total
expose :total, documentation: { type: 'integer', example: 3363 }
expose :test_suites, using: TestSuiteSummaryEntity do |summary|
summary.test_suites.values

View File

@ -1,18 +1,19 @@
# frozen_string_literal: true
class TestSuiteEntity < Grape::Entity
expose :name
expose :total_time
expose :total_count
expose :name, documentation: { type: 'string', example: 'test' }
expose :total_time, documentation: { type: 'integer', example: 1904 }
expose :total_count, documentation: { type: 'integer', example: 3363 }
expose :success_count
expose :failed_count
expose :skipped_count
expose :error_count
expose :success_count, documentation: { type: 'integer', example: 3351 }
expose :failed_count, documentation: { type: 'integer', example: 0 }
expose :skipped_count, documentation: { type: 'integer', example: 12 }
expose :error_count, documentation: { type: 'integer', example: 0 }
with_options if: -> (_, opts) { opts[:details] } do |test_suite|
expose :suite_error
expose :test_cases, using: TestCaseEntity do |test_suite|
expose :suite_error,
documentation: { type: 'string', example: 'JUnit XML parsing failed: 1:1: FATAL: Document is empty' }
expose :test_cases, using: TestCaseEntity, documentation: { is_array: true } do |test_suite|
test_suite.suite_error ? [] : test_suite.sorted.test_cases.values.flat_map(&:values)
end
end

View File

@ -1,9 +1,10 @@
# frozen_string_literal: true
class TestSuiteSummaryEntity < TestSuiteEntity
expose :build_ids do |summary|
expose :build_ids, documentation: { type: 'integer', is_array: true, example: [66004] } do |summary|
summary.build_ids
end
expose :suite_error
expose :suite_error,
documentation: { type: 'string', example: 'JUnit XML parsing failed: 1:1: FATAL: Document is empty' }
end

View File

@ -46,7 +46,6 @@ module MergeRequests
attr_reader :merge_request, :params, :results
def run_check(check)
return check.execute unless Feature.enabled?(:mergeability_caching, merge_request.project)
return check.execute unless check.cacheable?
cached_result = cached_results.read(merge_check: check)

View File

@ -1,6 +1,6 @@
- referenced_users = local_assigns.fetch(:referenced_users, nil)
- if defined?(@merge_request) && @merge_request.discussion_locked?
- if @merge_request&.discussion_locked?
.issuable-note-warning
= sprite_icon('lock', css_class: 'icon')
%span

View File

@ -1,8 +0,0 @@
---
name: mergeability_caching
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68312
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340810
milestone: '14.4'
type: development
group: group::code review
default_enabled: false

View File

@ -10,6 +10,9 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
options:
enabled: true
distribution:
- ee
- ce

View File

@ -10,6 +10,10 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
options:
enabled: true
keep_n: 1
distribution:
- ee
- ce

View File

@ -10,6 +10,10 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
options:
enabled: true
keep_n: 5
distribution:
- ee
- ce

View File

@ -10,6 +10,10 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
options:
enabled: true
keep_n: 10
distribution:
- ee
- ce

View File

@ -10,6 +10,10 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
options:
enabled: true
keep_n: 25
distribution:
- ee
- ce

View File

@ -10,6 +10,10 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
options:
enabled: true
keep_n: 50
distribution:
- ee
- ce

View File

@ -10,6 +10,10 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
options:
enabled: true
keep_n: 100
distribution:
- ee
- ce

View File

@ -11,6 +11,10 @@ value_type: number
status: active
time_frame: all
data_source: database
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
options:
enabled: true
keep_n: null
distribution:
- ee
- ce

View File

@ -4,37 +4,22 @@ group: Compliance
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Audit Events **(PREMIUM)**
# Audit events **(PREMIUM)**
GitLab offers a way to view the changes made within the GitLab server for owners and administrators
on a [paid plan](https://about.gitlab.com/pricing/).
Use audit events to track important events, including who performed the related action and when.
You can use audit events to track, for example:
GitLab system administrators can also view all audit events by accessing the [`audit_json.log` file](logs/index.md#audit_jsonlog).
The JSON audit log does not include events that are [only streamed](../development/audit_event_guide/index.md#event-streaming).
- Who changed the permission level of a particular user for a GitLab project, and when.
- Who added a new user or removed a user, and when.
You can:
The GitLab API, database, and `audit_json.log` record many audit events. Some audit events are only available through
[streaming audit events](audit_event_streaming.md).
- Generate an [audit report](audit_reports.md) of audit events.
- [Stream audit events](audit_event_streaming.md) to an external endpoint.
You can also generate an [audit report](audit_reports.md) of audit events.
## Overview
**Audit Events** is a tool for GitLab owners and administrators
to track important events such as who performed certain actions and the
time they happened. For example, these actions could be a change to a user
permission level, who added a new user, or who removed a user.
## Use cases
- Check who changed the permission level of a particular
user for a GitLab project.
- Track which users have access to a certain group of projects
in GitLab, and who gave them that permission level.
## Retention policy
There is no retention policy in place for audit events.
See the [Specify a retention period for audit events](https://gitlab.com/groups/gitlab-org/-/epics/7917) for more information.
NOTE:
You can't configure a retention policy for audit events, but epic
[7917](https://gitlab.com/groups/gitlab-org/-/epics/7917) proposes to change this.
## List of events

View File

@ -4255,6 +4255,38 @@ variables:
- A global variable defined with `value` but no `description` behaves the same as
[`variables`](#variables).
### `variables:expand`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353991) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_raw_variables_in_yaml_config`. Disabled by default.
Use the `expand` keyword to configure a variable to be expandable or not.
**Keyword type**: Global and job keyword. You can use it at the global level, and also at the job level.
**Possible inputs**:
- `true` (default): The variable is expandable.
- `false`: The variable is not expandable.
**Example of `variables:expand`**:
```yaml
variables:
VAR1: value1
VAR2: value2 $VAR1
VAR3:
value: value3 $VAR1
expand: false
```
- The result of `VAR2` is `value2 value1`.
- The result of `VAR3` is `value3 $VAR1`.
**Additional details**:
- The `expand` keyword can only be used with the global and job-level `variables` keywords.
You can't use it with [`rules:variables`](#rulesvariables) or [`workflow:rules:variables`](#workflowrulesvariables).
### `when`
Use `when` to configure the conditions for when jobs run. If not defined in a job,

View File

@ -17,6 +17,8 @@ Tutorials are learning aids that complement our core documentation.
They do not introduce new features.
Always use the primary [topic types](index.md) to document new features.
## Tutorial format
Tutorials should be in this format:
```markdown
@ -54,6 +56,9 @@ To do step 2:
1. Another step.
```
An example of a tutorial that follows this format is
[Tutorial: Make your first Git commit](../../../tutorials/make_your_first_git_commit.md).
## Tutorial page title
Start the page title with `Tutorial:` followed by an active verb, like `Tutorial: Create a website`.

View File

@ -30,7 +30,7 @@ Assumptions:
1. `Security::StoreScansWorker` is called and it schedules `Security::StoreScansService`.
1. `Security::StoreScansService` calls `Security::StoreGroupedScansService`.
1. `Security::StoreGroupedScansService` calls `Security::StoreScanService`.
1. `Security::StoreScanService` calls `Security::StoreFindingsMetadataService`.
1. `Security::StoreScanService` calls `Security::StoreFindingsService`.
1. At this point we have `Security::Finding` objects **only**.
At this point, the following things can happen to the `Security::Finding`:

View File

@ -4,7 +4,7 @@ group: Tutorials
info: For assistance with this tutorial, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
---
# Use GitLab to run an Agile iteration
# Tutorial: Use GitLab to run an Agile iteration
To run an Agile development iteration in GitLab, you use multiple GitLab features
that work together.

View File

@ -53,7 +53,7 @@ CI/CD pipelines are used to automatically build, test, and deploy your code.
| Topic | Description | Good for beginners |
|-------|-------------|--------------------|
| [Tutorial: Create and run your first GitLab CI/CD pipeline](../ci/quick_start/index.md) | Create a `.gitlab-ci.yml` file and start a pipeline. | **{star}** |
| [Create and run your first GitLab CI/CD pipeline](../ci/quick_start/index.md) | Create a `.gitlab-ci.yml` file and start a pipeline. | **{star}** |
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Get started: Learn about CI/CD](https://www.youtube.com/watch?v=sIegJaLy2ug) (9m 02s) | Learn about the `.gitlab-ci.yml` file and how it's used. | **{star}** |
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [CI deep dive](https://www.youtube.com/watch?v=ZVUbmVac-m8&list=PL05JrBw4t0KorkxIFgZGnzzxjZRCGROt_&index=27) (22m 51s) | Take a closer look at pipelines and continuous integration concepts. | |
| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [CD deep dive](https://www.youtube.com/watch?v=Cn0rzND-Yjw&list=PL05JrBw4t0KorkxIFgZGnzzxjZRCGROt_&index=10) (47m 54s) | Learn about deploying in GitLab. | |

View File

@ -4,7 +4,7 @@ group: Tutorials
info: For assistance with this tutorial, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
---
# Make your first Git commit
# Tutorial: Make your first Git commit
This tutorial is going to teach you a little bit about how Git works. It walks
you through the steps of creating your own project, editing a file, and

View File

@ -13,7 +13,7 @@ time_frame: <%= time_frame %>
data_source:
data_category: optional
instrumentation_class: <%= class_name %>
performance_indicator_type:
performance_indicator_type: []
distribution:
<%= distribution %>
tier:

View File

@ -182,6 +182,7 @@ module API
mount ::API::Ci::ResourceGroups
mount ::API::Ci::Runner
mount ::API::Ci::Runners
mount ::API::Ci::Pipelines
mount ::API::Ci::Variables
mount ::API::Clusters::AgentTokens
mount ::API::Clusters::Agents
@ -251,7 +252,6 @@ module API
mount ::API::Branches
mount ::API::Ci::JobArtifacts
mount ::API::Ci::PipelineSchedules
mount ::API::Ci::Pipelines
mount ::API::Ci::SecureFiles
mount ::API::Ci::Triggers
mount ::API::CommitStatuses

View File

@ -10,12 +10,17 @@ module API
before { authenticate_non_get! }
params do
requires :id, type: String, desc: 'The project ID'
requires :id, type: String, desc: 'The project ID or URL-encoded path', documentation: { example: 11 }
end
resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get all Pipelines of the project' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Ci::PipelineBasic
success status: 200, model: Entities::Ci::PipelineBasic
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' }
]
is_array true
end
helpers do
@ -31,27 +36,39 @@ module API
else
['unknown']
end
}
},
documentation: { example: %w[pending running] }
end
end
params do
use :pagination
optional :scope, type: String, values: %w[running pending finished branches tags],
desc: 'The scope of pipelines'
desc: 'The scope of pipelines',
documentation: { example: 'pending' }
optional :status, type: String, values: ::Ci::HasStatus::AVAILABLE_STATUSES,
desc: 'The status of pipelines'
optional :ref, type: String, desc: 'The ref of pipelines'
optional :sha, type: String, desc: 'The sha of pipelines'
optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations'
optional :username, type: String, desc: 'The username of the user who triggered pipelines'
optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ'
desc: 'The status of pipelines',
documentation: { example: 'pending' }
optional :ref, type: String, desc: 'The ref of pipelines',
documentation: { example: 'develop' }
optional :sha, type: String, desc: 'The sha of pipelines',
documentation: { example: 'a91957a858320c0e17f3a0eca7cfacbff50ea29a' }
optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations',
documentation: { example: false }
optional :username, type: String, desc: 'The username of the user who triggered pipelines',
documentation: { example: 'root' }
optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ',
documentation: { example: '2015-12-24T15:51:21.880Z' }
optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ',
documentation: { example: '2015-12-24T15:51:21.880Z' }
optional :order_by, type: String, values: ::Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id',
desc: 'Order pipelines'
desc: 'Order pipelines',
documentation: { example: 'status' }
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Sort pipelines'
optional :source, type: String, values: ::Ci::Pipeline.sources.keys
desc: 'Sort pipelines',
documentation: { example: 'asc' }
optional :source, type: String, values: ::Ci::Pipeline.sources.keys,
documentation: { example: 'push' }
end
get ':id/pipelines', urgency: :low, feature_category: :continuous_integration do
authorize! :read_pipeline, user_project
@ -63,13 +80,20 @@ module API
desc 'Create a new pipeline' do
detail 'This feature was introduced in GitLab 8.14'
success Entities::Ci::Pipeline
success status: 201, model: Entities::Ci::Pipeline
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
end
params do
requires :ref, type: String, desc: 'Reference'
requires :ref, type: String, desc: 'Reference',
documentation: { example: 'develop' }
optional :variables, type: Array, desc: 'Array of variables available in the pipeline' do
optional :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
optional :key, type: String, desc: 'The key of the variable', documentation: { example: 'UPLOAD_TO_S3' }
optional :value, type: String, desc: 'The value of the variable', documentation: { example: 'true' }
optional :variable_type, type: String, values: ::Ci::PipelineVariable.variable_types.keys, default: 'env_var', desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
end
end
@ -93,12 +117,18 @@ module API
end
end
desc 'Gets a the latest pipeline for the project branch' do
desc 'Gets the latest pipeline for the project branch' do
detail 'This feature was introduced in GitLab 12.3'
success Entities::Ci::Pipeline
success status: 200, model: Entities::Ci::Pipeline
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
end
params do
optional :ref, type: String, desc: 'branch ref of pipeline'
optional :ref, type: String, desc: 'Branch ref of pipeline. Uses project default branch if not specified.',
documentation: { example: 'develop' }
end
get ':id/pipelines/latest', urgency: :low, feature_category: :continuous_integration do
authorize! :read_pipeline, latest_pipeline
@ -108,10 +138,15 @@ module API
desc 'Gets a specific pipeline for the project' do
detail 'This feature was introduced in GitLab 8.11'
success Entities::Ci::Pipeline
success status: 200, model: Entities::Ci::Pipeline
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
end
get ':id/pipelines/:pipeline_id', urgency: :low, feature_category: :continuous_integration do
authorize! :read_pipeline, pipeline
@ -120,10 +155,16 @@ module API
end
desc 'Get pipeline jobs' do
success Entities::Ci::Job
success status: 200, model: Entities::Ci::Job
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
is_array true
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
optional :include_retried, type: Boolean, default: false, desc: 'Includes retried jobs'
use :optional_scope
use :pagination
@ -144,10 +185,16 @@ module API
end
desc 'Get pipeline bridge jobs' do
success Entities::Ci::Bridge
success status: 200, model: Entities::Ci::Bridge
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
is_array true
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
use :optional_scope
use :pagination
end
@ -167,10 +214,16 @@ module API
desc 'Gets the variables for a given pipeline' do
detail 'This feature was introduced in GitLab 11.11'
success Entities::Ci::Variable
success status: 200, model: Entities::Ci::Variable
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
is_array true
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
end
get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring, urgency: :low do
authorize! :read_pipeline_variable, pipeline
@ -180,10 +233,15 @@ module API
desc 'Gets the test report for a given pipeline' do
detail 'This feature was introduced in GitLab 13.0.'
success TestReportEntity
success status: 200, model: TestReportEntity
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
end
get ':id/pipelines/:pipeline_id/test_report', feature_category: :code_testing, urgency: :low do
authorize! :read_build, pipeline
@ -193,10 +251,15 @@ module API
desc 'Gets the test report summary for a given pipeline' do
detail 'This feature was introduced in GitLab 14.2'
success TestReportSummaryEntity
success status: 200, model: TestReportSummaryEntity
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
end
get ':id/pipelines/:pipeline_id/test_report_summary', feature_category: :code_testing do
authorize! :read_build, pipeline
@ -209,7 +272,7 @@ module API
http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']]
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
end
delete ':id/pipelines/:pipeline_id', urgency: :low, feature_category: :continuous_integration do
authorize! :destroy_pipeline, pipeline
@ -223,10 +286,15 @@ module API
desc 'Retry builds in the pipeline' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Ci::Pipeline
success status: 201, model: Entities::Ci::Pipeline
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
end
post ':id/pipelines/:pipeline_id/retry', urgency: :low, feature_category: :continuous_integration do
authorize! :update_pipeline, pipeline
@ -242,10 +310,15 @@ module API
desc 'Cancel all builds in the pipeline' do
detail 'This feature was introduced in GitLab 8.11.'
success Entities::Ci::Pipeline
success status: 200, model: Entities::Ci::Pipeline
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
end
params do
requires :pipeline_id, type: Integer, desc: 'The pipeline ID'
requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 }
end
post ':id/pipelines/:pipeline_id/cancel', urgency: :low, feature_category: :continuous_integration do
authorize! :update_pipeline, pipeline

View File

@ -4,13 +4,21 @@ module API
module Entities
module Ci
class Pipeline < PipelineBasic
expose :before_sha, :tag, :yaml_errors
expose :before_sha, documentation: { type: 'string', example: 'a91957a858320c0e17f3a0eca7cfacbff50ea29a' }
expose :tag, documentation: { type: 'boolean', example: false }
expose :yaml_errors, documentation: { type: 'string', example: "widgets:build: needs 'widgets:test'" }
expose :user, with: Entities::UserBasic
expose :created_at, :updated_at, :started_at, :finished_at, :committed_at
expose :duration
expose :queued_duration
expose :coverage do |pipeline|
expose :created_at, documentation: { type: 'dateTime', example: '2015-12-24T15:51:21.880Z' }
expose :updated_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' }
expose :started_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:30.733Z' }
expose :finished_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' }
expose :committed_at, documentation: { type: 'dateTime', example: '2015-12-24T15:51:21.880Z' }
expose :duration,
documentation: { type: 'integer', desc: 'Time spent running in seconds', example: 127 }
expose :queued_duration,
documentation: { type: 'integer', desc: 'Time spent enqueued in seconds', example: 63 }
expose :coverage, documentation: { type: 'number', format: 'float', example: 98.29 } do |pipeline|
pipeline.present.coverage
end
expose :detailed_status, using: DetailedStatusEntity do |pipeline, options|

View File

@ -94,7 +94,7 @@ module API
end
post ':id/repository_storage_moves' do
storage_move = user_project.repository_storage_moves.build(
declared_params.merge(source_storage_name: user_project.repository_storage)
declared_params.compact.merge(source_storage_name: user_project.repository_storage)
)
if storage_move.schedule

View File

@ -104,7 +104,7 @@ module API
end
post ':id/repository_storage_moves' do
storage_move = user_snippet.repository_storage_moves.build(
declared_params.merge(source_storage_name: user_snippet.repository_storage)
declared_params.compact.merge(source_storage_name: user_snippet.repository_storage)
)
if storage_move.schedule

View File

@ -8,13 +8,15 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
FILE_CLASSES = [
External::File::Remote,
External::File::Template,
External::File::Local,
External::File::Project,
External::File::Remote,
External::File::Template,
External::File::Artifact
].freeze
FILE_SUBKEYS = FILE_CLASSES.map { |f| f.name.demodulize.downcase }.freeze
Error = Class.new(StandardError)
AmbigiousSpecificationError = Class.new(Error)
TooManyIncludesError = Class.new(Error)
@ -120,9 +122,13 @@ module Gitlab
file_class.new(location, context)
end.select(&:matching?)
raise AmbigiousSpecificationError, "Include `#{masked_location(location.to_json)}` needs to match exactly one accessor!" unless matching.one?
matching.first
if matching.one?
matching.first
elsif matching.empty?
raise AmbigiousSpecificationError, "`#{masked_location(location.to_json)}` does not have a valid subkey for include. Valid subkeys are: `#{FILE_SUBKEYS.join('`, `')}`"
else
raise AmbigiousSpecificationError, "Each include must use only one of: `#{FILE_SUBKEYS.join('`, `')}`"
end
end
def verify!(location_object)

View File

@ -21,7 +21,10 @@ module Gitlab
class: self.class.name,
message: MESSAGE,
project_id: project.id,
plan: project.actual_plan_name)
plan: project.actual_plan_name,
project_path: project.path,
jobs_in_alive_pipelines_count: count_jobs_in_alive_pipelines
)
end
def break?

View File

@ -337,17 +337,15 @@ module Gitlab
# rubocop: disable UsageData/LargeTable
base = ::ContainerExpirationPolicy.active
# rubocop: enable UsageData/LargeTable
results[:projects_with_expiration_policy_enabled] = distinct_count(base, :project_id, start: start, finish: finish)
# rubocop: disable UsageData/LargeTable
%i[keep_n cadence older_than].each do |option|
%i[cadence older_than].each do |option|
::ContainerExpirationPolicy.public_send("#{option}_options").keys.each do |value| # rubocop: disable GitlabSecurity/PublicSend
results["projects_with_expiration_policy_enabled_with_#{option}_set_to_#{value}".to_sym] = distinct_count(base.where(option => value), :project_id, start: start, finish: finish)
end
end
# rubocop: enable UsageData/LargeTable
results[:projects_with_expiration_policy_enabled_with_keep_n_unset] = distinct_count(base.where(keep_n: nil), :project_id, start: start, finish: finish)
results[:projects_with_expiration_policy_enabled_with_older_than_unset] = distinct_count(base.where(older_than: nil), :project_id, start: start, finish: finish)
results

View File

@ -11114,9 +11114,6 @@ msgstr ""
msgid "Could not remove %{user} from %{group}. Cannot remove last group owner."
msgstr ""
msgid "Could not remove %{user} from %{group}. User is not a group member."
msgstr ""
msgid "Could not remove the trigger."
msgstr ""
@ -48393,6 +48390,9 @@ msgstr ""
msgid "is already associated to a GitLab Issue. New issue will not be associated."
msgstr ""
msgid "is already linked to this vulnerability"
msgstr ""
msgid "is an invalid IP address range"
msgstr ""

View File

@ -334,6 +334,23 @@ RSpec.describe Projects::Settings::IntegrationsController do
)
end
end
context 'with chat notification integration' do
let_it_be(:integration) { project.create_microsoft_teams_integration(webhook: 'http://webhook.com') }
let(:message) { 'Microsoft Teams notifications settings saved and active.' }
it_behaves_like 'integration update'
context 'with masked token' do
let(:integration_params) { { active: true, webhook: '************' } }
it_behaves_like 'integration update'
it 'does not update the webhook' do
expect(integration.reload.webhook).to eq('http://webhook.com')
end
end
end
end
describe 'as JSON' do

View File

@ -14,7 +14,7 @@ time_frame: 7d
data_source:
data_category: operational
instrumentation_class: Count
performance_indicator_type:
performance_indicator_type: []
distribution:
- ce
# Add here corresponding tiers

View File

@ -14,7 +14,7 @@ time_frame: 7d
data_source:
data_category: optional
instrumentation_class: Count
performance_indicator_type:
performance_indicator_type: []
distribution:
- ee
tier:

View File

@ -15,7 +15,7 @@ time_frame: 7d
data_source:
data_category: optional
instrumentation_class: Count
performance_indicator_type:
performance_indicator_type: []
distribution:
- ce
- ee

View File

@ -37,7 +37,8 @@ import JobWhenYaml from './yaml_tests/positive_tests/job_when.yml';
import ArtifactsNegativeYaml from './yaml_tests/negative_tests/artifacts.yml';
import IncludeNegativeYaml from './yaml_tests/negative_tests/include.yml';
import RulesNegativeYaml from './yaml_tests/negative_tests/rules.yml';
import VariablesNegativeYaml from './yaml_tests/negative_tests/variables.yml';
import VariablesInvalidSyntaxDescYaml from './yaml_tests/negative_tests/variables/invalid_syntax_desc.yml';
import VariablesWrongSyntaxUsageExpand from './yaml_tests/negative_tests/variables/wrong_syntax_usage_expand.yml';
import JobWhenNegativeYaml from './yaml_tests/negative_tests/job_when.yml';
import ProjectPathIncludeEmptyYaml from './yaml_tests/negative_tests/project_path/include/empty.yml';
@ -137,7 +138,8 @@ describe('negative tests', () => {
IncludeNegativeYaml,
JobWhenNegativeYaml,
RulesNegativeYaml,
VariablesNegativeYaml,
VariablesInvalidSyntaxDescYaml,
VariablesWrongSyntaxUsageExpand,
ProjectPathIncludeEmptyYaml,
ProjectPathIncludeInvalidVariableYaml,
ProjectPathIncludeLeadSlashYaml,

View File

@ -0,0 +1,4 @@
variables:
RAW_VAR:
value: Hello $FOO
expand: okay

View File

@ -6,3 +6,13 @@ variables:
description: "A single value variable"
DEPLOY_ENVIRONMENT:
description: "A multi-value variable"
RAW_VAR:
value: "Hello $FOO"
expand: false
rspec:
script: rspec
variables:
RAW_VAR2:
value: "Hello $DEPLOY_ENVIRONMENT"
expand: false

View File

@ -63,6 +63,14 @@ describe('Work items router', () => {
});
};
beforeEach(() => {
window.gon = {
features: {
workItemsMvc2: false,
},
};
});
afterEach(() => {
wrapper.destroy();
window.location.hash = '';
@ -74,7 +82,14 @@ describe('Work items router', () => {
expect(wrapper.findComponent(WorkItemsRoot).exists()).toBe(true);
});
it('does not render create work item page on `/new` route if `workItemsMvc2` feature flag is off', async () => {
await createComponent('/new');
expect(wrapper.findComponent(CreateWorkItem).exists()).toBe(false);
});
it('renders create work item page on `/new` route', async () => {
window.gon.features.workItemsMvc2 = true;
await createComponent('/new');
expect(wrapper.findComponent(CreateWorkItem).exists()).toBe(true);

View File

@ -113,7 +113,19 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
it_behaves_like 'logging config file fetch', 'config_file_fetch_template_content_duration_s', 1
end
context 'when the key is a hash of file and remote' do
context 'when the key is not valid' do
let(:local_file) { 'secret-file.yml' }
let(:values) do
{ include: { invalid: local_file },
image: 'image:1.0' }
end
it 'returns ambigious specification error' do
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, '`{"invalid":"secret-file.yml"}` does not have a valid subkey for include. Valid subkeys are: `local`, `project`, `remote`, `template`, `artifact`')
end
end
context 'when the key is a hash of local and remote' do
let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret-file', 'masked' => true }]) }
let(:local_file) { 'secret-file.yml' }
let(:remote_url) { 'https://gitlab.com/secret-file.yml' }
@ -123,7 +135,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
end
it 'returns ambigious specification error' do
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, 'Include `{"local":"xxxxxxxxxxx.yml","remote":"https://gitlab.com/xxxxxxxxxxx.yml"}` needs to match exactly one accessor!')
expect { subject }.to raise_error(described_class::AmbigiousSpecificationError, 'Each include must use only one of: `local`, `project`, `remote`, `template`, `artifact`')
end
end

View File

@ -484,7 +484,7 @@ RSpec.describe Gitlab::Ci::Config do
it 'raises ConfigError' do
expect { config }.to raise_error(
described_class::ConfigError,
'Include `{"remote":"http://url","local":"/local/file.yml"}` needs to match exactly one accessor!'
/Each include must use only one of/
)
end
end
@ -714,7 +714,7 @@ RSpec.describe Gitlab::Ci::Config do
it 'raises an error' do
expect { config }.to raise_error(
described_class::ConfigError,
/needs to match exactly one accessor!/
/does not have a valid subkey for include/
)
end
end

View File

@ -69,7 +69,9 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::ActiveJobs do
class: described_class.name,
message: described_class::MESSAGE,
project_id: project.id,
plan: default_plan.name
plan: default_plan.name,
project_path: project.path,
jobs_in_alive_pipelines_count: step.send(:count_jobs_in_alive_pipelines)
)
end

View File

@ -1407,7 +1407,7 @@ module Gitlab
context "when an array of wrong keyed object is provided" do
let(:include_content) { [{ yolo: "/local.gitlab-ci.yml" }] }
it_behaves_like 'returns errors', /needs to match exactly one accessor/
it_behaves_like 'returns errors', /does not have a valid subkey for include/
end
context "when an array of mixed typed objects is provided" do
@ -1432,7 +1432,7 @@ module Gitlab
context "when the include type is incorrect" do
let(:include_content) { { name: "/local.gitlab-ci.yml" } }
it_behaves_like 'returns errors', /needs to match exactly one accessor/
it_behaves_like 'returns errors', /does not have a valid subkey for include/
end
end

View File

@ -134,8 +134,9 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
:repair_issue_url | nil
:removed_by_url | 1
:instrumentation_class | 'Metric_Class'
:instrumentation_class | 'metricClass'
:performance_indicator_type | nil
:instrumentation_class | 'Metric_Class'
:instrumentation_class | 'metricClass'
end
with_them do

View File

@ -606,13 +606,12 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
let_it_be(:disabled) { create(:container_expiration_policy, enabled: false) }
let_it_be(:enabled) { create(:container_expiration_policy, enabled: true) }
%i[keep_n cadence older_than].each do |attribute|
%i[cadence older_than].each do |attribute|
ContainerExpirationPolicy.send("#{attribute}_options").keys.each do |value|
let_it_be("container_expiration_policy_with_#{attribute}_set_to_#{value}") { create(:container_expiration_policy, attribute => value) }
end
end
let_it_be('container_expiration_policy_with_keep_n_set_to_null') { create(:container_expiration_policy, keep_n: nil) }
let_it_be('container_expiration_policy_with_older_than_set_to_null') { create(:container_expiration_policy, older_than: nil) }
let(:inactive_policies) { ::ContainerExpirationPolicy.where(enabled: false) }
@ -621,23 +620,14 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
subject { described_class.data[:counts] }
it 'gathers usage data' do
expect(subject[:projects_with_expiration_policy_enabled]).to eq 19
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_unset]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_1]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_5]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_10]).to eq 13
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_25]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_keep_n_set_to_50]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_unset]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_7d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_14d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_30d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_60d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_90d]).to eq 14
expect(subject[:projects_with_expiration_policy_enabled_with_older_than_set_to_90d]).to eq 7
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1d]).to eq 15
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1d]).to eq 8
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_7d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_14d]).to eq 1
expect(subject[:projects_with_expiration_policy_enabled_with_cadence_set_to_1month]).to eq 1

View File

@ -234,6 +234,16 @@ RSpec.describe Integration do
end
end
describe '#chat?' do
it 'is true when integration is chat integration' do
expect(build(:mattermost_integration).chat?).to eq(true)
end
it 'is false when integration is not chat integration' do
expect(build(:integration).chat?).to eq(false)
end
end
describe '.find_or_initialize_non_project_specific_integration' do
let!(:integration_1) { create(:jira_integration, project_id: nil, group_id: group.id) }
let!(:integration_2) { create(:jira_integration, project: project) }

View File

@ -7,7 +7,7 @@ RSpec.describe Integrations::BaseChatNotification do
before do
allow(subject).to receive(:activated?).and_return(true)
allow(subject).to receive(:default_channel_placeholder).and_return('placeholder')
allow(subject).to receive(:webhook_placeholder).and_return('placeholder')
allow(subject).to receive(:webhook_help).and_return('help')
end
it { is_expected.to validate_presence_of :webhook }
@ -280,9 +280,9 @@ RSpec.describe Integrations::BaseChatNotification do
end
end
describe '#webhook_placeholder' do
describe '#webhook_help' do
it 'raises an error' do
expect { subject.webhook_placeholder }.to raise_error(NotImplementedError)
expect { subject.webhook_help }.to raise_error(NotImplementedError)
end
end

View File

@ -3252,14 +3252,6 @@ RSpec.describe MergeRequest, factory_default: :keep do
describe '#mergeable_state?' do
it_behaves_like 'for mergeable_state'
context 'when merge state caching is off' do
before do
stub_feature_flags(mergeability_caching: false)
end
it_behaves_like 'for mergeable_state'
end
end
describe "#public_merge_status" do

View File

@ -13,7 +13,7 @@ RSpec.describe Integrations::FieldEntity do
describe '#as_json' do
context 'with Jira integration' do
let(:integration) { create(:jira_integration) }
let(:integration) { build(:jira_integration) }
context 'with field with type text' do
let(:field) { integration_field('username') }
@ -59,7 +59,7 @@ RSpec.describe Integrations::FieldEntity do
end
context 'with EmailsOnPush integration' do
let(:integration) { create(:emails_on_push_integration, send_from_committer_email: '1') }
let(:integration) { build(:emails_on_push_integration, send_from_committer_email: '1') }
context 'with field with type checkbox' do
let(:field) { integration_field('send_from_committer_email') }
@ -111,6 +111,36 @@ RSpec.describe Integrations::FieldEntity do
end
end
end
context 'with chat integration' do
let(:integration) { build(:mattermost_integration) }
let(:field) { integration_field('webhook') }
it 'exposes correct attributes but masks webhook' do
expected_hash = {
section: nil,
type: 'text',
name: 'webhook',
title: nil,
placeholder: nil,
help: 'http://mattermost.example.com/hooks/',
required: true,
choices: nil,
value: '************',
checkbox_label: nil
}
is_expected.to eq(expected_hash)
end
context 'when webhook was not set' do
let(:integration) { build(:mattermost_integration, webhook: nil) }
it 'does not show the masked webhook' do
expect(subject[:value]).to be_nil
end
end
end
end
def integration_field(name)

View File

@ -397,9 +397,26 @@ RSpec.describe Issues::CloseService do
end
context 'when issue is not confidential' do
let(:expected_payload) do
include(
event_type: 'issue',
object_kind: 'issue',
changes: {
closed_at: { current: kind_of(Time), previous: nil },
state_id: { current: 2, previous: 1 },
updated_at: { current: kind_of(Time), previous: kind_of(Time) }
},
object_attributes: include(
closed_at: kind_of(Time),
state: 'closed',
action: 'close'
)
)
end
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_hooks).with(expected_payload, :issue_hooks)
expect(project).to receive(:execute_integrations).with(expected_payload, :issue_hooks)
described_class.new(project: project, current_user: user).close_issue(issue)
end

View File

@ -391,22 +391,61 @@ RSpec.describe Issues::CreateService do
end
end
it 'executes issue hooks when issue is not confidential' do
opts = { title: 'Title', description: 'Description', confidential: false }
describe 'executing hooks' do
let(:opts) { { title: 'Title', description: 'Description' } }
let(:expected_payload) do
include(
event_type: 'issue',
object_kind: 'issue',
changes: {
author_id: { current: user.id, previous: nil },
created_at: { current: kind_of(Time), previous: nil },
description: { current: opts[:description], previous: nil },
id: { current: kind_of(Integer), previous: nil },
iid: { current: kind_of(Integer), previous: nil },
project_id: { current: project.id, previous: nil },
title: { current: opts[:title], previous: nil },
updated_at: { current: kind_of(Time), previous: nil }
},
object_attributes: include(
opts.merge(
author_id: user.id,
project_id: project.id
)
)
)
end
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks)
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(expected_payload, :issue_hooks)
expect(project).to receive(:execute_integrations).with(expected_payload, :issue_hooks)
described_class.new(project: project, current_user: user, params: opts, spam_params: spam_params).execute
end
described_class.new(project: project, current_user: user, params: opts, spam_params: spam_params).execute
end
it 'executes confidential issue hooks when issue is confidential' do
opts = { title: 'Title', description: 'Description', confidential: true }
context 'when issue is confidential' do
let(:expected_payload) do
include(
event_type: 'confidential_issue',
object_kind: 'issue',
changes: include(
confidential: { current: true, previous: false }
),
object_attributes: include(confidential: true)
)
end
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :confidential_issue_hooks)
before do
opts[:confidential] = true
end
described_class.new(project: project, current_user: user, params: opts, spam_params: spam_params).execute
it 'executes confidential issue hooks' do
expect(project).to receive(:execute_hooks).with(expected_payload, :confidential_issue_hooks)
expect(project).to receive(:execute_integrations).with(expected_payload, :confidential_issue_hooks)
described_class.new(project: project, current_user: user, params: opts, spam_params: spam_params).execute
end
end
end
context 'after_save callback to store_mentions' do

View File

@ -228,18 +228,48 @@ RSpec.describe Issues::MoveService do
end
context 'project issue hooks' do
let!(:hook) { create(:project_hook, project: old_project, issues_events: true) }
let_it_be(:old_project_hook) { create(:project_hook, project: old_project, issues_events: true) }
let_it_be(:new_project_hook) { create(:project_hook, project: new_project, issues_events: true) }
it 'executes project issue hooks' do
allow_next_instance_of(WebHookService) do |instance|
allow(instance).to receive(:execute)
let(:expected_new_project_hook_payload) do
hash_including(
event_type: 'issue',
object_kind: 'issue',
object_attributes: include(
project_id: new_project.id,
state: 'opened',
action: 'open'
)
)
end
let(:expected_old_project_hook_payload) do
hash_including(
event_type: 'issue',
object_kind: 'issue',
changes: {
state_id: { current: 2, previous: 1 },
closed_at: { current: kind_of(Time), previous: nil },
updated_at: { current: kind_of(Time), previous: kind_of(Time) }
},
object_attributes: include(
id: old_issue.id,
closed_at: kind_of(Time),
state: 'closed',
action: 'close'
)
)
end
it 'executes project issue hooks for both projects' do
expect_next_instance_of(WebHookService, new_project_hook, expected_new_project_hook_payload, 'issue_hooks') do |service|
expect(service).to receive(:async_execute).once
end
expect_next_instance_of(WebHookService, old_project_hook, expected_old_project_hook_payload, 'issue_hooks') do |service|
expect(service).to receive(:async_execute).once
end
# Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
# but since the entire spec run takes place in a transaction, we never
# actually get to the `after_commit` hook that queues these jobs.
expect { move_service.execute(old_issue, new_project) }
.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
move_service.execute(old_issue, new_project)
end
end

View File

@ -85,9 +85,25 @@ RSpec.describe Issues::ReopenService do
end
context 'when issue is not confidential' do
let(:expected_payload) do
include(
event_type: 'issue',
object_kind: 'issue',
changes: {
closed_at: { current: nil, previous: kind_of(Time) },
state_id: { current: 1, previous: 2 },
updated_at: { current: kind_of(Time), previous: kind_of(Time) }
},
object_attributes: include(
state: 'opened',
action: 'reopen'
)
)
end
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_hooks).with(expected_payload, :issue_hooks)
expect(project).to receive(:execute_integrations).with(expected_payload, :issue_hooks)
execute
end

View File

@ -543,7 +543,7 @@ RSpec.describe Issues::UpdateService, :mailer do
end
end
context 'when decription is not changed' do
context 'when description is not changed' do
it 'does not trigger GraphQL description updated subscription' do
expect(GraphqlTriggers).not_to receive(:issuable_description_updated)
@ -1402,7 +1402,7 @@ RSpec.describe Issues::UpdateService, :mailer do
end
end
include_examples 'issuable update service' do
it_behaves_like 'issuable update service' do
let(:open_issuable) { issue }
let(:closed_issuable) { create(:closed_issue, project: project) }
end

View File

@ -104,18 +104,6 @@ RSpec.describe MergeRequests::Mergeability::RunChecksService, :clean_gitlab_redi
expect(execute.success?).to eq(true)
end
end
context 'when mergeability_caching is turned off' do
before do
stub_feature_flags(mergeability_caching: false)
end
it 'does not call the results store' do
expect(Gitlab::MergeRequests::Mergeability::ResultsStore).not_to receive(:new)
expect(execute.success?).to eq(true)
end
end
end
end

View File

@ -1148,7 +1148,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
end
include_examples 'issuable update service' do
it_behaves_like 'issuable update service' do
let(:open_issuable) { merge_request }
let(:closed_issuable) { create(:closed_merge_request, source_project: project) }
end

View File

@ -67,13 +67,6 @@ module UsageDataHelpers
projects_with_repositories_enabled
projects_with_error_tracking_enabled
projects_with_enabled_alert_integrations
projects_with_expiration_policy_enabled
projects_with_expiration_policy_enabled_with_keep_n_unset
projects_with_expiration_policy_enabled_with_keep_n_set_to_1
projects_with_expiration_policy_enabled_with_keep_n_set_to_5
projects_with_expiration_policy_enabled_with_keep_n_set_to_10
projects_with_expiration_policy_enabled_with_keep_n_set_to_25
projects_with_expiration_policy_enabled_with_keep_n_set_to_50
projects_with_expiration_policy_enabled_with_older_than_unset
projects_with_expiration_policy_enabled_with_older_than_set_to_7d
projects_with_expiration_policy_enabled_with_older_than_set_to_14d

View File

@ -3223,7 +3223,6 @@
- './ee/spec/services/security/security_orchestration_policies/sync_opened_merge_requests_service_spec.rb'
- './ee/spec/services/security/security_orchestration_policies/sync_open_merge_requests_head_pipeline_service_spec.rb'
- './ee/spec/services/security/security_orchestration_policies/validate_policy_service_spec.rb'
- './ee/spec/services/security/store_findings_metadata_service_spec.rb'
- './ee/spec/services/security/store_grouped_scans_service_spec.rb'
- './ee/spec/services/security/store_scan_service_spec.rb'
- './ee/spec/services/security/store_scans_service_spec.rb'

View File

@ -6,18 +6,48 @@ RSpec.shared_examples 'issuable update service' do
end
context 'changing state' do
before do
expect(project).to receive(:execute_hooks).once
end
let(:hook_event) { :"#{closed_issuable.class.name.underscore.to_sym}_hooks" }
context 'to reopened' do
it 'executes hooks only once' do
let(:expected_payload) do
include(
changes: include(
state_id: { current: 1, previous: 2 },
updated_at: { current: kind_of(Time), previous: kind_of(Time) }
),
object_attributes: include(
state: 'opened',
action: 'reopen'
)
)
end
it 'executes hooks' do
expect(project).to receive(:execute_hooks).with(expected_payload, hook_event)
expect(project).to receive(:execute_integrations).with(expected_payload, hook_event)
described_class.new(project: project, current_user: user, params: { state_event: 'reopen' }).execute(closed_issuable)
end
end
context 'to closed' do
it 'executes hooks only once' do
let(:expected_payload) do
include(
changes: include(
state_id: { current: 2, previous: 1 },
updated_at: { current: kind_of(Time), previous: kind_of(Time) }
),
object_attributes: include(
state: 'closed',
action: 'close'
)
)
end
it 'executes hooks' do
expect(project).to receive(:execute_hooks).with(expected_payload, hook_event)
expect(project).to receive(:execute_integrations).with(expected_payload, hook_event)
described_class.new(project: project, current_user: user, params: { state_event: 'close' }).execute(open_issuable)
end
end

View File

@ -67,5 +67,13 @@ RSpec.describe 'projects/commit/show.html.haml' do
expect(rendered).to have_content("This commit is part of merge request")
expect(rendered).to have_link(merge_request.to_reference, href: merge_request_url)
end
context 'when merge request is nil' do
let(:merge_request) { nil }
it 'renders the page' do
expect { rendered }.not_to raise_error
end
end
end
end