Commit 52c0497b authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch 'pl-status-page-publish-incident-service' into 'master'

Publish incident details and list to status page

See merge request gitlab-org/gitlab!26201
parents f96750dd 9576062b
......@@ -126,6 +126,7 @@ class License < ApplicationRecord
report_approver_rules
sast
security_dashboard
status_page
subepics
threat_monitoring
tracing
......
# frozen_string_literal: true
module StatusPage
class PublishBaseService
JSON_MAX_SIZE = 1.megabyte
def initialize(project:, storage_client:, serializer:)
@project = project
@storage_client = storage_client
@serializer = serializer
end
def execute(*args)
return error_feature_not_available unless feature_available?
publish(*args)
end
private
attr_reader :project, :storage_client, :serializer
def publish(*args)
raise NotImplementedError
end
def feature_available?
project.feature_available?(:status_page)
end
def upload(key, json)
return error_limit_exceeded(key) if limit_exceeded?(json)
content = json.to_json
storage_client.upload_object(key, content)
success(object_key: key)
rescue StatusPage::Storage::Error => e
error(e.message, error: e)
end
def limit_exceeded?(json)
!Gitlab::Utils::DeepSize.new(json, max_size: JSON_MAX_SIZE).valid?
end
def error(message, payload = {})
ServiceResponse.error(message: message, payload: payload)
end
def error_limit_exceeded(key)
error("Failed to upload #{key}: Limit exceeded")
end
def error_feature_not_available
error('Feature not available')
end
def success(payload = {})
ServiceResponse.success(payload: payload)
end
end
end
# frozen_string_literal: true
module StatusPage
class PublishDetailsService < PublishBaseService
private
def publish(issue, user_notes)
json = serialize(issue, user_notes)
key = object_key(json)
return error('Missing object key') unless key
upload(key, json)
end
def serialize(issue, user_notes)
serializer.represent_details(issue, user_notes)
end
def object_key(json)
id = json[:id]
return unless id
StatusPage::Storage.details_path(id)
end
end
end
# frozen_string_literal: true
module StatusPage
class PublishListService < PublishBaseService
private
def publish(issues)
json = serialize(issues)
upload(object_key, json)
end
def serialize(issues)
serializer.represent_list(issues)
end
def object_key
StatusPage::Storage.list_path
end
end
end
......@@ -2,6 +2,14 @@
module StatusPage
module Storage
def self.details_path(id)
"incident/#{id}.json"
end
def self.list_path
'list.json'
end
class Error < StandardError
def initialize(bucket:, error:, **args)
super(
......
# frozen_string_literal: true
require 'spec_helper'
describe StatusPage::Storage do
describe '.details_path' do
subject { described_class.details_path(123) }
it { is_expected.to eq('incident/123.json') }
end
describe '.list_path' do
subject { described_class.list_path }
it { is_expected.to eq('list.json') }
end
end
# frozen_string_literal: true
require 'spec_helper'
describe StatusPage::PublishDetailsService do
let_it_be(:project, refind: true) { create(:project) }
let(:storage_client) { instance_double(StatusPage::Storage::S3Client) }
let(:serializer) { instance_double(StatusPage::IncidentSerializer) }
let(:issue) { instance_double(Issue) }
let(:user_notes) { double(:user_notes) }
let(:incident_id) { 1 }
let(:key) { StatusPage::Storage.details_path(incident_id) }
let(:content) { { id: incident_id } }
let(:content_json) { content.to_json }
let(:service) do
described_class.new(
project: project, storage_client: storage_client, serializer: serializer
)
end
subject(:result) { service.execute(issue, user_notes) }
describe '#execute' do
context 'when license is available' do
before do
allow(serializer).to receive(:represent_details).with(issue, user_notes)
.and_return(content)
end
include_examples 'publish incidents'
context 'when serialized content is missing id' do
let(:content) { { other_id: incident_id } }
it 'returns an error' do
expect(result).to be_error
expect(result.message).to eq('Missing object key')
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe StatusPage::PublishListService do
let_it_be(:project, refind: true) { create(:project) }
let(:storage_client) { instance_double(StatusPage::Storage::S3Client) }
let(:serializer) { instance_double(StatusPage::IncidentSerializer) }
let(:issues) { [instance_double(Issue)] }
let(:key) { StatusPage::Storage.list_path }
let(:content) { [{ some: :content }] }
let(:content_json) { content.to_json }
let(:service) do
described_class.new(
project: project, storage_client: storage_client, serializer: serializer
)
end
subject(:result) { service.execute(issues) }
describe '#execute' do
context 'when license is available' do
before do
allow(serializer).to receive(:represent_list).with(issues)
.and_return(content)
end
include_examples 'publish incidents'
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'publish incidents' do
before do
stub_licensed_features(status_page: true)
end
context 'when upload succeeds' do
before do
allow(storage_client).to receive(:upload_object).with(key, content_json)
end
it 'publishes details as JSON' do
expect(result).to be_success
expect(result.payload).to eq(object_key: key)
end
end
context 'when upload fails due to exception' do
let(:bucket) { 'bucket_name' }
let(:error) { StandardError.new }
let(:exception) do
StatusPage::Storage::Error.new(bucket: bucket, error: error)
end
before do
allow(storage_client).to receive(:upload_object).with(key, content_json)
.and_raise(exception)
end
it 'returns an error with exception' do
expect(result).to be_error
expect(result.message).to eq(exception.message)
expect(result.payload).to eq(error: exception)
end
end
context 'when limits exceeded' do
let(:too_big) { 'a' * StatusPage::PublishBaseService::JSON_MAX_SIZE }
before do
if content.is_a?(Array)
content.concat([too_big: too_big])
else
content.merge!(too_big: too_big)
end
end
it 'returns limit exceeded error' do
expect(result).to be_error
expect(result.message).to eq(
"Failed to upload #{key}: Limit exceeded"
)
end
end
context 'when feature is not available' do
before do
stub_licensed_features(status_page: false)
end
it 'returns feature not available error' do
expect(result).to be_error
expect(result.message).to eq('Feature not available')
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