Commit b2a98edc authored by David Barr's avatar David Barr

Add webhooks for creating and updating a release

Allow for the sending of webhooks when creating and
updating a release, for both project and group level
webhooks.
parent db863982
...@@ -70,6 +70,10 @@ module GitlabRoutingHelper ...@@ -70,6 +70,10 @@ module GitlabRoutingHelper
project_commit_url(entity.project, entity.sha, *args) project_commit_url(entity.project, entity.sha, *args)
end end
def release_url(entity, *args)
project_release_url(entity.project, entity, *args)
end
def preview_markdown_path(parent, *args) def preview_markdown_path(parent, *args)
return group_preview_markdown_path(parent, *args) if parent.is_a?(Group) return group_preview_markdown_path(parent, *args) if parent.is_a?(Group)
......
...@@ -14,7 +14,8 @@ module TriggerableHooks ...@@ -14,7 +14,8 @@ module TriggerableHooks
pipeline_hooks: :pipeline_events, pipeline_hooks: :pipeline_events,
wiki_page_hooks: :wiki_page_events, wiki_page_hooks: :wiki_page_events,
deployment_hooks: :deployment_events, deployment_hooks: :deployment_events,
feature_flag_hooks: :feature_flag_events feature_flag_hooks: :feature_flag_events,
release_hooks: :releases_events
}.freeze }.freeze
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
...@@ -19,7 +19,8 @@ class ProjectHook < WebHook ...@@ -19,7 +19,8 @@ class ProjectHook < WebHook
:pipeline_hooks, :pipeline_hooks,
:wiki_page_hooks, :wiki_page_hooks,
:deployment_hooks, :deployment_hooks,
:feature_flag_hooks :feature_flag_hooks,
:release_hooks
] ]
belongs_to :project belongs_to :project
......
...@@ -83,6 +83,15 @@ class Release < ApplicationRecord ...@@ -83,6 +83,15 @@ class Release < ApplicationRecord
self.milestones.map {|m| m.title }.sort.join(", ") self.milestones.map {|m| m.title }.sort.join(", ")
end end
def to_hook_data(action)
Gitlab::HookData::ReleaseBuilder.new(self).build(action)
end
def execute_hooks(action)
hook_data = to_hook_data(action)
project.execute_hooks(hook_data, :release_hooks)
end
private private
def actual_sha def actual_sha
......
...@@ -30,5 +30,15 @@ module Releases ...@@ -30,5 +30,15 @@ module Releases
def external? def external?
!internal? !internal?
end end
def hook_attrs
{
id: id,
external: external?,
link_type: link_type,
name: name,
url: url
}
end
end end
end end
...@@ -24,6 +24,13 @@ module Releases ...@@ -24,6 +24,13 @@ module Releases
format: format) format: format)
end end
def hook_attrs
{
format: format,
url: url
}
end
private private
def archive_prefix def archive_prefix
......
...@@ -58,5 +58,12 @@ module Integrations ...@@ -58,5 +58,12 @@ module Integrations
Gitlab::DataBuilder::Deployment.build(deployment) Gitlab::DataBuilder::Deployment.build(deployment)
end end
def releases_events_data
release = project.releases.first
return { error: s_('TestHooks|Ensure the project has releases.') } unless release.present?
release.to_hook_data('create')
end
end end
end end
...@@ -35,6 +35,8 @@ module Integrations ...@@ -35,6 +35,8 @@ module Integrations
wiki_page_events_data wiki_page_events_data
when 'deployment' when 'deployment'
deployment_events_data deployment_events_data
when 'release'
releases_events_data
else else
push_events_data push_events_data
end end
......
...@@ -81,6 +81,10 @@ module Releases ...@@ -81,6 +81,10 @@ module Releases
params.key?(:milestones) params.key?(:milestones)
end end
def execute_hooks(release, action = 'create')
release.execute_hooks(action)
end
# overridden in EE # overridden in EE
def project_group_id; end def project_group_id; end
end end
......
...@@ -52,6 +52,8 @@ module Releases ...@@ -52,6 +52,8 @@ module Releases
notify_create_release(release) notify_create_release(release)
execute_hooks(release, 'create')
create_evidence!(release, evidence_pipeline) create_evidence!(release, evidence_pipeline)
success(tag: tag, release: release) success(tag: tag, release: release)
......
...@@ -3,11 +3,9 @@ ...@@ -3,11 +3,9 @@
module Releases module Releases
class UpdateService < Releases::BaseService class UpdateService < Releases::BaseService
def execute def execute
return error('Tag does not exist', 404) unless existing_tag if error = validate
return error('Release does not exist', 404) unless release return error
return error('Access Denied', 403) unless allowed? end
return error('params is empty', 400) if empty_params?
return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any?
if param_for_milestone_titles_provided? if param_for_milestone_titles_provided?
previous_milestones = release.milestones.map(&:title) previous_milestones = release.milestones.map(&:title)
...@@ -20,6 +18,7 @@ module Releases ...@@ -20,6 +18,7 @@ module Releases
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43385 # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43385
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
if release.update(params) if release.update(params)
execute_hooks(release, 'update')
success(tag: existing_tag, release: release, milestones_updated: milestones_updated?(previous_milestones)) success(tag: existing_tag, release: release, milestones_updated: milestones_updated?(previous_milestones))
else else
error(release.errors.messages || '400 Bad request', 400) error(release.errors.messages || '400 Bad request', 400)
...@@ -31,6 +30,14 @@ module Releases ...@@ -31,6 +30,14 @@ module Releases
private private
def validate
return error('Tag does not exist', 404) unless existing_tag
return error('Release does not exist', 404) unless release
return error('Access Denied', 403) unless allowed?
return error('params is empty', 400) if empty_params?
return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any?
end
def allowed? def allowed?
Ability.allowed?(current_user, :update_release, release) Ability.allowed?(current_user, :update_release, release)
end end
......
...@@ -30,6 +30,8 @@ module TestHooks ...@@ -30,6 +30,8 @@ module TestHooks
pipeline_events_data pipeline_events_data
when 'wiki_page_events' when 'wiki_page_events'
wiki_page_events_data wiki_page_events_data
when 'releases_events'
releases_events_data
end end
end end
end end
......
...@@ -84,6 +84,12 @@ ...@@ -84,6 +84,12 @@
%strong= s_('Webhooks|Feature Flag events') %strong= s_('Webhooks|Feature Flag events')
%p.text-muted.ml-1 %p.text-muted.ml-1
= s_('Webhooks|This URL is triggered when a feature flag is turned on or off') = s_('Webhooks|This URL is triggered when a feature flag is turned on or off')
%li
= form.check_box :releases_events, class: 'form-check-input'
= form.label :releases_events, class: 'list-label form-check-label ml-1' do
%strong= s_('Webhooks|Releases events')
%p.text-muted.ml-1
= s_('Webhooks|This URL is triggered when a release is created/updated')
.form-group .form-group
= form.label :enable_ssl_verification, s_('Webhooks|SSL verification'), class: 'label-bold checkbox' = form.label :enable_ssl_verification, s_('Webhooks|SSL verification'), class: 'label-bold checkbox'
.form-check .form-check
......
---
title: Add webhooks for creating and updating a release
merge_request: 44881
author: David Barr @davebarr
type: added
# frozen_string_literal: true
class AddReleasesEventsToWebHooks < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :web_hooks, :releases_events, :boolean, null: false, default: false
end
end
f9bc943b61460b1a9a6db8189ab5b21eba46e14650c68658175299b14d48a030
\ No newline at end of file
...@@ -17364,6 +17364,7 @@ CREATE TABLE web_hooks ( ...@@ -17364,6 +17364,7 @@ CREATE TABLE web_hooks (
encrypted_url character varying, encrypted_url character varying,
encrypted_url_iv character varying, encrypted_url_iv character varying,
deployment_events boolean DEFAULT false NOT NULL, deployment_events boolean DEFAULT false NOT NULL,
releases_events boolean DEFAULT false NOT NULL,
feature_flag_events boolean DEFAULT false NOT NULL feature_flag_events boolean DEFAULT false NOT NULL
); );
......
...@@ -1043,6 +1043,7 @@ GET /groups/:id/hooks/:hook_id ...@@ -1043,6 +1043,7 @@ GET /groups/:id/hooks/:hook_id
"pipeline_events": true, "pipeline_events": true,
"wiki_page_events": true, "wiki_page_events": true,
"deployment_events": true, "deployment_events": true,
"releases_events": true,
"enable_ssl_verification": true, "enable_ssl_verification": true,
"created_at": "2012-10-12T17:04:47Z" "created_at": "2012-10-12T17:04:47Z"
} }
...@@ -1071,6 +1072,7 @@ POST /groups/:id/hooks ...@@ -1071,6 +1072,7 @@ POST /groups/:id/hooks
| `pipeline_events` | boolean | no | Trigger hook on pipeline events | | `pipeline_events` | boolean | no | Trigger hook on pipeline events |
| `wiki_page_events` | boolean | no | Trigger hook on wiki events | | `wiki_page_events` | boolean | no | Trigger hook on wiki events |
| `deployment_events` | boolean | no | Trigger hook on deployment events | | `deployment_events` | boolean | no | Trigger hook on deployment events |
| `releases_events` | boolean | no | Trigger hook on release events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook | | `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response | | `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
...@@ -1098,6 +1100,7 @@ PUT /groups/:id/hooks/:hook_id ...@@ -1098,6 +1100,7 @@ PUT /groups/:id/hooks/:hook_id
| `pipeline_events` | boolean | no | Trigger hook on pipeline events | | `pipeline_events` | boolean | no | Trigger hook on pipeline events |
| `wiki_events` | boolean | no | Trigger hook on wiki events | | `wiki_events` | boolean | no | Trigger hook on wiki events |
| `deployment_events` | boolean | no | Trigger hook on deployment events | | `deployment_events` | boolean | no | Trigger hook on deployment events |
| `releases_events` | boolean | no | Trigger hook on release events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook | | `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response | | `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
......
...@@ -2006,6 +2006,7 @@ GET /projects/:id/hooks/:hook_id ...@@ -2006,6 +2006,7 @@ GET /projects/:id/hooks/:hook_id
"pipeline_events": true, "pipeline_events": true,
"wiki_page_events": true, "wiki_page_events": true,
"deployment_events": true, "deployment_events": true,
"releases_events": true,
"enable_ssl_verification": true, "enable_ssl_verification": true,
"created_at": "2012-10-12T17:04:47Z" "created_at": "2012-10-12T17:04:47Z"
} }
...@@ -2065,6 +2066,7 @@ PUT /projects/:id/hooks/:hook_id ...@@ -2065,6 +2066,7 @@ PUT /projects/:id/hooks/:hook_id
| `token` | string | **{dotted-circle}** No | Secret token to validate received payloads; this isn't returned in the response. | | `token` | string | **{dotted-circle}** No | Secret token to validate received payloads; this isn't returned in the response. |
| `url` | string | **{check-circle}** Yes | The hook URL. | | `url` | string | **{check-circle}** Yes | The hook URL. |
| `wiki_events` | boolean | **{dotted-circle}** No | Trigger hook on wiki events. | | `wiki_events` | boolean | **{dotted-circle}** No | Trigger hook on wiki events. |
| `releases_events` | boolean | **{dotted-circle}** No | Trigger hook on release events. |
### Delete project hook ### Delete project hook
......
...@@ -1407,6 +1407,91 @@ X-Gitlab-Event: Feature Flag Hook ...@@ -1407,6 +1407,91 @@ X-Gitlab-Event: Feature Flag Hook
} }
``` ```
### Release events
Triggered when a release is created or updated.
**Request Header**:
```plaintext
X-Gitlab-Event: Release Hook
```
**Request Body**:
```json
{
"id": 1,
"created_at": "2020-11-02 12:55:12 UTC",
"description": "v1.0 has been released",
"name": "v1.1",
"released_at": "2020-11-02 12:55:12 UTC",
"tag": "v1.1",
"object_kind": "release",
"project": {
"id": 2,
"name": "release-webhook-example",
"description": "",
"web_url": "https://example.com/gitlab-org/release-webhook-example",
"avatar_url": null,
"git_ssh_url": "ssh://git@example.com/gitlab-org/release-webhook-example.git",
"git_http_url": "https://example.com/gitlab-org/release-webhook-example.git",
"namespace": "Gitlab",
"visibility_level": 0,
"path_with_namespace": "gitlab-org/release-webhook-example",
"default_branch": "master",
"ci_config_path": null,
"homepage": "https://example.com/gitlab-org/release-webhook-example",
"url": "ssh://git@example.com/gitlab-org/release-webhook-example.git",
"ssh_url": "ssh://git@example.com/gitlab-org/release-webhook-example.git",
"http_url": "https://example.com/gitlab-org/release-webhook-example.git"
},
"url": "https://example.com/gitlab-org/release-webhook-example/-/releases/v1.1",
"action": "create",
"assets": {
"count": 5,
"links": [
{
"id": 1,
"external": true,
"link_type": "other",
"name": "Changelog",
"url": "https://example.net/changelog"
}
],
"sources": [
{
"format": "zip",
"url": "https://example.com/gitlab-org/release-webhook-example/-/archive/v1.1/release-webhook-example-v1.1.zip"
},
{
"format": "tar.gz",
"url": "https://example.com/gitlab-org/release-webhook-example/-/archive/v1.1/release-webhook-example-v1.1.tar.gz"
},
{
"format": "tar.bz2",
"url": "https://example.com/gitlab-org/release-webhook-example/-/archive/v1.1/release-webhook-example-v1.1.tar.bz2"
},
{
"format": "tar",
"url": "https://example.com/gitlab-org/release-webhook-example/-/archive/v1.1/release-webhook-example-v1.1.tar"
}
]
},
"commit": {
"id": "ee0a3fb31ac16e11b9dbb596ad16d4af654d08f8",
"message": "Release v1.1",
"title": "Release v1.1",
"timestamp": "2020-10-31T14:58:32+11:00",
"url": "https://example.com/gitlab-org/release-webhook-example/-/commit/ee0a3fb31ac16e11b9dbb596ad16d4af654d08f8",
"author": {
"name": "Example User",
"email": "user@example.com"
}
}
}
```
## Image URL rewriting ## Image URL rewriting
From GitLab 11.2, simple image references are rewritten to use an absolute URL From GitLab 11.2, simple image references are rewritten to use an absolute URL
......
...@@ -19,7 +19,8 @@ class GroupHook < WebHook ...@@ -19,7 +19,8 @@ class GroupHook < WebHook
:job_hooks, :job_hooks,
:pipeline_hooks, :pipeline_hooks,
:wiki_page_hooks, :wiki_page_hooks,
:deployment_hooks :deployment_hooks,
:release_hooks
] ]
belongs_to :group belongs_to :group
......
...@@ -23,6 +23,7 @@ module API ...@@ -23,6 +23,7 @@ module API
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events" optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events" optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
optional :deployment_events, type: Boolean, desc: "Trigger hook on deployment events" optional :deployment_events, type: Boolean, desc: "Trigger hook on deployment events"
optional :releases_events, type: Boolean, desc: "Trigger hook on release events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response" optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
end end
......
...@@ -6,7 +6,7 @@ module EE ...@@ -6,7 +6,7 @@ module EE
class GroupHook < ::API::Entities::Hook class GroupHook < ::API::Entities::Hook
expose :group_id, :issues_events, :confidential_issues_events, expose :group_id, :issues_events, :confidential_issues_events,
:note_events, :confidential_note_events, :pipeline_events, :wiki_page_events, :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events,
:job_events, :deployment_events :job_events, :deployment_events, :releases_events
end end
end end
end end
......
...@@ -80,7 +80,8 @@ RSpec.describe Groups::HooksController do ...@@ -80,7 +80,8 @@ RSpec.describe Groups::HooksController do
token: 'TEST TOKEN', token: 'TEST TOKEN',
url: 'http://example.com', url: 'http://example.com',
wiki_page_events: true, wiki_page_events: true,
deployment_events: true deployment_events: true,
releases_events: true
} }
end end
......
...@@ -16,6 +16,7 @@ FactoryBot.define do ...@@ -16,6 +16,7 @@ FactoryBot.define do
job_events { true } job_events { true }
pipeline_events { true } pipeline_events { true }
wiki_page_events { true } wiki_page_events { true }
releases_events { true }
end end
end end
end end
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
"pipeline_events", "pipeline_events",
"wiki_page_events", "wiki_page_events",
"job_events", "job_events",
"deployment_events" "deployment_events",
"releases_events"
], ],
"properties": { "properties": {
"id": { "type": "integer" }, "id": { "type": "integer" },
...@@ -36,7 +37,8 @@ ...@@ -36,7 +37,8 @@
"pipeline_events": { "type": "boolean" }, "pipeline_events": { "type": "boolean" },
"wiki_page_events": { "type": "boolean" }, "wiki_page_events": { "type": "boolean" },
"job_events": { "type": "boolean" }, "job_events": { "type": "boolean" },
"deployment_events": { "type": "boolean" } "deployment_events": { "type": "boolean" },
"releases_events": { "type": "boolean" }
}, },
"additionalProperties": false "additionalProperties": false
} }
...@@ -119,7 +119,8 @@ RSpec.describe API::GroupHooks do ...@@ -119,7 +119,8 @@ RSpec.describe API::GroupHooks do
job_events: true, job_events: true,
pipeline_events: true, pipeline_events: true,
wiki_page_events: true, wiki_page_events: true,
deployment_events: true deployment_events: true,
releases_events: true
} }
end end
...@@ -144,6 +145,7 @@ RSpec.describe API::GroupHooks do ...@@ -144,6 +145,7 @@ RSpec.describe API::GroupHooks do
expect(json_response['pipeline_events']).to eq(true) expect(json_response['pipeline_events']).to eq(true)
expect(json_response['wiki_page_events']).to eq(true) expect(json_response['wiki_page_events']).to eq(true)
expect(json_response['deployment_events']).to eq(true) expect(json_response['deployment_events']).to eq(true)
expect(json_response['releases_events']).to eq(true)
expect(json_response['enable_ssl_verification']).to eq(true) expect(json_response['enable_ssl_verification']).to eq(true)
end end
......
...@@ -5,7 +5,7 @@ module API ...@@ -5,7 +5,7 @@ module API
class ProjectHook < Hook class ProjectHook < Hook
expose :project_id, :issues_events, :confidential_issues_events expose :project_id, :issues_events, :confidential_issues_events
expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events, :deployment_events expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events, :deployment_events
expose :job_events expose :job_events, :releases_events
expose :push_events_branch_filter expose :push_events_branch_filter
end end
end end
......
...@@ -23,6 +23,7 @@ module API ...@@ -23,6 +23,7 @@ module API
optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events" optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events" optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
optional :deployment_events, type: Boolean, desc: "Trigger hook on deployment events" optional :deployment_events, type: Boolean, desc: "Trigger hook on deployment events"
optional :releases_events, type: Boolean, desc: "Trigger hook on release events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response" optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
optional :push_events_branch_filter, type: String, desc: "Trigger hook on specified branch only" optional :push_events_branch_filter, type: String, desc: "Trigger hook on specified branch only"
......
# frozen_string_literal: true
module Gitlab
module HookData
class ReleaseBuilder < BaseBuilder
def self.safe_hook_attributes
%i[
id
created_at
description
name
released_at
tag
].freeze
end
alias_method :release, :object
def build(action)
attrs = {
object_kind: object_kind,
project: release.project.hook_attrs,
description: absolute_image_urls(release.description),
url: Gitlab::UrlBuilder.build(release),
action: action,
assets: {
count: release.assets_count,
links: release.links.map(&:hook_attrs),
sources: release.sources.map(&:hook_attrs)
},
commit: release.commit.hook_attrs
}
release.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
.merge!(attrs)
end
private
def object_kind
release.class.name.underscore
end
end
end
end
...@@ -32,6 +32,8 @@ module Gitlab ...@@ -32,6 +32,8 @@ module Gitlab
instance.milestone_url(object, **options) instance.milestone_url(object, **options)
when Note when Note
note_url(object, **options) note_url(object, **options)
when Release
instance.release_url(object, **options)
when Project when Project
instance.project_url(object, **options) instance.project_url(object, **options)
when Snippet when Snippet
......
...@@ -26424,6 +26424,9 @@ msgstr "" ...@@ -26424,6 +26424,9 @@ msgstr ""
msgid "TestHooks|Ensure the project has notes." msgid "TestHooks|Ensure the project has notes."
msgstr "" msgstr ""
msgid "TestHooks|Ensure the project has releases."
msgstr ""
msgid "TestHooks|Ensure the wiki is enabled and has pages." msgid "TestHooks|Ensure the wiki is enabled and has pages."
msgstr "" msgstr ""
...@@ -30054,6 +30057,9 @@ msgstr "" ...@@ -30054,6 +30057,9 @@ msgstr ""
msgid "Webhooks|Push events" msgid "Webhooks|Push events"
msgstr "" msgstr ""
msgid "Webhooks|Releases events"
msgstr ""
msgid "Webhooks|SSL verification" msgid "Webhooks|SSL verification"
msgstr "" msgstr ""
...@@ -30069,6 +30075,9 @@ msgstr "" ...@@ -30069,6 +30075,9 @@ msgstr ""
msgid "Webhooks|This URL is triggered when a feature flag is turned on or off" msgid "Webhooks|This URL is triggered when a feature flag is turned on or off"
msgstr "" msgstr ""
msgid "Webhooks|This URL is triggered when a release is created/updated"
msgstr ""
msgid "Webhooks|This URL will be triggered by a push to the repository" msgid "Webhooks|This URL will be triggered by a push to the repository"
msgstr "" msgstr ""
......
...@@ -23,6 +23,7 @@ FactoryBot.define do ...@@ -23,6 +23,7 @@ FactoryBot.define do
wiki_page_events { true } wiki_page_events { true }
deployment_events { true } deployment_events { true }
feature_flag_events { true } feature_flag_events { true }
releases_events { true }
end end
end end
end end
...@@ -45,6 +45,7 @@ RSpec.describe 'Projects > Settings > Webhook Settings' do ...@@ -45,6 +45,7 @@ RSpec.describe 'Projects > Settings > Webhook Settings' do
expect(page).to have_content('Merge requests events') expect(page).to have_content('Merge requests events')
expect(page).to have_content('Pipeline events') expect(page).to have_content('Pipeline events')
expect(page).to have_content('Wiki page events') expect(page).to have_content('Wiki page events')
expect(page).to have_content('Releases events')
end end
it 'create webhook' do it 'create webhook' do
......
...@@ -322,4 +322,14 @@ RSpec.describe GitlabRoutingHelper do ...@@ -322,4 +322,14 @@ RSpec.describe GitlabRoutingHelper do
end end
end end
end end
context 'releases' do
let(:release) { create(:release) }
describe '#release_url' do
it 'returns the url for the release page' do
expect(release_url(release)).to eq("http://test.host/#{release.project.full_path}/-/releases/#{release.tag}")
end
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::HookData::ReleaseBuilder do
let_it_be(:project) { create(:project, :public, :repository) }
let(:release) { create(:release, project: project) }
let(:builder) { described_class.new(release) }
describe '#build' do
let(:data) { builder.build('create') }
it 'includes safe attribute' do
%w[
id
created_at
description
name
released_at
tag
].each do |key|
expect(data).to include(key)
end
end
it 'includes additional attrs' do
expect(data[:object_kind]).to eq('release')
expect(data[:project]).to eq(builder.release.project.hook_attrs.with_indifferent_access)
expect(data[:action]).to eq('create')
expect(data).to include(:assets)
expect(data).to include(:commit)
end
context 'when the Release has an image in the description' do
let(:release_with_description) do
create(:release, project: project, description: 'test![Release_Image](/uploads/abc/Release_Image.png)')
end
let(:builder) { described_class.new(release_with_description) }
it 'sets the image to use an absolute URL' do
expected_path = "#{release_with_description.project.path_with_namespace}/uploads/abc/Release_Image.png"
expect(data[:description])
.to eq("test![Release_Image](#{Settings.gitlab.url}/#{expected_path})")
end
end
end
end
...@@ -61,6 +61,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do ...@@ -61,6 +61,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do
'enable_ssl_verification' => true, 'enable_ssl_verification' => true,
'job_events' => false, 'job_events' => false,
'wiki_page_events' => true, 'wiki_page_events' => true,
'releases_events' => false,
'token' => token 'token' => token
} }
end end
......
...@@ -489,6 +489,7 @@ ProjectHook: ...@@ -489,6 +489,7 @@ ProjectHook:
- confidential_issues_events - confidential_issues_events
- confidential_note_events - confidential_note_events
- repository_update_events - repository_update_events
- releases_events
ProtectedBranch: ProtectedBranch:
- id - id
- project_id - project_id
......
...@@ -24,6 +24,7 @@ RSpec.describe Gitlab::UrlBuilder do ...@@ -24,6 +24,7 @@ RSpec.describe Gitlab::UrlBuilder do
:project_milestone | ->(milestone) { "/#{milestone.project.full_path}/-/milestones/#{milestone.iid}" } :project_milestone | ->(milestone) { "/#{milestone.project.full_path}/-/milestones/#{milestone.iid}" }
:project_snippet | ->(snippet) { "/#{snippet.project.full_path}/-/snippets/#{snippet.id}" } :project_snippet | ->(snippet) { "/#{snippet.project.full_path}/-/snippets/#{snippet.id}" }
:project_wiki | ->(wiki) { "/#{wiki.container.full_path}/-/wikis/home" } :project_wiki | ->(wiki) { "/#{wiki.container.full_path}/-/wikis/home" }
:release | ->(release) { "/#{release.project.full_path}/-/releases/#{release.tag}" }
:ci_build | ->(build) { "/#{build.project.full_path}/-/jobs/#{build.id}" } :ci_build | ->(build) { "/#{build.project.full_path}/-/jobs/#{build.id}" }
:design | ->(design) { "/#{design.project.full_path}/-/design_management/designs/#{design.id}/raw_image" } :design | ->(design) { "/#{design.project.full_path}/-/design_management/designs/#{design.id}/raw_image" }
......
...@@ -41,6 +41,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do ...@@ -41,6 +41,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response.first['pipeline_events']).to eq(true) expect(json_response.first['pipeline_events']).to eq(true)
expect(json_response.first['wiki_page_events']).to eq(true) expect(json_response.first['wiki_page_events']).to eq(true)
expect(json_response.first['deployment_events']).to eq(true) expect(json_response.first['deployment_events']).to eq(true)
expect(json_response.first['releases_events']).to eq(true)
expect(json_response.first['enable_ssl_verification']).to eq(true) expect(json_response.first['enable_ssl_verification']).to eq(true)
expect(json_response.first['push_events_branch_filter']).to eq('master') expect(json_response.first['push_events_branch_filter']).to eq('master')
end end
...@@ -72,6 +73,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do ...@@ -72,6 +73,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response['job_events']).to eq(hook.job_events) expect(json_response['job_events']).to eq(hook.job_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events) expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events) expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['releases_events']).to eq(hook.releases_events)
expect(json_response['deployment_events']).to eq(true) expect(json_response['deployment_events']).to eq(true)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
end end
...@@ -97,7 +99,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do ...@@ -97,7 +99,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do
post(api("/projects/#{project.id}/hooks", user), post(api("/projects/#{project.id}/hooks", user),
params: { url: "http://example.com", issues_events: true, params: { url: "http://example.com", issues_events: true,
confidential_issues_events: true, wiki_page_events: true, confidential_issues_events: true, wiki_page_events: true,
job_events: true, deployment_events: true, job_events: true, deployment_events: true, releases_events: true,
push_events_branch_filter: 'some-feature-branch' }) push_events_branch_filter: 'some-feature-branch' })
end.to change {project.hooks.count}.by(1) end.to change {project.hooks.count}.by(1)
...@@ -114,6 +116,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do ...@@ -114,6 +116,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response['pipeline_events']).to eq(false) expect(json_response['pipeline_events']).to eq(false)
expect(json_response['wiki_page_events']).to eq(true) expect(json_response['wiki_page_events']).to eq(true)
expect(json_response['deployment_events']).to eq(true) expect(json_response['deployment_events']).to eq(true)
expect(json_response['releases_events']).to eq(true)
expect(json_response['enable_ssl_verification']).to eq(true) expect(json_response['enable_ssl_verification']).to eq(true)
expect(json_response['push_events_branch_filter']).to eq('some-feature-branch') expect(json_response['push_events_branch_filter']).to eq('some-feature-branch')
expect(json_response).not_to include('token') expect(json_response).not_to include('token')
...@@ -169,6 +172,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do ...@@ -169,6 +172,7 @@ RSpec.describe API::ProjectHooks, 'ProjectHooks' do
expect(json_response['job_events']).to eq(hook.job_events) expect(json_response['job_events']).to eq(hook.job_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events) expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events) expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['releases_events']).to eq(hook.releases_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification) expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
end end
......
...@@ -22,6 +22,12 @@ RSpec.describe Releases::CreateService do ...@@ -22,6 +22,12 @@ RSpec.describe Releases::CreateService do
it 'creates a new release' do it 'creates a new release' do
expected_job_count = MailScheduler::NotificationServiceWorker.jobs.size + 1 expected_job_count = MailScheduler::NotificationServiceWorker.jobs.size + 1
expect_next_instance_of(Release) do |release|
expect(release)
.to receive(:execute_hooks)
.with('create')
end
result = service.execute result = service.execute
expect(project.releases.count).to eq(1) expect(project.releases.count).to eq(1)
......
...@@ -32,6 +32,12 @@ RSpec.describe Releases::UpdateService do ...@@ -32,6 +32,12 @@ RSpec.describe Releases::UpdateService do
expect(result[:release].description).to eq(new_description) expect(result[:release].description).to eq(new_description)
end end
it 'executes hooks' do
expect(service.release).to receive(:execute_hooks).with('update')
service.execute
end
context 'when the tag does not exists' do context 'when the tag does not exists' do
let(:tag_name) { 'foobar' } let(:tag_name) { 'foobar' }
......
...@@ -186,5 +186,23 @@ RSpec.describe TestHooks::ProjectService do ...@@ -186,5 +186,23 @@ RSpec.describe TestHooks::ProjectService do
expect(service.execute).to include(success_result) expect(service.execute).to include(success_result)
end end
end end
context 'releases_events' do
let(:trigger) { 'releases_events' }
let(:trigger_key) { :release_hooks }
it 'returns error message if not enough data' do
expect(hook).not_to receive(:execute)
expect(service.execute).to include({ status: :error, message: 'Ensure the project has releases.' })
end
it 'executes hook' do
allow(project).to receive(:releases).and_return([Release.new])
allow_any_instance_of(Release).to receive(:to_hook_data).and_return(sample_data)
expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
expect(service.execute).to include(success_result)
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment