diff --git a/ee/lib/ee/web_ide/extensions_marketplace.rb b/ee/lib/ee/web_ide/extensions_marketplace.rb new file mode 100644 index 0000000000000000000000000000000000000000..fd9045fe21e5cd00c3e632984ffc52c859d5a4db --- /dev/null +++ b/ee/lib/ee/web_ide/extensions_marketplace.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module EE + module WebIde + module ExtensionsMarketplace + extend ActiveSupport::Concern + + class_methods do + extend ::Gitlab::Utils::Override + + override :gallery_disabled_extra_attributes + def gallery_disabled_extra_attributes(disabled_reason:, user:) + return enterprise_group_disabled_attributes(user) if disabled_reason == :enterprise_group_disabled + + super + end + + private + + def enterprise_group_disabled_attributes(user) + group = user.enterprise_group + + { + enterprise_group_name: group.full_name, + enterprise_group_url: ::Gitlab::Routing.url_helpers.group_url(group) + } + end + end + end + end +end diff --git a/ee/lib/ee/web_ide/settings/extensions_gallery_metadata_generator.rb b/ee/lib/ee/web_ide/settings/extensions_gallery_metadata_generator.rb new file mode 100644 index 0000000000000000000000000000000000000000..aa6b0ffcfc9cf676af3e5fe730052107daa9a572 --- /dev/null +++ b/ee/lib/ee/web_ide/settings/extensions_gallery_metadata_generator.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module EE + module WebIde + module Settings + module ExtensionsGalleryMetadataGenerator + extend ActiveSupport::Concern + + # NOTE: Please see the note for DISABLED_REASONS in the relevant CE module + EE_DISABLED_REASONS = %i[ + enterprise_group_disabled + ].to_h { |reason| [reason, reason] }.freeze + + class_methods do + extend ::Gitlab::Utils::Override + + override :disabled_reasons + def disabled_reasons + super.merge(EE_DISABLED_REASONS).freeze + end + + override :build_metadata_for_user + def build_metadata_for_user(user) + return metadata_disabled(:enterprise_group_disabled) unless enabled_for_enterprise_group?(user) + + super + end + + private + + def enabled_for_enterprise_group?(user) + return true unless user.enterprise_user? && user.enterprise_group + + user.enterprise_group.enterprise_users_extensions_marketplace_enabled? + end + end + end + end + end +end diff --git a/ee/spec/lib/ee/web_ide/extensions_marketplace_spec.rb b/ee/spec/lib/ee/web_ide/extensions_marketplace_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9630687ec895f8ccf8698fb7f5e81b5998b17771 --- /dev/null +++ b/ee/spec/lib/ee/web_ide/extensions_marketplace_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WebIde::ExtensionsMarketplace, feature_category: :web_ide do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:group) { create(:group) } + let_it_be_with_reload(:current_user) { create(:user) } + + describe '#webide_extensions_gallery_settings' do + let_it_be(:enabled_expectation) { a_hash_including(enabled: true) } + let_it_be(:enterprise_disabled_expectation) do + { + enabled: false, + enterprise_group_name: group.full_name, + enterprise_group_url: ::Gitlab::Routing.url_helpers.group_url(group), + help_url: a_string_matching('/help/user/project/web_ide/index#extension-marketplace'), + reason: :enterprise_group_disabled + } + end + + subject(:webide_settings) { described_class.webide_extensions_gallery_settings(user: current_user) } + + where(:enterprise_group, :extensions_enabled, :expectation) do + nil | false | ref(:enabled_expectation) + ref(:group) | false | ref(:enterprise_disabled_expectation) + ref(:group) | true | ref(:enabled_expectation) + end + + with_them do + before do + stub_feature_flags( + web_ide_extensions_marketplace: current_user, + web_ide_oauth: current_user, + vscode_web_ide: current_user + ) + current_user.update!(extensions_marketplace_opt_in_status: :enabled, enterprise_group: enterprise_group) + group.update!(enterprise_users_extensions_marketplace_enabled: extensions_enabled) + end + + it 'returns expected settings' do + expect(webide_settings).to match(expectation) + end + end + end +end diff --git a/ee/spec/lib/ee/web_ide/settings/extensions_gallery_metadata_generator_spec.rb b/ee/spec/lib/ee/web_ide/settings/extensions_gallery_metadata_generator_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d6b6cc7f2eccd0e204def6ff946381ff2f4e6d89 --- /dev/null +++ b/ee/spec/lib/ee/web_ide/settings/extensions_gallery_metadata_generator_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "fast_spec_helper" + +RSpec.describe WebIde::Settings::ExtensionsGalleryMetadataGenerator, :web_ide_fast, feature_category: :web_ide do + using RSpec::Parameterized::TableSyntax + + let(:user_class) { stub_const('User', Class.new) } + let(:group_class) { stub_const('Namespace', Class.new) } + let(:user) { user_class.new } + let(:group) { group_class.new } + let(:input_context) do + { + requested_setting_names: [:vscode_extensions_gallery_metadata], + options: { + user: user, + vscode_extensions_marketplace_feature_flag_enabled: true + }, + settings: {} + } + end + + subject(:actual_settings) do + described_class.generate(input_context).dig(:settings, :vscode_extensions_gallery_metadata) + end + + where( + :enterprise_group, + :enterprise_group_enabled, + :expectation + ) do + nil | false | { enabled: false, disabled_reason: :opt_in_unset } + ref(:group) | false | { enabled: false, disabled_reason: :enterprise_group_disabled } + ref(:group) | true | { enabled: false, disabled_reason: :opt_in_unset } + end + + with_them do + before do + allow(user).to receive(:enterprise_user?).and_return(!!enterprise_group) + allow(user).to receive(:enterprise_group).and_return(enterprise_group) + # note: Leaving user's opt_in unset so we can test that the CE checks are still running + allow(user).to receive(:extensions_marketplace_opt_in_status).and_return(:unset) + allow(group).to receive(:enterprise_users_extensions_marketplace_enabled?).and_return(enterprise_group_enabled) + end + + it "adds settings with disabled reason based on enterprise_group presence and setting" do + expect(actual_settings).to eq(expectation) + end + end +end diff --git a/ee/spec/lib/web_ide/settings/settings_integration_spec.rb b/ee/spec/lib/web_ide/settings/settings_integration_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..bd06b704da04bd412993ab0c14973eaae9d1e35b --- /dev/null +++ b/ee/spec/lib/web_ide/settings/settings_integration_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::WebIde::Settings, feature_category: :web_ide do # rubocop:disable RSpec/FilePath -- This cop fails because the spec is named 'settings_integration_spec.rb' but describes ::WebIde::Settings class. But we want it that way, because it's an integration spec, not a unit spec, but we still want to be able to use `described_class` + let_it_be_with_reload(:group) { create(:group, :private, :nested) } + let_it_be_with_reload(:user) { create(:enterprise_user, enterprise_group: group) } + let_it_be(:options) do + { + user: user, + vscode_extensions_marketplace_feature_flag_enabled: true + } + end + + subject(:settings) { described_class.get([:vscode_extensions_gallery_metadata], options) } + + before do + user.update!(extensions_marketplace_enabled: true) + end + + describe "default - enterprise group has extensions marketplace disabled" do + it do + is_expected.to include(vscode_extensions_gallery_metadata: { + disabled_reason: :enterprise_group_disabled, + enabled: false + }) + end + end + + describe "enterprise group has extensions marketplace enabled" do + before do + group.update!(enterprise_users_extensions_marketplace_enabled: true) + end + + it do + is_expected.to include(vscode_extensions_gallery_metadata: { + enabled: true + }) + end + end +end diff --git a/lib/web_ide/extensions_marketplace.rb b/lib/web_ide/extensions_marketplace.rb index c407c5c886722932bfa670df33c9ab1c0d417709..94a6797572f72087933634c8052180bc5e621e21 100644 --- a/lib/web_ide/extensions_marketplace.rb +++ b/lib/web_ide/extensions_marketplace.rb @@ -53,12 +53,17 @@ def self.webide_extensions_gallery_settings(user:) result = { enabled: false, reason: disabled_reason, help_url: help_url } - if disabled_reason == :opt_in_unset || disabled_reason == :opt_in_disabled - result[:user_preferences_url] = user_preferences_url - end + result.merge(gallery_disabled_extra_attributes(disabled_reason: disabled_reason, user: user)) + end + + # rubocop:disable Lint/UnusedMethodArgument -- `user:` param is used in EE + def self.gallery_disabled_extra_attributes(disabled_reason:, user:) + return { user_preferences_url: user_preferences_url } if disabled_reason == :opt_in_unset + return { user_preferences_url: user_preferences_url } if disabled_reason == :opt_in_disabled - result + {} end + # rubocop:enable Lint/UnusedMethodArgument def self.help_url ::Gitlab::Routing.url_helpers.help_page_url('user/project/web_ide/index', anchor: 'extension-marketplace') @@ -72,3 +77,5 @@ def self.user_preferences_url private_class_method :help_url, :user_preferences_url end end + +WebIde::ExtensionsMarketplace.prepend_mod diff --git a/lib/web_ide/settings/extensions_gallery_metadata_generator.rb b/lib/web_ide/settings/extensions_gallery_metadata_generator.rb index 57cbd28d634c186485393d62af3ca73dc2f1ecb4..19fee10c6e2bb0bc1e52e2e8f39d9fb06031ff9f 100644 --- a/lib/web_ide/settings/extensions_gallery_metadata_generator.rb +++ b/lib/web_ide/settings/extensions_gallery_metadata_generator.rb @@ -9,14 +9,14 @@ class ExtensionsGalleryMetadataGenerator # the "gitlab-web-ide" and "gitlab-web-ide-vscode-fork" projects # (https://gitlab.com/gitlab-org/gitlab-web-ide & https://gitlab.com/gitlab-org/gitlab-web-ide-vscode-fork), # so we must ensure that any changes made here are also reflected in those projects. - DISABLED_REASONS = - %i[ - no_user - no_flag - instance_disabled - opt_in_unset - opt_in_disabled - ].to_h { |reason| [reason, reason] }.freeze + # Please also see EE_DISABLED_REASONS in the relevant EE module. + DISABLED_REASONS = %i[ + no_user + no_flag + instance_disabled + opt_in_unset + opt_in_disabled + ].to_h { |reason| [reason, reason] }.freeze # @param [Hash] context # @return [Hash] @@ -31,7 +31,7 @@ def self.generate(context) extensions_marketplace_feature_flag_enabled } - extensions_gallery_metadata = metadata_for_user( + extensions_gallery_metadata = build_metadata( user: user, flag_enabled: extensions_marketplace_feature_flag_enabled ) @@ -43,11 +43,23 @@ def self.generate(context) # @param [User, nil] user # @param [Boolean, nil] flag_enabled # @return [Hash] - def self.metadata_for_user(user:, flag_enabled:) + def self.build_metadata(user:, flag_enabled:) return metadata_disabled(:no_user) unless user return metadata_disabled(:no_flag) if flag_enabled.nil? return metadata_disabled(:instance_disabled) unless flag_enabled + build_metadata_for_user(user) + end + + def self.disabled_reasons + DISABLED_REASONS + end + + # note: This is overridden in EE + # + # @param [User] user + # @return [Hash] + def self.build_metadata_for_user(user) # noinspection RubyNilAnalysis -- RubyMine doesn't realize user can't be nil because of guard clause above opt_in_status = user.extensions_marketplace_opt_in_status.to_sym @@ -73,10 +85,13 @@ def self.metadata_enabled # @param [symbol] reason # @return [Hash] def self.metadata_disabled(reason) - { enabled: false, disabled_reason: DISABLED_REASONS.fetch(reason) } + { enabled: false, disabled_reason: disabled_reasons.fetch(reason) } end - private_class_method :metadata_for_user, :metadata_enabled, :metadata_disabled + private_class_method :build_metadata, :build_metadata_for_user, :disabled_reasons, :metadata_enabled, + :metadata_disabled end end end + +WebIde::Settings::ExtensionsGalleryMetadataGenerator.prepend_mod diff --git a/spec/lib/web_ide/settings/extensions_gallery_metadata_generator_spec.rb b/spec/lib/web_ide/settings/extensions_gallery_metadata_generator_spec.rb index 4de5ce102a1d54981572ee8d8de2c0b5c8a8ae6c..7a68f0a999805791f07bb0257967c29d59e848e9 100644 --- a/spec/lib/web_ide/settings/extensions_gallery_metadata_generator_spec.rb +++ b/spec/lib/web_ide/settings/extensions_gallery_metadata_generator_spec.rb @@ -77,6 +77,8 @@ before do allow(user).to receive(:extensions_marketplace_opt_in_status) { opt_in_status.to_s } + # EE feature has to be stubbed since we run EE code through CE tests + allow(user).to receive(:enterprise_user?).and_return(false) allow(enums).to receive(:statuses).and_return({ unset: :unset, enabled: :enabled, disabled: :disabled }) end