Commit 6510991f authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'storage-usage-email-notification-service' into 'master'

Add Storage Usage Email Notification Service

See merge request gitlab-org/gitlab!83222
parents a5ae176c 87b0a87c
...@@ -23,6 +23,14 @@ class Namespace::RootStorageStatistics < ApplicationRecord ...@@ -23,6 +23,14 @@ class Namespace::RootStorageStatistics < ApplicationRecord
delegate :all_projects, to: :namespace delegate :all_projects, to: :namespace
enum notification_level: {
storage_remaining: 100,
caution: 30,
warning: 15,
danger: 5,
exceeded: 0
}, _prefix: true
def recalculate! def recalculate!
update!(merged_attributes) update!(merged_attributes)
end end
......
# frozen_string_literal: true
module Namespaces
module Storage
class EmailNotificationService
def initialize(mailer)
@mailer = mailer
end
def execute(namespace)
return unless namespace.root_storage_statistics
root_storage_size = ::Namespace::RootStorageSize.new(namespace)
usage_ratio = root_storage_size.usage_ratio
level = notification_level(usage_ratio)
last_level = namespace.root_storage_statistics.notification_level.to_sym
if level != last_level
send_notification(level, namespace, usage_ratio)
update_notification_level(level, namespace)
end
end
private
attr_reader :mailer
def notification_level(usage_ratio)
case usage_ratio
when 0...0.7 then :storage_remaining
when 0.7...0.85 then :caution
when 0.85...0.95 then :warning
when 0.95...1 then :danger
when 1..Float::INFINITY then :exceeded
end
end
def send_notification(level, namespace, usage_ratio)
return if level == :storage_remaining
owner_emails = namespace.owners.map(&:email)
if level == :exceeded
mailer.notify_out_of_storage(namespace, owner_emails)
else
percentage = storage_remaining_percentage(usage_ratio)
mailer.notify_limit_warning(namespace, owner_emails, percentage)
end
end
def update_notification_level(level, namespace)
namespace.root_storage_statistics.update!(notification_level: level)
end
def storage_remaining_percentage(usage_ratio)
(100 - usage_ratio * 100).floor
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Namespaces::Storage::EmailNotificationService do
using RSpec::Parameterized::TableSyntax
describe 'execute' do
let(:mailer) { class_double(::Emails::NamespaceStorageUsageMailer) }
let(:service) { described_class.new(mailer) }
context 'in a saas environment', :saas do
let_it_be(:group, refind: true) { create(:group_with_plan, plan: :ultimate_plan) }
let_it_be(:owner) { create(:user) }
before_all do
create(:namespace_root_storage_statistics, namespace: group)
group.add_owner(owner)
end
where(:limit, :used, :last_notification_level, :expected_level) do
100 | 100 | :storage_remaining | :exceeded
100 | 200 | :storage_remaining | :exceeded
100 | 100 | :caution | :exceeded
100 | 100 | :warning | :exceeded
100 | 100 | :danger | :exceeded
end
with_them do
it 'sends an out of storage notification when the namespace runs out of storage' do
set_storage_size_limit(group, megabytes: limit)
set_used_storage(group, megabytes: used)
set_notification_level(last_notification_level)
expect(mailer).to receive(:notify_out_of_storage).with(group, [owner.email])
service.execute(group)
expect(group.root_storage_statistics.reload.notification_level.to_sym).to eq(expected_level)
end
end
where(:limit, :used, :last_notification_level, :expected_percent_remaining, :expected_level) do
100 | 70 | :storage_remaining | 30 | :caution
100 | 85 | :storage_remaining | 15 | :warning
100 | 95 | :storage_remaining | 5 | :danger
100 | 77 | :storage_remaining | 23 | :caution
1000 | 971 | :storage_remaining | 2 | :danger
100 | 85 | :caution | 15 | :warning
100 | 95 | :warning | 5 | :danger
100 | 99 | :exceeded | 1 | :danger
100 | 94 | :danger | 6 | :warning
100 | 84 | :warning | 16 | :caution
end
with_them do
it 'sends a storage limit notification when storage is running low' do
set_storage_size_limit(group, megabytes: limit)
set_used_storage(group, megabytes: used)
set_notification_level(last_notification_level)
expect(mailer).to receive(:notify_limit_warning).with(group, [owner.email], expected_percent_remaining)
service.execute(group)
expect(group.root_storage_statistics.reload.notification_level.to_sym).to eq(expected_level)
end
end
where(:limit, :used, :last_notification_level) do
100 | 5 | :storage_remaining
100 | 69 | :storage_remaining
100 | 69 | :caution
100 | 69 | :warning
100 | 69 | :danger
100 | 69 | :exceeded
1000 | 699 | :exceeded
end
with_them do
it 'does not send an email when there is sufficient storage remaining' do
set_storage_size_limit(group, megabytes: limit)
set_used_storage(group, megabytes: used)
set_notification_level(last_notification_level)
expect(mailer).not_to receive(:notify_out_of_storage)
expect(mailer).not_to receive(:notify_limit_warning)
service.execute(group)
end
end
it 'sends an email to all group owners' do
set_storage_size_limit(group, megabytes: 100)
set_used_storage(group, megabytes: 200)
owner2 = create(:user)
group.add_owner(owner2)
group.add_maintainer(create(:user))
group.add_developer(create(:user))
group.add_reporter(create(:user))
group.add_guest(create(:user))
owner_emails = [owner.email, owner2.email]
expect(mailer).to receive(:notify_out_of_storage).with(group, match_array(owner_emails))
service.execute(group)
end
it 'does not send an out of storage notification twice' do
set_storage_size_limit(group, megabytes: 100)
set_used_storage(group, megabytes: 200)
set_notification_level(:exceeded)
expect(mailer).not_to receive(:notify_out_of_storage)
service.execute(group)
end
where(:limit, :used, :last_notification_level) do
100 | 70 | :caution
100 | 85 | :warning
100 | 95 | :danger
end
with_them do
it 'does not send a storage limit notification for the same threshold twice' do
set_storage_size_limit(group, megabytes: limit)
set_used_storage(group, megabytes: used)
set_notification_level(last_notification_level)
expect(mailer).not_to receive(:notify_limit_warning)
service.execute(group)
end
end
it 'does nothing if there is no defined storage limit' do
set_used_storage(group, megabytes: 150)
expect(mailer).not_to receive(:notify_out_of_storage)
expect(mailer).not_to receive(:notify_limit_warning)
service.execute(group)
expect(group.root_storage_statistics.reload.notification_level).to eq('storage_remaining')
end
it 'does nothing if there is no root_storage_statistics' do
group.root_storage_statistics.destroy!
group.reload
expect(mailer).not_to receive(:notify_out_of_storage)
expect(mailer).not_to receive(:notify_limit_warning)
service.execute(group)
expect(group.reload.root_storage_statistics).to be_nil
end
context 'with a personal namespace' do
let_it_be(:namespace) { create(:namespace_with_plan, plan: :ultimate_plan) }
before_all do
create(:namespace_root_storage_statistics, namespace: namespace)
end
it 'sends a limit notification' do
set_storage_size_limit(namespace, megabytes: 1000)
set_used_storage(namespace, megabytes: 851)
owner = namespace.owner
expect(mailer).to receive(:notify_limit_warning).with(namespace, [owner.email], 14)
service.execute(namespace)
end
it 'sends an out of storage notification' do
set_storage_size_limit(namespace, megabytes: 100)
set_used_storage(namespace, megabytes: 550)
owner = namespace.owner
expect(mailer).to receive(:notify_out_of_storage).with(namespace, [owner.email])
service.execute(namespace)
end
end
end
context 'in a self-managed environment' do
it 'does nothing' do
group = create(:group)
create(:namespace_root_storage_statistics, namespace: group)
owner = create(:user)
group.add_owner(owner)
set_used_storage(group, megabytes: 87)
expect(mailer).not_to receive(:notify_out_of_storage)
expect(mailer).not_to receive(:notify_limit_warning)
service.execute(group)
expect(group.root_storage_statistics.reload.notification_level).to eq('storage_remaining')
end
end
end
def set_storage_size_limit(group, megabytes:)
group.gitlab_subscription.hosted_plan.actual_limits.update!(storage_size_limit: megabytes)
end
def set_used_storage(group, megabytes:)
group.root_storage_statistics.update!(storage_size: megabytes.megabytes)
end
def set_notification_level(level)
group.root_storage_statistics.update!(notification_level: level)
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