Commit e0f64d91 authored by Francisco Javier López's avatar Francisco Javier López

Merge branch 'vij-refactor-namespace-statistics-ce' into 'master'

Refactor NamespaceStatistics into CE

See merge request gitlab-org/gitlab!79657
parents 7ac35d76 2bae697a
......@@ -43,6 +43,7 @@ class Namespace < ApplicationRecord
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics
has_one :namespace_settings, inverse_of: :namespace, class_name: 'NamespaceSetting', autosave: true
has_one :namespace_statistics
has_one :namespace_route, foreign_key: :namespace_id, autosave: false, inverse_of: :namespace, class_name: 'Route'
has_many :namespace_members, foreign_key: :member_namespace_id, inverse_of: :member_namespace, class_name: 'Member'
......
......@@ -27,10 +27,17 @@ class Namespace::RootStorageStatistics < ApplicationRecord
update!(merged_attributes)
end
def self.namespace_statistics_attributes
%w(storage_size dependency_proxy_size)
end
private
def merged_attributes
attributes_from_project_statistics.merge!(attributes_from_personal_snippets) { |key, v1, v2| v1 + v2 }
attributes_from_project_statistics.merge!(
attributes_from_personal_snippets,
attributes_from_namespace_statistics
) { |key, v1, v2| v1 + v2 }
end
def attributes_from_project_statistics
......@@ -68,6 +75,27 @@ class Namespace::RootStorageStatistics < ApplicationRecord
.where(author: namespace.owner_id)
.select("COALESCE(SUM(s.repository_size), 0) AS #{SNIPPETS_SIZE_STAT_NAME}")
end
def from_namespace_statistics
namespace
.self_and_descendants
.joins("INNER JOIN namespace_statistics ns ON ns.namespace_id = namespaces.id")
.select(
'COALESCE(SUM(ns.storage_size), 0) AS storage_size',
'COALESCE(SUM(ns.dependency_proxy_size), 0) AS dependency_proxy_size'
)
end
def attributes_from_namespace_statistics
# At the moment, only groups can have some storage data because of dependency proxy assets.
# Therefore, if the namespace is not a group one, there is no need to perform
# the query. If this changes in the future and we add some sort of resource to
# users that it's store in NamespaceStatistics, we will need to remove this
# guard clause.
return {} unless namespace.group_namespace?
from_namespace_statistics.take.slice(*self.class.namespace_statistics_attributes)
end
end
Namespace::RootStorageStatistics.prepend_mod_with('Namespace::RootStorageStatistics')
# frozen_string_literal: true
class NamespaceStatistics < ApplicationRecord
class NamespaceStatistics < ApplicationRecord # rubocop:disable Gitlab/NamespacedClass
include AfterCommitQueue
belongs_to :namespace
......@@ -15,13 +15,11 @@ class NamespaceStatistics < ApplicationRecord
delegate :group_namespace?, to: :namespace
COLUMNS_TO_REFRESH = [:wiki_size].freeze
def refresh!(only: [])
return if Gitlab::Database.read_only?
return unless group_namespace?
COLUMNS_TO_REFRESH.each do |column|
self.class.columns_to_refresh.each do |column|
if only.empty? || only.include?(column)
public_send("update_#{column}") # rubocop:disable GitlabSecurity/PublicSend
end
......@@ -31,21 +29,21 @@ class NamespaceStatistics < ApplicationRecord
end
def update_storage_size
self.storage_size = wiki_size
self.storage_size = dependency_proxy_size
end
def update_wiki_size
return unless group_wiki_available?
def update_dependency_proxy_size
return unless group_namespace?
self.wiki_size = namespace.wiki.repository.size.megabytes
self.dependency_proxy_size = namespace.dependency_proxy_manifests.sum(:size) + namespace.dependency_proxy_blobs.sum(:size)
end
private
def group_wiki_available?
group_namespace? && namespace.feature_available?(:group_wikis)
def self.columns_to_refresh
[:dependency_proxy_size]
end
private
def update_root_storage_statistics
return unless group_namespace?
......@@ -54,3 +52,5 @@ class NamespaceStatistics < ApplicationRecord
end
end
end
NamespaceStatistics.prepend_mod_with('NamespaceStatistics')
......@@ -23,7 +23,6 @@ module EE
prepended do
include EachBatch
has_one :namespace_statistics
has_one :namespace_limit, inverse_of: :namespace
has_one :gitlab_subscription
has_one :elasticsearch_indexed_namespace
......
......@@ -6,34 +6,20 @@ module EE
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do
NAMESPACE_STATISTICS_ATTRIBUTES = %w[storage_size wiki_size].freeze
end
class_methods do
extend ::Gitlab::Utils::Override
override :merged_attributes
def merged_attributes
super.merge!(attributes_from_namespace_statistics) { |key, v1, v2| v1 + v2 }
override :namespace_statistics_attributes
def namespace_statistics_attributes
super << 'wiki_size'
end
end
private
def attributes_from_namespace_statistics
# At the moment, only groups can have some storage data because of group wikis.
# Therefore, if the namespace is not a group one, there is no need to perform
# the query. If this changes in the future and we add some sort of resource to
# users that it's store in NamespaceStatistics, we will need to remove this
# guard clause.
return {} unless namespace.group_namespace?
from_namespace_statistics.take.slice(*NAMESPACE_STATISTICS_ATTRIBUTES)
end
override :from_namespace_statistics
def from_namespace_statistics
namespace
.self_and_descendants
.joins("INNER JOIN namespace_statistics ns ON ns.namespace_id = namespaces.id")
.select(
'COALESCE(SUM(ns.storage_size), 0) AS storage_size',
super.select(
'COALESCE(SUM(ns.wiki_size), 0) AS wiki_size'
)
end
......
# frozen_string_literal: true
module EE
module NamespaceStatistics
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
def update_storage_size
super
self.storage_size += wiki_size
end
def update_wiki_size
return unless group_wiki_available?
self.wiki_size = namespace.wiki.repository.size.megabytes
end
class_methods do
extend ::Gitlab::Utils::Override
override :columns_to_refresh
def columns_to_refresh
super << :wiki_size
end
end
private
def group_wiki_available?
group_namespace? && namespace.licensed_feature_available?(:group_wikis)
end
end
end
......@@ -22,7 +22,7 @@ RSpec.describe EE::Namespace::RootStorageStatistics do
let(:namespace) { root_group }
it 'aggregates namespace statistics' do
it 'aggregates namespace wiki statistics' do
# This group is not a descendant of the root_group so it shouldn't be included in the final stats.
other_group = create(:group)
create(:namespace_statistics, namespace: other_group, storage_size: 500, wiki_size: 500)
......@@ -31,23 +31,9 @@ RSpec.describe EE::Namespace::RootStorageStatistics do
root_storage_statistics.reload
total_repository_size = project_stat1.repository_size + project_stat2.repository_size
total_lfs_objects_size = project_stat1.lfs_objects_size + project_stat2.lfs_objects_size
total_build_artifacts_size = project_stat1.build_artifacts_size + project_stat2.build_artifacts_size
total_packages_size = project_stat1.packages_size + project_stat2.packages_size
total_snippets_size = project_stat1.snippets_size + project_stat2.snippets_size
total_pipeline_artifacts_size = project_stat1.pipeline_artifacts_size + project_stat2.pipeline_artifacts_size
total_uploads_size = project_stat1.uploads_size + project_stat2.uploads_size
total_wiki_size = project_stat1.wiki_size + project_stat2.wiki_size + root_namespace_stat.wiki_size + group1_namespace_stat.wiki_size + group2_namespace_stat.wiki_size + subgroup1_namespace_stat.wiki_size
total_storage_size = project_stat1.storage_size + project_stat2.storage_size + root_namespace_stat.storage_size + group1_namespace_stat.storage_size + group2_namespace_stat.storage_size + subgroup1_namespace_stat.storage_size
expect(root_storage_statistics.repository_size).to eq(total_repository_size)
expect(root_storage_statistics.lfs_objects_size).to eq(total_lfs_objects_size)
expect(root_storage_statistics.build_artifacts_size).to eq(total_build_artifacts_size)
expect(root_storage_statistics.packages_size).to eq(total_packages_size)
expect(root_storage_statistics.snippets_size).to eq(total_snippets_size)
expect(root_storage_statistics.pipeline_artifacts_size).to eq(total_pipeline_artifacts_size)
expect(root_storage_statistics.uploads_size).to eq(total_uploads_size)
expect(root_storage_statistics.storage_size).to eq(total_storage_size)
expect(root_storage_statistics.wiki_size).to eq(total_wiki_size)
end
......
......@@ -14,7 +14,6 @@ RSpec.describe Namespace do
let!(:premium_plan) { create(:premium_plan) }
let!(:ultimate_plan) { create(:ultimate_plan) }
it { is_expected.to have_one(:namespace_statistics) }
it { is_expected.to have_one(:namespace_limit) }
it { is_expected.to have_one(:elasticsearch_indexed_namespace) }
it { is_expected.to have_one :upcoming_reconciliation }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe NamespaceStatistics do
include WikiHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:group_wiki) do
create(:group_wiki, group: group).tap do |group_wiki|
group_wiki.create_page('home', 'test content')
end
end
describe '#refresh!' do
let(:namespace) { group }
let(:statistics) { create(:namespace_statistics, namespace: namespace) }
let(:columns) { [] }
subject(:refresh!) { statistics.refresh!(only: columns) }
shared_examples 'creates the namespace statistics' do
specify do
expect(statistics).to receive(:save!)
refresh!
end
end
context 'when no option is passed' do
it 'updates the wiki size' do
expect(statistics).to receive(:update_wiki_size)
refresh!
end
it_behaves_like 'creates the namespace statistics'
end
context 'when wiki_size option is passed' do
let(:columns) { [:wiki_size] }
it 'updates the wiki size' do
expect(statistics).to receive(:update_wiki_size)
refresh!
end
it_behaves_like 'creates the namespace statistics'
end
end
describe '#update_storage_size' do
let_it_be(:statistics, reload: true) { create(:namespace_statistics, namespace: group, dependency_proxy_size: 2, storage_size: 2) }
it 'adds wiki_size to the storage_size' do
statistics.wiki_size = 3
statistics.update_storage_size
expect(statistics.storage_size).to eq 5
end
end
describe '#update_wiki_size' do
let_it_be(:statistics, reload: true) { create(:namespace_statistics, namespace: group) }
subject(:update_wiki_size) { statistics.update_wiki_size }
context 'when group_wikis feature is not enabled' do
it 'does not update the wiki size' do
stub_group_wikis(false)
update_wiki_size
expect(statistics.wiki_size).to be_zero
end
end
context 'when group_wikis feature is enabled' do
before do
stub_group_wikis(true)
end
it 'updates the wiki size' do
update_wiki_size
expect(statistics.wiki_size).to eq group.wiki.repository.size.megabytes.to_i
end
context 'when namespace does not belong to a group' do
let(:statistics) { create(:namespace_statistics, namespace: user.namespace) }
it 'does not update the wiki size' do
expect(statistics).not_to receive(:wiki)
update_wiki_size
expect(statistics.wiki_size).to be_zero
end
end
end
end
end
......@@ -28,24 +28,24 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
let(:project1) { create(:project, namespace: namespace) }
let(:project2) { create(:project, namespace: namespace) }
let!(:stat1) { create(:project_statistics, project: project1, with_data: true, size_multiplier: 100) }
let!(:stat2) { create(:project_statistics, project: project2, with_data: true, size_multiplier: 200) }
let!(:project_stat1) { create(:project_statistics, project: project1, with_data: true, size_multiplier: 100) }
let!(:project_stat2) { create(:project_statistics, project: project2, with_data: true, size_multiplier: 200) }
shared_examples 'data refresh' do
shared_examples 'project data refresh' do
it 'aggregates project statistics' do
root_storage_statistics.recalculate!
root_storage_statistics.reload
total_repository_size = stat1.repository_size + stat2.repository_size
total_wiki_size = stat1.wiki_size + stat2.wiki_size
total_lfs_objects_size = stat1.lfs_objects_size + stat2.lfs_objects_size
total_build_artifacts_size = stat1.build_artifacts_size + stat2.build_artifacts_size
total_packages_size = stat1.packages_size + stat2.packages_size
total_storage_size = stat1.storage_size + stat2.storage_size
total_snippets_size = stat1.snippets_size + stat2.snippets_size
total_pipeline_artifacts_size = stat1.pipeline_artifacts_size + stat2.pipeline_artifacts_size
total_uploads_size = stat1.uploads_size + stat2.uploads_size
total_repository_size = project_stat1.repository_size + project_stat2.repository_size
total_wiki_size = project_stat1.wiki_size + project_stat2.wiki_size
total_lfs_objects_size = project_stat1.lfs_objects_size + project_stat2.lfs_objects_size
total_build_artifacts_size = project_stat1.build_artifacts_size + project_stat2.build_artifacts_size
total_packages_size = project_stat1.packages_size + project_stat2.packages_size
total_storage_size = project_stat1.storage_size + project_stat2.storage_size
total_snippets_size = project_stat1.snippets_size + project_stat2.snippets_size
total_pipeline_artifacts_size = project_stat1.pipeline_artifacts_size + project_stat2.pipeline_artifacts_size
total_uploads_size = project_stat1.uploads_size + project_stat2.uploads_size
expect(root_storage_statistics.repository_size).to eq(total_repository_size)
expect(root_storage_statistics.wiki_size).to eq(total_wiki_size)
......@@ -83,7 +83,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
end
end
it_behaves_like 'data refresh'
it_behaves_like 'project data refresh'
it_behaves_like 'does not include personal snippets'
context 'with subgroups' do
......@@ -93,19 +93,81 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
let(:project1) { create(:project, namespace: subgroup1) }
let(:project2) { create(:project, namespace: subgroup2) }
it_behaves_like 'data refresh'
it_behaves_like 'project data refresh'
it_behaves_like 'does not include personal snippets'
end
context 'with a group namespace' do
let_it_be(:root_group) { create(:group) }
let_it_be(:group1) { create(:group, parent: root_group) }
let_it_be(:subgroup1) { create(:group, parent: group1) }
let_it_be(:group2) { create(:group, parent: root_group) }
let_it_be(:root_namespace_stat) { create(:namespace_statistics, namespace: root_group, storage_size: 100, dependency_proxy_size: 100) }
let_it_be(:group1_namespace_stat) { create(:namespace_statistics, namespace: group1, storage_size: 200, dependency_proxy_size: 200) }
let_it_be(:group2_namespace_stat) { create(:namespace_statistics, namespace: group2, storage_size: 300, dependency_proxy_size: 300) }
let_it_be(:subgroup1_namespace_stat) { create(:namespace_statistics, namespace: subgroup1, storage_size: 300, dependency_proxy_size: 100) }
let(:namespace) { root_group }
it 'aggregates namespace statistics' do
# This group is not a descendant of the root_group so it shouldn't be included in the final stats.
other_group = create(:group)
create(:namespace_statistics, namespace: other_group, storage_size: 500, dependency_proxy_size: 500)
root_storage_statistics.recalculate!
total_repository_size = project_stat1.repository_size + project_stat2.repository_size
total_lfs_objects_size = project_stat1.lfs_objects_size + project_stat2.lfs_objects_size
total_build_artifacts_size = project_stat1.build_artifacts_size + project_stat2.build_artifacts_size
total_packages_size = project_stat1.packages_size + project_stat2.packages_size
total_snippets_size = project_stat1.snippets_size + project_stat2.snippets_size
total_pipeline_artifacts_size = project_stat1.pipeline_artifacts_size + project_stat2.pipeline_artifacts_size
total_uploads_size = project_stat1.uploads_size + project_stat2.uploads_size
total_wiki_size = project_stat1.wiki_size + project_stat2.wiki_size
total_dependency_proxy_size = root_namespace_stat.dependency_proxy_size + group1_namespace_stat.dependency_proxy_size + group2_namespace_stat.dependency_proxy_size + subgroup1_namespace_stat.dependency_proxy_size
total_storage_size = project_stat1.storage_size + project_stat2.storage_size + root_namespace_stat.storage_size + group1_namespace_stat.storage_size + group2_namespace_stat.storage_size + subgroup1_namespace_stat.storage_size
expect(root_storage_statistics.repository_size).to eq(total_repository_size)
expect(root_storage_statistics.lfs_objects_size).to eq(total_lfs_objects_size)
expect(root_storage_statistics.build_artifacts_size).to eq(total_build_artifacts_size)
expect(root_storage_statistics.packages_size).to eq(total_packages_size)
expect(root_storage_statistics.snippets_size).to eq(total_snippets_size)
expect(root_storage_statistics.pipeline_artifacts_size).to eq(total_pipeline_artifacts_size)
expect(root_storage_statistics.uploads_size).to eq(total_uploads_size)
expect(root_storage_statistics.dependency_proxy_size).to eq(total_dependency_proxy_size)
expect(root_storage_statistics.wiki_size).to eq(total_wiki_size)
expect(root_storage_statistics.storage_size).to eq(total_storage_size)
end
it 'works when there are no namespace statistics' do
NamespaceStatistics.delete_all
root_storage_statistics.recalculate!
total_storage_size = project_stat1.storage_size + project_stat2.storage_size
expect(root_storage_statistics.storage_size).to eq(total_storage_size)
end
end
context 'with a personal namespace' do
let_it_be(:user) { create(:user) }
let(:namespace) { user.namespace }
it_behaves_like 'data refresh'
it_behaves_like 'project data refresh'
it 'does not aggregate namespace statistics' do
create(:namespace_statistics, namespace: user.namespace, storage_size: 200, dependency_proxy_size: 200)
root_storage_statistics.recalculate!
expect(root_storage_statistics.storage_size).to eq(project_stat1.storage_size + project_stat2.storage_size)
expect(root_storage_statistics.dependency_proxy_size).to eq(0)
end
context 'when user has personal snippets' do
let(:total_project_snippets_size) { stat1.snippets_size + stat2.snippets_size }
let(:total_project_snippets_size) { project_stat1.snippets_size + project_stat2.snippets_size }
it 'aggregates personal and project snippets size' do
# This is just a a snippet authored by other user
......
......@@ -23,6 +23,7 @@ RSpec.describe Namespace do
it { is_expected.to have_one :root_storage_statistics }
it { is_expected.to have_one :aggregation_schedule }
it { is_expected.to have_one :namespace_settings }
it { is_expected.to have_one(:namespace_statistics) }
it { is_expected.to have_many :custom_emoji }
it { is_expected.to have_one :package_setting_relation }
it { is_expected.to have_one :onboarding_progress }
......
......@@ -3,15 +3,8 @@
require 'spec_helper'
RSpec.describe NamespaceStatistics do
include WikiHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:group_wiki) do
create(:group_wiki, group: group).tap do |group_wiki|
group_wiki.create_page('home', 'test content')
end
end
it { is_expected.to belong_to(:namespace) }
......@@ -22,7 +15,7 @@ RSpec.describe NamespaceStatistics do
let(:statistics) { create(:namespace_statistics, namespace: namespace) }
let(:columns) { [] }
subject { statistics.refresh!(only: columns) }
subject(:refresh!) { statistics.refresh!(only: columns) }
context 'when database is read_only' do
it 'does not save the object' do
......@@ -30,7 +23,7 @@ RSpec.describe NamespaceStatistics do
expect(statistics).not_to receive(:save!)
subject
refresh!
end
end
......@@ -40,7 +33,7 @@ RSpec.describe NamespaceStatistics do
it 'does not save the object' do
expect(statistics).not_to receive(:save!)
subject
refresh!
end
end
......@@ -48,7 +41,7 @@ RSpec.describe NamespaceStatistics do
specify do
expect(statistics).to receive(:save!)
subject
refresh!
end
end
......@@ -56,31 +49,32 @@ RSpec.describe NamespaceStatistics do
let(:columns) { [:foo] }
it 'does not update any column' do
expect(statistics).not_to receive(:update_wiki_size)
create(:dependency_proxy_manifest, group: namespace, size: 50)
subject
expect(statistics).not_to receive(:update_dependency_proxy_size)
expect { refresh! }.not_to change { statistics.reload.storage_size }
end
it_behaves_like 'creates the namespace statistics'
end
context 'when no option is passed' do
it 'updates the wiki size' do
expect(statistics).to receive(:update_wiki_size)
it 'updates the dependency proxy size' do
expect(statistics).to receive(:update_dependency_proxy_size)
subject
refresh!
end
it_behaves_like 'creates the namespace statistics'
end
context 'when wiki_size option is passed' do
let(:columns) { [:wiki_size] }
context 'when dependency_proxy_size option is passed' do
let(:columns) { [:dependency_proxy_size] }
it 'updates the wiki size' do
expect(statistics).to receive(:update_wiki_size)
it 'updates the dependency proxy size' do
expect(statistics).to receive(:update_dependency_proxy_size)
subject
refresh!
end
it_behaves_like 'creates the namespace statistics'
......@@ -90,8 +84,8 @@ RSpec.describe NamespaceStatistics do
describe '#update_storage_size' do
let_it_be(:statistics, reload: true) { create(:namespace_statistics, namespace: group) }
it 'sets storage_size to the wiki_size' do
statistics.wiki_size = 3
it 'sets storage_size to the dependency_proxy_size' do
statistics.dependency_proxy_size = 3
statistics.update_storage_size
......@@ -99,48 +93,32 @@ RSpec.describe NamespaceStatistics do
end
end
describe '#update_wiki_size' do
describe '#update_dependency_proxy_size' do
let_it_be(:statistics, reload: true) { create(:namespace_statistics, namespace: group) }
let_it_be(:dependency_proxy_manifest) { create(:dependency_proxy_manifest, group: group, size: 50) }
let_it_be(:dependency_proxy_blob) { create(:dependency_proxy_blob, group: group, size: 50) }
subject { statistics.update_wiki_size }
context 'when group_wikis feature is not enabled' do
it 'does not update the wiki size' do
stub_group_wikis(false)
subject(:update_dependency_proxy_size) { statistics.update_dependency_proxy_size }
subject
it 'updates the dependency proxy size' do
update_dependency_proxy_size
expect(statistics.wiki_size).to be_zero
end
end
context 'when group_wikis feature is enabled enabled' do
before do
stub_group_wikis(true)
end
it 'updates the wiki size' do
subject
expect(statistics.wiki_size).to eq group.wiki.repository.size.megabytes.to_i
expect(statistics.dependency_proxy_size).to eq 100
end
context 'when namespace does not belong to a group' do
let(:statistics) { create(:namespace_statistics, namespace: user.namespace) }
it 'does not update the wiki size' do
expect(statistics).not_to receive(:wiki)
subject
it 'does not update the dependency proxy size' do
update_dependency_proxy_size
expect(statistics.wiki_size).to be_zero
end
expect(statistics.dependency_proxy_size).to be_zero
end
end
end
context 'before saving statistics' do
let(:statistics) { create(:namespace_statistics, namespace: group, wiki_size: 10) }
let(:statistics) { create(:namespace_statistics, namespace: group, dependency_proxy_size: 10) }
it 'updates storage size' do
expect(statistics).to receive(:update_storage_size).and_call_original
......@@ -167,9 +145,9 @@ RSpec.describe NamespaceStatistics do
context 'when storage_size is updated' do
before do
# we have to update this value instead of `storage_size` because the before_save
# hook we have. If we don't do it, storage_size will be set to the wiki_size value
# hook we have. If we don't do it, storage_size will be set to the dependency_proxy_size value
# which is 0.
statistics.wiki_size = 10
statistics.dependency_proxy_size = 10
end
it 'enqueues the job to update root storage statistics' do
......@@ -193,7 +171,7 @@ RSpec.describe NamespaceStatistics do
context 'when other columns are updated' do
it 'does not enqueue the job to update root storage statistics' do
columns_to_update = NamespaceStatistics.columns_hash.except('id', 'namespace_id', 'wiki_size', 'storage_size').keys
columns_to_update = NamespaceStatistics.columns_hash.reject { |k, _| %w(id namespace_id).include?(k) || k.include?('_size') }.keys
columns_to_update.each { |c| statistics[c] = 10 }
expect(statistics).not_to receive(:update_root_storage_statistics)
......
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