Commit 2dbbe9ae authored by Peter Leitzen's avatar Peter Leitzen

Trigger publish only for eligible issues

parent ce33b660
# frozen_string_literal: true
module StatusPage
# Note: Any new fields exposures should also be added to
# +StatusPage::TriggerPublishService::PUBLISH_WHEN_ISSUE_CHANGED+.
class IncidentEntity < Grape::Entity
expose :iid, as: :id
expose :state, as: :status
......
......@@ -3,24 +3,37 @@
module StatusPage
# Triggers a background job to publish of incidents to the status page.
#
# Use this service when issues/notes/emoji have changed to kickoff the
# publish process.
# This service determines whether the passed +triggered_by+ (issue, note,
# or emoji) is eligible to kick-off the publish process.
class TriggerPublishService
def initialize(user:, project:)
@user = user
include Gitlab::Utils::StrongMemoize
# Publish status page only if the following issue attributes have changed.
# If we expose new fields in +StatusPage::IncidentEntity+ add them to
# this list too.
#
# Note: `closed_by_id` is needed because we cannot rely on `state_id` in
# Issues::CloseService
PUBLISH_WHEN_ISSUE_CHANGED =
%w[title description confidential state_id closed_by_id].freeze
def initialize(project, user, triggered_by)
@project = project
@user = user
@triggered_by = triggered_by
end
def execute(issue_id)
def execute
return unless can_publish?
return unless status_page_enabled?
return unless issue_id
StatusPage::PublishWorker.perform_async(user.id, project.id, issue_id)
end
private
attr_reader :user, :project
attr_reader :user, :project, :triggered_by
def can_publish?
user&.can?(:publish_status_page, project)
......@@ -29,5 +42,28 @@ module StatusPage
def status_page_enabled?
project.status_page_setting&.enabled?
end
def issue_id
strong_memoize(:issue_id) { eligable_issue_id }
end
def eligable_issue_id
case triggered_by
when Issue then eligable_issue_id_from_issue
else
raise ArgumentError, "unsupported trigger type #{triggered_by.class}"
end
end
def eligable_issue_id_from_issue
changes = triggered_by.previous_changes.keys & PUBLISH_WHEN_ISSUE_CHANGED
return if changes.none?
# Ignore updates for already confidential issues
# Note: Issues becoming confidential _will_ be unpublished.
return if triggered_by.confidential? && changes.exclude?('confidential')
triggered_by.id
end
end
end
......@@ -5,79 +5,135 @@ require 'spec_helper'
describe StatusPage::TriggerPublishService do
let_it_be(:user) { create(:user) }
let_it_be(:project, refind: true) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let(:service) { described_class.new(user: user, project: project) }
let(:worker) { StatusPage::PublishWorker }
let_it_be(:status_page_setting) do
create(:status_page_setting, :enabled, project: project)
end
subject { service.execute(issue.id) }
let(:service) { described_class.new(project, user, triggered_by) }
shared_examples 'no job scheduled' do
it 'does not schedule a job' do
expect(worker).not_to receive(:perform_async)
describe '#execute' do
# Variables used by shared examples
let(:execute) { subject }
let(:issue_id) { triggered_by.id }
subject
let_it_be(:status_page_setting, reload: true) do
create(:status_page_setting, :enabled, project: project)
end
end
describe '#execute' do
before do
project.add_maintainer(user)
stub_feature_flags(status_page: true)
stub_licensed_features(status_page: true)
subject { service.execute }
allow(worker).to receive(:perform_async)
.with(user.id, project.id, issue.id)
end
describe 'triggered by issue' do
let_it_be(:triggered_by, reload: true) { create(:issue, project: project) }
it 'schedules a job' do
expect(worker).to receive(:perform_async)
.with(user.id, project.id, issue.id)
using RSpec::Parameterized::TableSyntax
subject
end
where(:changes, :shared_example_name) do
{ weight: 23 } | 'no trigger status page publish'
{ title: 'changed' } | 'trigger status page publish'
{ description: 'changed' } | 'trigger status page publish'
{ confidential: true } | 'trigger status page publish'
end
context 'when status page is missing' do
before do
status_page_setting.destroy
with_them do
include_examples params[:shared_example_name] do
before do
triggered_by.update!(changes)
end
end
end
include_examples 'no job scheduled'
end
context 'without changes' do
include_examples 'no trigger status page publish'
end
context 'when status page is not enabled' do
before do
status_page_setting.update!(enabled: false)
context 'when a confidential issue changes' do
let(:triggered_by) { create(:issue, :confidential, project: project) }
include_examples 'no trigger status page publish' do
before do
triggered_by.update!(title: 'changed')
end
end
end
include_examples 'no job scheduled'
end
context 'when closing an issue' do
include_examples 'trigger status page publish' do
before do
# Mimic Issues::CloseService#close_issue
triggered_by.close!
triggered_by.update!(closed_by: user)
end
end
end
context 'when license is not available' do
before do
stub_licensed_features(status_page: false)
context 'when reopening an issue' do
include_examples 'trigger status page publish' do
let_it_be(:triggered_by) { create(:issue, :closed, project: project) }
before do
triggered_by.reopen!
end
end
end
end
describe 'triggered by unsupported type' do
context 'for some abitary type' do
let(:triggered_by) { Object.new }
include_context 'status page enabled'
include_examples 'no job scheduled'
it 'raises ArgumentError' do
expect { subject }
.to raise_error(ArgumentError, 'unsupported trigger type Object')
end
end
end
context 'when feature is disabled' do
before do
stub_feature_flags(status_page: false)
context 'with eligable triggered_by' do
let_it_be(:triggered_by) { create(:issue, project: project) }
context 'when eligable' do
include_examples 'trigger status page publish'
end
include_examples 'no job scheduled'
end
context 'when status page is missing' do
include_examples 'no trigger status page publish' do
before do
project.status_page_setting.destroy
project.reload
end
end
end
context 'when status page is not enabled' do
include_examples 'no trigger status page publish' do
before do
project.status_page_setting.update!(enabled: false)
end
end
end
context 'when user cannot publish status page' do
before do
project.add_reporter(user)
context 'when license is not available' do
include_examples 'no trigger status page publish' do
before do
stub_licensed_features(status_page: false)
end
end
end
include_examples 'no job scheduled'
context 'when feature is disabled' do
include_examples 'no trigger status page publish' do
before do
stub_feature_flags(status_page: false)
end
end
end
context 'when user cannot publish status page' do
include_examples 'no trigger status page publish' do
before do
project.add_reporter(user)
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