Commit 52dca0d6 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'sk/220898-feature-flag-webhook' into 'master'

Trigger webhook when a feature flag is enabled or disabled

See merge request gitlab-org/gitlab!41863
parents 60d07686 2d33bcec
...@@ -13,7 +13,8 @@ module TriggerableHooks ...@@ -13,7 +13,8 @@ module TriggerableHooks
job_hooks: :job_events, job_hooks: :job_events,
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
}.freeze }.freeze
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
...@@ -18,7 +18,8 @@ class ProjectHook < WebHook ...@@ -18,7 +18,8 @@ class ProjectHook < WebHook
:job_hooks, :job_hooks,
:pipeline_hooks, :pipeline_hooks,
:wiki_page_hooks, :wiki_page_hooks,
:deployment_hooks :deployment_hooks,
:feature_flag_hooks
] ]
belongs_to :project belongs_to :project
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module Operations module Operations
class FeatureFlag < ApplicationRecord class FeatureFlag < ApplicationRecord
include AfterCommitQueue
include AtomicInternalId include AtomicInternalId
include IidRoutes include IidRoutes
include Limitable include Limitable
...@@ -77,6 +78,22 @@ module Operations ...@@ -77,6 +78,22 @@ module Operations
Ability.issues_readable_by_user(issues, current_user) Ability.issues_readable_by_user(issues, current_user)
end end
def execute_hooks(current_user)
run_after_commit do
feature_flag_data = Gitlab::DataBuilder::FeatureFlag.build(self, current_user)
project.execute_hooks(feature_flag_data, :feature_flag_hooks)
end
end
def hook_attrs
{
id: id,
name: name,
description: description,
active: active
}
end
private private
def version_associations def version_associations
......
...@@ -22,6 +22,10 @@ module FeatureFlags ...@@ -22,6 +22,10 @@ module FeatureFlags
audit_event = audit_event(feature_flag) audit_event = audit_event(feature_flag)
if feature_flag.active_changed?
feature_flag.execute_hooks(current_user)
end
if feature_flag.save if feature_flag.save
save_audit_event(audit_event) save_audit_event(audit_event)
......
...@@ -78,6 +78,12 @@ ...@@ -78,6 +78,12 @@
%strong= s_('Webhooks|Deployment events') %strong= s_('Webhooks|Deployment events')
%p.text-muted.ml-1 %p.text-muted.ml-1
= s_('Webhooks|This URL is triggered when a deployment starts, finishes, fails, or is canceled') = s_('Webhooks|This URL is triggered when a deployment starts, finishes, fails, or is canceled')
%li
= form.check_box :feature_flag_events, class: 'form-check-input'
= form.label :feature_flag_events, class: 'list-label form-check-label ml-1' do
%strong= s_('Webhooks|Feature Flag events')
%p.text-muted.ml-1
= s_('Webhooks|This URL is triggered when a feature flag is turned on or off')
.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 feature flag
merge_request: 41863
author: Sashi
type: added
# frozen_string_literal: true
class AddFeatureFlagEventsToWebHooks < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :web_hooks, :feature_flag_events, :boolean, null: false, default: false
end
end
a9605126178d887bbf526a4a33b7060b072eff7a8d6712e3552099f7e615f88b
\ No newline at end of file
...@@ -17295,7 +17295,8 @@ CREATE TABLE web_hooks ( ...@@ -17295,7 +17295,8 @@ CREATE TABLE web_hooks (
encrypted_token_iv character varying, encrypted_token_iv character varying,
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,
feature_flag_events boolean DEFAULT false NOT NULL
); );
CREATE SEQUENCE web_hooks_id_seq CREATE SEQUENCE web_hooks_id_seq
......
...@@ -1358,6 +1358,55 @@ X-Gitlab-Event: Deployment Hook ...@@ -1358,6 +1358,55 @@ X-Gitlab-Event: Deployment Hook
Note that `deployable_id` is the ID of the CI job. Note that `deployable_id` is the ID of the CI job.
### Feature Flag events
Triggered when a feature flag is turned on or off.
**Request Header**:
```plaintext
X-Gitlab-Event: Feature Flag Hook
```
**Request Body**:
```json
{
"object_kind": "feature_flag",
"project": {
"id": 1,
"name":"Gitlab Test",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlabhq/gitlab-test",
"avatar_url":null,
"git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"git_http_url":"http://example.com/gitlabhq/gitlab-test.git",
"namespace":"GitlabHQ",
"visibility_level":20,
"path_with_namespace":"gitlabhq/gitlab-test",
"default_branch":"master",
"ci_config_path": null,
"homepage":"http://example.com/gitlabhq/gitlab-test",
"url":"http://example.com/gitlabhq/gitlab-test.git",
"ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"http_url":"http://example.com/gitlabhq/gitlab-test.git"
},
"user": {
"name": "Administrator",
"username": "root",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"email": "admin@example.com"
},
"user_url": "http://example.com/root",
"object_attributes": {
"id": 6,
"name": "test-feature-flag",
"description": "test-feature-flag-description",
"active": true
}
}
```
## 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
......
# frozen_string_literal: true
module Gitlab
module DataBuilder
module FeatureFlag
extend self
def build(feature_flag, user)
{
object_kind: 'feature_flag',
project: feature_flag.project.hook_attrs,
user: user.hook_attrs,
user_url: Gitlab::UrlBuilder.build(user),
object_attributes: feature_flag.hook_attrs
}
end
end
end
end
...@@ -29704,6 +29704,9 @@ msgstr "" ...@@ -29704,6 +29704,9 @@ msgstr ""
msgid "Webhooks|Enable SSL verification" msgid "Webhooks|Enable SSL verification"
msgstr "" msgstr ""
msgid "Webhooks|Feature Flag events"
msgstr ""
msgid "Webhooks|Issues events" msgid "Webhooks|Issues events"
msgstr "" msgstr ""
...@@ -29731,6 +29734,9 @@ msgstr "" ...@@ -29731,6 +29734,9 @@ msgstr ""
msgid "Webhooks|This URL is triggered when a deployment starts, finishes, fails, or is canceled" msgid "Webhooks|This URL is triggered when a deployment starts, finishes, fails, or is canceled"
msgstr "" msgstr ""
msgid "Webhooks|This URL is triggered when a feature flag is turned on or off"
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 ""
......
...@@ -22,6 +22,7 @@ FactoryBot.define do ...@@ -22,6 +22,7 @@ FactoryBot.define do
pipeline_events { true } pipeline_events { true }
wiki_page_events { true } wiki_page_events { true }
deployment_events { true } deployment_events { true }
feature_flag_events { true }
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::DataBuilder::FeatureFlag do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:feature_flag) { create(:operations_feature_flag, project: project) }
describe '.build' do
let(:data) { described_class.build(feature_flag, user) }
it { expect(data).to be_a(Hash) }
it { expect(data[:object_kind]).to eq('feature_flag') }
it 'contains the correct object attributes' do
object_attributes = data[:object_attributes]
expect(object_attributes[:id]).to eq(feature_flag.id)
expect(object_attributes[:name]).to eq(feature_flag.name)
expect(object_attributes[:description]).to eq(feature_flag.description)
expect(object_attributes[:active]).to eq(feature_flag.active)
end
end
end
...@@ -261,4 +261,38 @@ RSpec.describe Operations::FeatureFlag do ...@@ -261,4 +261,38 @@ RSpec.describe Operations::FeatureFlag do
expect(flags.map(&:id)).to eq([feature_flag.id, feature_flag_b.id]) expect(flags.map(&:id)).to eq([feature_flag.id, feature_flag_b.id])
end end
end end
describe '#hook_attrs' do
it 'includes expected attributes' do
hook_attrs = {
id: subject.id,
name: subject.name,
description: subject.description,
active: subject.active
}
expect(subject.hook_attrs).to eq(hook_attrs)
end
end
describe "#execute_hooks" do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:feature_flag) { create(:operations_feature_flag, project: project) }
it 'does not execute the hook when feature_flag event is disabled' do
create(:project_hook, project: project, feature_flag_events: false)
expect(WebHookWorker).not_to receive(:perform_async)
feature_flag.execute_hooks(user)
feature_flag.touch
end
it 'executes hook when feature_flag event is enabled' do
hook = create(:project_hook, project: project, feature_flag_events: true)
expect(WebHookWorker).to receive(:perform_async).with(hook.id, an_instance_of(Hash), 'feature_flag_hooks')
feature_flag.execute_hooks(user)
feature_flag.touch
end
end
end end
...@@ -100,6 +100,13 @@ RSpec.describe FeatureFlags::UpdateService do ...@@ -100,6 +100,13 @@ RSpec.describe FeatureFlags::UpdateService do
include('Updated active from <strong>"true"</strong> to <strong>"false"</strong>.') include('Updated active from <strong>"true"</strong> to <strong>"false"</strong>.')
) )
end end
it 'executes hooks' do
hook = create(:project_hook, :all_events_enabled, project: project)
expect(WebHookWorker).to receive(:perform_async).with(hook.id, an_instance_of(Hash), 'feature_flag_hooks')
subject
end
end end
context 'when scope active state is changed' do context 'when scope active state is changed' do
......
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