Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
43e40e8daa
commit
2761b4465b
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Vue.use(VueRouter);
|
|||
|
||||
export function createRouter(fullPath) {
|
||||
return new VueRouter({
|
||||
routes,
|
||||
routes: routes(),
|
||||
mode: 'history',
|
||||
base: joinPaths(fullPath, '-', 'work_items'),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -20,3 +20,7 @@
|
|||
min-height: 372px;
|
||||
}
|
||||
}
|
||||
|
||||
.cluster-button-container:focus-within {
|
||||
@include gl-focus;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -589,6 +589,10 @@ class Integration < ApplicationRecord
|
|||
false
|
||||
end
|
||||
|
||||
def chat?
|
||||
category == :chat
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Ancestors sorted by hierarchy depth in bottom-top order.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ module Integrations
|
|||
'my-channel'
|
||||
end
|
||||
|
||||
def webhook_placeholder
|
||||
def webhook_help
|
||||
'http://mattermost.example.com/hooks/'
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -10,6 +10,9 @@ value_type: number
|
|||
status: active
|
||||
time_frame: all
|
||||
data_source: database
|
||||
instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric
|
||||
options:
|
||||
enabled: true
|
||||
distribution:
|
||||
- ee
|
||||
- ce
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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. | |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ time_frame: 7d
|
|||
data_source:
|
||||
data_category: optional
|
||||
instrumentation_class: Count
|
||||
performance_indicator_type:
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ee
|
||||
tier:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ time_frame: 7d
|
|||
data_source:
|
||||
data_category: optional
|
||||
instrumentation_class: Count
|
||||
performance_indicator_type:
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# invalid variable (unknown keyword is used)
|
||||
variables:
|
||||
FOO:
|
||||
value: BAR
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
variables:
|
||||
RAW_VAR:
|
||||
value: Hello $FOO
|
||||
expand: okay
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue