diff options
| author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-11 12:08:16 +0000 |
|---|---|---|
| committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-11 12:08:16 +0000 |
| commit | 2761b4465bb13e170f0b8b2941d83f356a47eee6 (patch) | |
| tree | 9a34e185671e7a9afe8720c8eafc6a3319a63c97 | |
| parent | 43e40e8daaceafb9b78fde9ac5ce97584a210a90 (diff) | |
| download | gitlab-ce-2761b4465bb13e170f0b8b2941d83f356a47eee6.tar.gz | |
Add latest changes from gitlab-org/gitlab@master
89 files changed, 707 insertions, 296 deletions
diff --git a/.rubocop_todo/performance/method_object_as_block.yml b/.rubocop_todo/performance/method_object_as_block.yml index 1bc82ff05ec..acb1e2d621b 100644 --- a/.rubocop_todo/performance/method_object_as_block.yml +++ b/.rubocop_todo/performance/method_object_as_block.yml @@ -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' diff --git a/.rubocop_todo/rspec/expect_change.yml b/.rubocop_todo/rspec/expect_change.yml index 626ed1390ce..26981055462 100644 --- a/.rubocop_todo/rspec/expect_change.yml +++ b/.rubocop_todo/rspec/expect_change.yml @@ -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' @@ -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' diff --git a/Gemfile.checksum b/Gemfile.checksum index 9dca9488e95..06379a80710 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -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"}, diff --git a/Gemfile.lock b/Gemfile.lock index 36f5c4f00db..1b41829b4e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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 diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue index 6b5aac57f1c..03bc4b825ae 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue @@ -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"> diff --git a/app/assets/javascripts/clusters_list/components/delete_agent_button.vue b/app/assets/javascripts/clusters_list/components/delete_agent_button.vue index 6f2c353a67b..7a028858d10 100644 --- a/app/assets/javascripts/clusters_list/components/delete_agent_button.vue +++ b/app/assets/javascripts/clusters_list/components/delete_agent_button.vue @@ -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 diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 4c2d6ac30bb..45f063a2048 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -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" diff --git a/app/assets/javascripts/work_items/router/index.js b/app/assets/javascripts/work_items/router/index.js index 2b39a298720..777badeb5be 100644 --- a/app/assets/javascripts/work_items/router/index.js +++ b/app/assets/javascripts/work_items/router/index.js @@ -9,7 +9,7 @@ Vue.use(VueRouter); export function createRouter(fullPath) { return new VueRouter({ - routes, + routes: routes(), mode: 'history', base: joinPaths(fullPath, '-', 'work_items'), }); diff --git a/app/assets/javascripts/work_items/router/routes.js b/app/assets/javascripts/work_items/router/routes.js index 95772bbd026..1e3a7e184bb 100644 --- a/app/assets/javascripts/work_items/router/routes.js +++ b/app/assets/javascripts/work_items/router/routes.js @@ -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; diff --git a/app/assets/stylesheets/page_bundles/clusters.scss b/app/assets/stylesheets/page_bundles/clusters.scss index a877ae72e31..4f29ff4b1ad 100644 --- a/app/assets/stylesheets/page_bundles/clusters.scss +++ b/app/assets/stylesheets/page_bundles/clusters.scss @@ -20,3 +20,7 @@ min-height: 372px; } } + +.cluster-button-container:focus-within { + @include gl-focus; +} diff --git a/app/controllers/concerns/integrations/params.rb b/app/controllers/concerns/integrations/params.rb index c3ad9d3dff3..30de4a86bec 100644 --- a/app/controllers/concerns/integrations/params.rb +++ b/app/controllers/concerns/integrations/params.rb @@ -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 diff --git a/app/models/concerns/repository_storage_movable.rb b/app/models/concerns/repository_storage_movable.rb index b7fd52ab305..87ff413f2c1 100644 --- a/app/models/concerns/repository_storage_movable.rb +++ b/app/models/concerns/repository_storage_movable.rb @@ -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 diff --git a/app/models/integration.rb b/app/models/integration.rb index 90dc6b486ab..41278dce22d 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -589,6 +589,10 @@ class Integration < ApplicationRecord false end + def chat? + category == :chat + end + private # Ancestors sorted by hierarchy depth in bottom-top order. diff --git a/app/models/integrations/base_chat_notification.rb b/app/models/integrations/base_chat_notification.rb index c7992e4083c..06d18b7260f 100644 --- a/app/models/integrations/base_chat_notification.rb +++ b/app/models/integrations/base_chat_notification.rb @@ -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 diff --git a/app/models/integrations/discord.rb b/app/models/integrations/discord.rb index d0389b82410..061c491034d 100644 --- a/app/models/integrations/discord.rb +++ b/app/models/integrations/discord.rb @@ -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, diff --git a/app/models/integrations/hangouts_chat.rb b/app/models/integrations/hangouts_chat.rb index 6e7f31aa030..c903e8d9eb8 100644 --- a/app/models/integrations/hangouts_chat.rb +++ b/app/models/integrations/hangouts_chat.rb @@ -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', diff --git a/app/models/integrations/mattermost.rb b/app/models/integrations/mattermost.rb index 60f3b10b80c..dd1c98ee06b 100644 --- a/app/models/integrations/mattermost.rb +++ b/app/models/integrations/mattermost.rb @@ -25,7 +25,7 @@ module Integrations 'my-channel' end - def webhook_placeholder + def webhook_help 'http://mattermost.example.com/hooks/' end diff --git a/app/models/integrations/microsoft_teams.rb b/app/models/integrations/microsoft_teams.rb index 69863f164cd..d6cbe5760e8 100644 --- a/app/models/integrations/microsoft_teams.rb +++ b/app/models/integrations/microsoft_teams.rb @@ -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, diff --git a/app/models/integrations/pumble.rb b/app/models/integrations/pumble.rb index 6c5493c4b65..e08dc6d0f51 100644 --- a/app/models/integrations/pumble.rb +++ b/app/models/integrations/pumble.rb @@ -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', diff --git a/app/models/integrations/slack.rb b/app/models/integrations/slack.rb index ffca0d40f69..89326b8174f 100644 --- a/app/models/integrations/slack.rb +++ b/app/models/integrations/slack.rb @@ -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 diff --git a/app/models/integrations/unify_circuit.rb b/app/models/integrations/unify_circuit.rb index 2e7b9b7dc60..aa19133b8c2 100644 --- a/app/models/integrations/unify_circuit.rb +++ b/app/models/integrations/unify_circuit.rb @@ -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', diff --git a/app/models/integrations/webex_teams.rb b/app/models/integrations/webex_teams.rb index 5a7a296f550..8e6f5ca6d17 100644 --- a/app/models/integrations/webex_teams.rb +++ b/app/models/integrations/webex_teams.rb @@ -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', diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index bb7c19d67eb..bfeb1a602ab 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -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 diff --git a/app/serializers/detailed_status_entity.rb b/app/serializers/detailed_status_entity.rb index 4f23ef0ed82..ed8ac9f40f7 100644 --- a/app/serializers/detailed_status_entity.rb +++ b/app/serializers/detailed_status_entity.rb @@ -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 diff --git a/app/serializers/integrations/field_entity.rb b/app/serializers/integrations/field_entity.rb index 697b53a737e..1c548cfab78 100644 --- a/app/serializers/integrations/field_entity.rb +++ b/app/serializers/integrations/field_entity.rb @@ -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 diff --git a/app/serializers/test_case_entity.rb b/app/serializers/test_case_entity.rb index 8a5fadf53a6..1a872274cbf 100644 --- a/app/serializers/test_case_entity.rb +++ b/app/serializers/test_case_entity.rb @@ -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 diff --git a/app/serializers/test_report_entity.rb b/app/serializers/test_report_entity.rb index 9eb487da60a..0ff1c671f53 100644 --- a/app/serializers/test_report_entity.rb +++ b/app/serializers/test_report_entity.rb @@ -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 diff --git a/app/serializers/test_report_summary_entity.rb b/app/serializers/test_report_summary_entity.rb index bc73c49092f..f712b9f5500 100644 --- a/app/serializers/test_report_summary_entity.rb +++ b/app/serializers/test_report_summary_entity.rb @@ -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 diff --git a/app/serializers/test_suite_entity.rb b/app/serializers/test_suite_entity.rb index 15eb2891b22..a31b1f3ab9b 100644 --- a/app/serializers/test_suite_entity.rb +++ b/app/serializers/test_suite_entity.rb @@ -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 diff --git a/app/serializers/test_suite_summary_entity.rb b/app/serializers/test_suite_summary_entity.rb index 228c6e499fe..3a9ccb22713 100644 --- a/app/serializers/test_suite_summary_entity.rb +++ b/app/serializers/test_suite_summary_entity.rb @@ -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 diff --git a/app/services/merge_requests/mergeability/run_checks_service.rb b/app/services/merge_requests/mergeability/run_checks_service.rb index 0af589b7987..740a6feac2c 100644 --- a/app/services/merge_requests/mergeability/run_checks_service.rb +++ b/app/services/merge_requests/mergeability/run_checks_service.rb @@ -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) diff --git a/app/views/shared/_md_preview.html.haml b/app/views/shared/_md_preview.html.haml index 7314a7ddadc..2fff70cdc74 100644 --- a/app/views/shared/_md_preview.html.haml +++ b/app/views/shared/_md_preview.html.haml @@ -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 diff --git a/config/feature_flags/development/mergeability_caching.yml b/config/feature_flags/development/mergeability_caching.yml deleted file mode 100644 index b9063299926..00000000000 --- a/config/feature_flags/development/mergeability_caching.yml +++ /dev/null @@ -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 diff --git a/config/metrics/counts_all/20210216181016_projects_with_expiration_policy_enabled.yml b/config/metrics/counts_all/20210216181016_projects_with_expiration_policy_enabled.yml index c5e5e7fffd1..3c49f82fa6b 100644 --- a/config/metrics/counts_all/20210216181016_projects_with_expiration_policy_enabled.yml +++ b/config/metrics/counts_all/20210216181016_projects_with_expiration_policy_enabled.yml @@ -10,6 +10,9 @@ value_type: number status: active time_frame: all data_source: database +instrumentation_class: DistinctCountProjectsWithExpirationPolicyMetric +options: + enabled: true distribution: - ee - ce diff --git a/config/metrics/counts_all/20210216181018_projects_with_expiration_policy_enabled_with_keep_n_set_to_1.yml b/config/metrics/counts_all/20210216181018_projects_with_expiration_policy_enabled_with_keep_n_set_to_1.yml index 2a531707621..e9eaec20453 100644 --- a/config/metrics/counts_all/20210216181018_projects_with_expiration_policy_enabled_with_keep_n_set_to_1.yml +++ b/config/metrics/counts_all/20210216181018_projects_with_expiration_policy_enabled_with_keep_n_set_to_1.yml @@ -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 diff --git a/config/metrics/counts_all/20210216181020_projects_with_expiration_policy_enabled_with_keep_n_set_to_5.yml b/config/metrics/counts_all/20210216181020_projects_with_expiration_policy_enabled_with_keep_n_set_to_5.yml index 4e9df93f171..b6bc610b97a 100644 --- a/config/metrics/counts_all/20210216181020_projects_with_expiration_policy_enabled_with_keep_n_set_to_5.yml +++ b/config/metrics/counts_all/20210216181020_projects_with_expiration_policy_enabled_with_keep_n_set_to_5.yml @@ -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 diff --git a/config/metrics/counts_all/20210216181022_projects_with_expiration_policy_enabled_with_keep_n_set_to_10.yml b/config/metrics/counts_all/20210216181022_projects_with_expiration_policy_enabled_with_keep_n_set_to_10.yml index da0e5006521..906382f2e52 100644 --- a/config/metrics/counts_all/20210216181022_projects_with_expiration_policy_enabled_with_keep_n_set_to_10.yml +++ b/config/metrics/counts_all/20210216181022_projects_with_expiration_policy_enabled_with_keep_n_set_to_10.yml @@ -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 diff --git a/config/metrics/counts_all/20210216181024_projects_with_expiration_policy_enabled_with_keep_n_set_to_25.yml b/config/metrics/counts_all/20210216181024_projects_with_expiration_policy_enabled_with_keep_n_set_to_25.yml index 84bce062d63..2ba5e503c81 100644 --- a/config/metrics/counts_all/20210216181024_projects_with_expiration_policy_enabled_with_keep_n_set_to_25.yml +++ b/config/metrics/counts_all/20210216181024_projects_with_expiration_policy_enabled_with_keep_n_set_to_25.yml @@ -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 diff --git a/config/metrics/counts_all/20210216181025_projects_with_expiration_policy_enabled_with_keep_n_set_to_50.yml b/config/metrics/counts_all/20210216181025_projects_with_expiration_policy_enabled_with_keep_n_set_to_50.yml index 38579a1bdc2..faa4bb4ba8f 100644 --- a/config/metrics/counts_all/20210216181025_projects_with_expiration_policy_enabled_with_keep_n_set_to_50.yml +++ b/config/metrics/counts_all/20210216181025_projects_with_expiration_policy_enabled_with_keep_n_set_to_50.yml @@ -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 diff --git a/config/metrics/counts_all/20210216181027_projects_with_expiration_policy_enabled_with_keep_n_set_to_100.yml b/config/metrics/counts_all/20210216181027_projects_with_expiration_policy_enabled_with_keep_n_set_to_100.yml index 69c59dd2262..bc2963db71f 100644 --- a/config/metrics/counts_all/20210216181027_projects_with_expiration_policy_enabled_with_keep_n_set_to_100.yml +++ b/config/metrics/counts_all/20210216181027_projects_with_expiration_policy_enabled_with_keep_n_set_to_100.yml @@ -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 diff --git a/config/metrics/counts_all/20210216181046_projects_with_expiration_policy_enabled_with_keep_n_unset.yml b/config/metrics/counts_all/20210216181046_projects_with_expiration_policy_enabled_with_keep_n_unset.yml index 6a98e4ecfd8..912e349a206 100644 --- a/config/metrics/counts_all/20210216181046_projects_with_expiration_policy_enabled_with_keep_n_unset.yml +++ b/config/metrics/counts_all/20210216181046_projects_with_expiration_policy_enabled_with_keep_n_unset.yml @@ -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 diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index e7da59e5845..0aa0d163972 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -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 diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 9701451ae31..e07aa0e42c1 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -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, diff --git a/doc/development/documentation/topic_types/tutorial.md b/doc/development/documentation/topic_types/tutorial.md index 80fdadcc6b3..1b1426a0465 100644 --- a/doc/development/documentation/topic_types/tutorial.md +++ b/doc/development/documentation/topic_types/tutorial.md @@ -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`. diff --git a/doc/development/sec/security_report_ingestion_overview.md b/doc/development/sec/security_report_ingestion_overview.md index 11389f388b3..c1d977c2a17 100644 --- a/doc/development/sec/security_report_ingestion_overview.md +++ b/doc/development/sec/security_report_ingestion_overview.md @@ -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`: diff --git a/doc/tutorials/agile_sprint.md b/doc/tutorials/agile_sprint.md index eb6aed92467..8e1886cf464 100644 --- a/doc/tutorials/agile_sprint.md +++ b/doc/tutorials/agile_sprint.md @@ -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. diff --git a/doc/tutorials/index.md b/doc/tutorials/index.md index f9be21b3887..9b264a8c636 100644 --- a/doc/tutorials/index.md +++ b/doc/tutorials/index.md @@ -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. | | diff --git a/doc/tutorials/make_your_first_git_commit.md b/doc/tutorials/make_your_first_git_commit.md index e4fe5486b6d..3df65389c76 100644 --- a/doc/tutorials/make_your_first_git_commit.md +++ b/doc/tutorials/make_your_first_git_commit.md @@ -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 diff --git a/generator_templates/usage_metric_definition/metric_definition.yml b/generator_templates/usage_metric_definition/metric_definition.yml index f8920b8d963..ca4f00866ab 100644 --- a/generator_templates/usage_metric_definition/metric_definition.yml +++ b/generator_templates/usage_metric_definition/metric_definition.yml @@ -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: diff --git a/lib/api/api.rb b/lib/api/api.rb index 3d739c28624..4d25108cfe5 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -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 diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index e7c54b9cc61..c055512e54e 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -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 diff --git a/lib/api/entities/ci/pipeline.rb b/lib/api/entities/ci/pipeline.rb index a8033a21044..7631cf60dbd 100644 --- a/lib/api/entities/ci/pipeline.rb +++ b/lib/api/entities/ci/pipeline.rb @@ -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| diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb index 817dc49c431..5777b8754e7 100644 --- a/lib/api/project_repository_storage_moves.rb +++ b/lib/api/project_repository_storage_moves.rb @@ -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 diff --git a/lib/api/snippet_repository_storage_moves.rb b/lib/api/snippet_repository_storage_moves.rb index 7e9d07d8e28..92eb10b3bb8 100644 --- a/lib/api/snippet_repository_storage_moves.rb +++ b/lib/api/snippet_repository_storage_moves.rb @@ -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 diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb index 2a1060a6059..fc03ac125fd 100644 --- a/lib/gitlab/ci/config/external/mapper.rb +++ b/lib/gitlab/ci/config/external/mapper.rb @@ -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) diff --git a/lib/gitlab/ci/pipeline/chain/limit/active_jobs.rb b/lib/gitlab/ci/pipeline/chain/limit/active_jobs.rb index 8b26416edf7..2bb32a316be 100644 --- a/lib/gitlab/ci/pipeline/chain/limit/active_jobs.rb +++ b/lib/gitlab/ci/pipeline/chain/limit/active_jobs.rb @@ -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? diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index fba5b183ce5..544211666bb 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7326c9f81df..3189ad63035 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb index b76269f6f93..2b23f177a9d 100644 --- a/spec/controllers/projects/settings/integrations_controller_spec.rb +++ b/spec/controllers/projects/settings/integrations_controller_spec.rb @@ -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 diff --git a/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric.yml b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric.yml index a5bdd378f53..520328f1041 100644 --- a/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric.yml +++ b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric.yml @@ -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 diff --git a/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_ee.yml b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_ee.yml index 4931285f6cf..1942f33e043 100644 --- a/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_ee.yml +++ b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_ee.yml @@ -14,7 +14,7 @@ time_frame: 7d data_source: data_category: optional instrumentation_class: Count -performance_indicator_type: +performance_indicator_type: [] distribution: - ee tier: diff --git a/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_name_suggestions.yml b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_name_suggestions.yml index 39472af686d..a72ba5109cc 100644 --- a/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_name_suggestions.yml +++ b/spec/fixtures/lib/generators/gitlab/usage_metric_definition_generator/sample_metric_with_name_suggestions.yml @@ -15,7 +15,7 @@ time_frame: 7d data_source: data_category: optional instrumentation_class: Count -performance_indicator_type: +performance_indicator_type: [] distribution: - ce - ee diff --git a/spec/frontend/editor/schema/ci/ci_schema_spec.js b/spec/frontend/editor/schema/ci/ci_schema_spec.js index b2c9118ddcf..203a00577f1 100644 --- a/spec/frontend/editor/schema/ci/ci_schema_spec.js +++ b/spec/frontend/editor/schema/ci/ci_schema_spec.js @@ -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, diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/variables.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/variables/invalid_syntax_desc.yml index a7f23cf0d73..4916a6b354e 100644 --- a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/variables.yml +++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/variables/invalid_syntax_desc.yml @@ -1,4 +1,3 @@ -# invalid variable (unknown keyword is used) variables: FOO: value: BAR diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/variables/wrong_syntax_usage_expand.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/variables/wrong_syntax_usage_expand.yml new file mode 100644 index 00000000000..62bebfa57e7 --- /dev/null +++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/variables/wrong_syntax_usage_expand.yml @@ -0,0 +1,4 @@ +variables: + RAW_VAR: + value: Hello $FOO + expand: okay diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/variables.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/variables.yml index ee71087a72e..53d020c432f 100644 --- a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/variables.yml +++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/variables.yml @@ -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
\ No newline at end of file diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js index 66a917d8052..eb81c38f889 100644 --- a/spec/frontend/work_items/router_spec.js +++ b/spec/frontend/work_items/router_spec.js @@ -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); diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index e12f5dcee0a..d905568f01e 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -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 diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 475503de7da..c4a6641ff6b 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -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 diff --git a/spec/lib/gitlab/ci/pipeline/chain/limit/active_jobs_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/limit/active_jobs_spec.rb index bc453f1502b..c5a5e905d17 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/limit/active_jobs_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/limit/active_jobs_spec.rb @@ -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 diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 28318a776f9..5de813f7739 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -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 diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb index 90aa524d6bf..931340947a2 100644 --- a/spec/lib/gitlab/usage/metric_definition_spec.rb +++ b/spec/lib/gitlab/usage/metric_definition_spec.rb @@ -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 diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 04e91547c8e..5d01b4afbcb 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -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 diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb index 3cd08479977..4938e1797af 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -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) } diff --git a/spec/models/integrations/base_chat_notification_spec.rb b/spec/models/integrations/base_chat_notification_spec.rb index e9d931bb564..3875f0edec5 100644 --- a/spec/models/integrations/base_chat_notification_spec.rb +++ b/spec/models/integrations/base_chat_notification_spec.rb @@ -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 diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 7e9f933fa22..922f4ac8804 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -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 diff --git a/spec/serializers/integrations/field_entity_spec.rb b/spec/serializers/integrations/field_entity_spec.rb index 7af17cf6df6..4212a1ee6a2 100644 --- a/spec/serializers/integrations/field_entity_spec.rb +++ b/spec/serializers/integrations/field_entity_spec.rb @@ -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) diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index e88fe1b42f0..ef92b6984d5 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -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 diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 5fe4c693451..5ddf91e167e 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -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 + + 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) - 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) + 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 + 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 - it 'executes confidential issue hooks when issue is confidential' do - opts = { title: 'Title', description: 'Description', confidential: true } + before do + opts[: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) + 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 + 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 diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 23180f75eb3..655c5085fdc 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -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) } + + 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' do - allow_next_instance_of(WebHookService) do |instance| - allow(instance).to receive(:execute) + 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 diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index 477b44f4c2c..6013826f9b1 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -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 diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index ebc870406e8..f1ee62fd589 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -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 diff --git a/spec/services/merge_requests/mergeability/run_checks_service_spec.rb b/spec/services/merge_requests/mergeability/run_checks_service_spec.rb index 2d3fd554a1b..c56b38bccc1 100644 --- a/spec/services/merge_requests/mergeability/run_checks_service_spec.rb +++ b/spec/services/merge_requests/mergeability/run_checks_service_spec.rb @@ -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 diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index cc520568244..da78f86c7c8 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -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 diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index b4f0cbd8527..fa221e64edc 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -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 diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 3ca7a37806b..fbee77307d0 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -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' diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb index 94061b140f4..b85c3904127 100644 --- a/spec/support/services/issuable_update_service_shared_examples.rb +++ b/spec/support/services/issuable_update_service_shared_examples.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 diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb index 59182f6e757..4d5c987ce37 100644 --- a/spec/views/projects/commit/show.html.haml_spec.rb +++ b/spec/views/projects/commit/show.html.haml_spec.rb @@ -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 |
