diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index f611bfacc527375177341bb287272c0f93ae3b57..ef31153232b4c1544ac2c4ff11fe88d92493f747 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -173,12 +173,14 @@ def extensions_marketplace_view end def build_extensions_marketplace_view(title:, message:) - extensions_marketplace_home = "%{linkStart}#{::WebIde::ExtensionMarketplace.marketplace_home_url}%{linkEnd}" + marketplace_home_url = ::WebIde::ExtensionMarketplace.marketplace_home_url(user: current_user) + + extensions_marketplace_home = "%{linkStart}#{marketplace_home_url}%{linkEnd}" { name: 'extensions_marketplace', message: format(message, extensions_marketplace_home: extensions_marketplace_home), title: title, - message_url: WebIde::ExtensionMarketplace.marketplace_home_url, + message_url: marketplace_home_url, help_link: WebIde::ExtensionMarketplace.help_preferences_url } end diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 2b0875fd4b14ef7a828f0d2b40402e9ae79b0b02..b448cf5443f2b869b59f25945bd694c548a8b3dd 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -10,7 +10,7 @@ - rich_text_editor_help_text = s_('Preferences|Type in rich text, as you see it.') - @color_modes = Gitlab::ColorModes::available_modes.to_json - @themes = Gitlab::Themes::available_themes.to_json -- extensions_marketplace_url = ::WebIde::ExtensionMarketplace.marketplace_home_url +- extensions_marketplace_url = ::WebIde::ExtensionMarketplace.marketplace_home_url(user: current_user) - data_attributes = { color_modes: @color_modes, themes: @themes, integration_views: integration_views.to_json, user_fields: user_fields, body_classes: Gitlab::Themes.body_classes, profile_preferences_path: profile_preferences_path, extensions_marketplace_url: extensions_marketplace_url } - @force_desktop_expanded_sidebar = true diff --git a/ee/app/helpers/ee/preferences_helper.rb b/ee/app/helpers/ee/preferences_helper.rb index de40ade83ef3a7f7b65e5ad56cea43d4b25ad86b..cb4b9fd83d2b45738beaeb47f8e93ac7d7cdcc3c 100644 --- a/ee/app/helpers/ee/preferences_helper.rb +++ b/ee/app/helpers/ee/preferences_helper.rb @@ -15,6 +15,8 @@ def excluded_dashboard_choices override :extensions_marketplace_view def extensions_marketplace_view + return unless ::WebIde::ExtensionMarketplace.feature_enabled_from_application_settings?(user: current_user) + if License.feature_available?(:remote_development) && ::WebIde::ExtensionMarketplace.feature_enabled?(user: current_user) build_extensions_marketplace_view( diff --git a/ee/spec/factories/remote_development/workspaces.rb b/ee/spec/factories/remote_development/workspaces.rb index 30a8f19d269c46b3cdeea3c74a511cbde85fee14..c3695013d18bb27663a184138e3e99c6f5209f88 100644 --- a/ee/spec/factories/remote_development/workspaces.rb +++ b/ee/spec/factories/remote_development/workspaces.rb @@ -84,8 +84,7 @@ user_name: workspace.user.name, user_email: workspace.user.email, workspace_id: workspace.id, - vscode_extension_marketplace: - WebIde::Settings::DefaultSettings.default_settings.fetch(:vscode_extension_marketplace).first, + vscode_extension_marketplace: ::WebIde::ExtensionMarketplacePreset.open_vsx.values, variables: [] ) diff --git a/ee/spec/helpers/preferences_helper_spec.rb b/ee/spec/helpers/preferences_helper_spec.rb index a1ce1fc2d202104338263d066b5c1fad2249604e..4088e2d3774ec42d1f8e28aebabe301cc6f6f76f 100644 --- a/ee/spec/helpers/preferences_helper_spec.rb +++ b/ee/spec/helpers/preferences_helper_spec.rb @@ -48,17 +48,34 @@ end describe '#extensions_marketplace_view' do + let(:feature_enabled) { false } + let(:application_setting_enabled) { true } + subject { helper.extensions_marketplace_view } + before do + allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled?) + .with(user: user) + .and_return(feature_enabled) + + allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled_from_application_settings?) + .with(user: user) + .and_return(application_setting_enabled) + end + + context 'when feature not enabled at application_settings' do + let(:application_setting_enabled) { false } + + it { is_expected.to be_nil } + end + context 'when remote_development licensed feature is enabled' do before do stub_licensed_features(remote_development: true) end context 'when Web IDE Extension Marketplace feature is enabled' do - before do - allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled?).with(user: user).and_return(true) - end + let(:feature_enabled) { true } it { is_expected.to match(a_hash_including(title: 'Web IDE and Workspaces', message: /IDE and Workspaces/)) } end @@ -70,9 +87,7 @@ context 'when remote_development licensed feature is not enabled' do context 'when Web IDE Extension Marketplace feature is enabled' do - before do - allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled?).with(user: user).and_return(true) - end + let(:feature_enabled) { true } it { is_expected.to match(a_hash_including(title: 'Web IDE', message: /for the Web IDE/)) } end diff --git a/ee/spec/lib/ee/web_ide/settings/extension_marketplace_metadata_generator_spec.rb b/ee/spec/lib/ee/web_ide/settings/extension_marketplace_metadata_generator_spec.rb index 24be6d450b12a1feebf5b5082cddbbe8227e6957..46fa65abcae089c81e2bd4e06065b351745d26c5 100644 --- a/ee/spec/lib/ee/web_ide/settings/extension_marketplace_metadata_generator_spec.rb +++ b/ee/spec/lib/ee/web_ide/settings/extension_marketplace_metadata_generator_spec.rb @@ -5,7 +5,17 @@ RSpec.describe WebIde::Settings::ExtensionMarketplaceMetadataGenerator, feature_category: :web_ide do using RSpec::Parameterized::TableSyntax - let(:user_class) { stub_const('User', Class.new) } + let(:user_class) do + stub_const( + "User", + Class.new do + def flipper_id + "UserStub" + end + end + ) + end + let(:group_class) { stub_const('Namespace', Class.new) } let(:user) { user_class.new } let(:group) { group_class.new } @@ -43,6 +53,10 @@ extensions_marketplace_opt_in_status: :unset ) allow(group).to receive(:enterprise_users_extensions_marketplace_enabled?).and_return(enterprise_group_enabled) + + allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled_from_application_settings?) + .with(user: user) + .and_return(true) end it "adds settings with disabled reason based on enterprise_group presence and setting" do diff --git a/ee/spec/lib/web_ide/settings/settings_integration_spec.rb b/ee/spec/lib/web_ide/settings/settings_integration_spec.rb index 2fa6a9e107d1f6b8fae7d07afdf1f45b3b364ead..72219f8b1a12bc91d2cd324bd8625a5da37e1b01 100644 --- a/ee/spec/lib/web_ide/settings/settings_integration_spec.rb +++ b/ee/spec/lib/web_ide/settings/settings_integration_spec.rb @@ -15,8 +15,13 @@ subject(:settings) { described_class.get([:vscode_extension_marketplace_metadata], options) } before do - stub_feature_flags(vscode_web_ide: true, web_ide_extensions_marketplace: true) + stub_feature_flags( + vscode_web_ide: true, + web_ide_extensions_marketplace: true, + vscode_extension_marketplace_settings: true + ) stub_licensed_features(disable_extensions_marketplace_for_enterprise_users: true) + stub_application_setting(vscode_extension_marketplace: { enabled: true, preset: 'open_vsx' }) user.update!(extensions_marketplace_enabled: true) end diff --git a/ee/spec/requests/groups_controller_spec.rb b/ee/spec/requests/groups_controller_spec.rb index 7f9b664a5faf075c77e9284c41efe2dcc1a65202..a019df293ab85f2ba11fca00e66fb0b293fbd15a 100644 --- a/ee/spec/requests/groups_controller_spec.rb +++ b/ee/spec/requests/groups_controller_spec.rb @@ -452,6 +452,7 @@ vscode_web_ide: true, web_ide_extensions_marketplace: true ) + stub_application_setting(vscode_extension_marketplace: { enabled: true, preset: 'open_vsx' }) end it 'does not change the column' do diff --git a/lib/web_ide/extension_marketplace.rb b/lib/web_ide/extension_marketplace.rb index 61cd2c63386f89d9e63c5cf70d607ad7782cbe1a..a2da3bb37316548917343359b0b095ac98e85ce6 100644 --- a/lib/web_ide/extension_marketplace.rb +++ b/lib/web_ide/extension_marketplace.rb @@ -2,28 +2,43 @@ module WebIde module ExtensionMarketplace - # This returns true if the extensions marketplace feature is available to any users + # Returns true if the extensions marketplace feature is enabled for any users # # @return [Boolean] def self.feature_enabled_for_any_user? - feature_flag_enabled_for_any_actor?(:web_ide_extensions_marketplace) && + # note: Intentionally pass `nil` here since we don't have a user in scope + feature_enabled_from_application_settings?(user: nil) && + feature_flag_enabled_for_any_actor?(:web_ide_extensions_marketplace) && feature_flag_enabled_for_any_actor?(:vscode_web_ide) end - # This returns true if the extensions marketplace feature is available to the given user + # Returns true if the extensions marketplace feature is enabled for the given user # # @param user [User] # @return [Boolean] def self.feature_enabled?(user:) - Feature.enabled?(:web_ide_extensions_marketplace, user) && - Feature.enabled?(:vscode_web_ide, user) + feature_enabled_from_application_settings?(user: user) && + feature_enabled_from_flags?(user: user) + end + + # Returns true if the ExtensionMarketplace feature is enabled from application settings + # + # @param user [User, nil] Current user for feature enablement context + # @return [Boolean] + def self.feature_enabled_from_application_settings?(user:) + return true unless should_use_application_settings?(user: user) + + Gitlab::CurrentSettings.vscode_extension_marketplace&.fetch('enabled', false) end # This value is used when the end-user is accepting the third-party extension marketplace integration. # + # @param user [User] Current user for context # @return [String] URL of the VSCode Extension Marketplace home - def self.marketplace_home_url - "https://open-vsx.org" + def self.marketplace_home_url(user:) + Gitlab::SafeRequestStore.fetch(:vscode_extension_marketplace_home_url) do + Settings.get_single_setting(:vscode_extension_marketplace_home_url, user: user) + end end # @return [String] URL of the help page for the user preferences for Extensions Marketplace opt-in @@ -43,7 +58,7 @@ def self.webide_extension_marketplace_settings(user:) Settings.get_single_setting( :vscode_extension_marketplace_view_model, user: user, - vscode_extension_marketplace_feature_flag_enabled: feature_enabled?(user: user) + vscode_extension_marketplace_feature_flag_enabled: feature_enabled_from_flags?(user: user) ) end @@ -61,6 +76,28 @@ def self.feature_flag_enabled_for_any_actor?(flag) feature && !feature.off? end - private_class_method :feature_flag_enabled_for_any_actor? + # Returns true if we should use `feature_enabled_from_application_settings?` to determine feature availability + # + # @param user [User, nil] Current user for feature enablement context + # @return [Boolean] + def self.should_use_application_settings?(user:) + if user + Feature.enabled?(:vscode_extension_marketplace_settings, user) + else + feature_flag_enabled_for_any_actor?(:vscode_extension_marketplace_settings) + end + end + + # This returns true if the extensions marketplace flags are enabled + # + # @param user [User] + # @return [Boolean] + def self.feature_enabled_from_flags?(user:) + Feature.enabled?(:web_ide_extensions_marketplace, user) && + Feature.enabled?(:vscode_web_ide, user) + end + + private_class_method :feature_flag_enabled_for_any_actor?, :should_use_application_settings?, + :feature_enabled_from_flags? end end diff --git a/lib/web_ide/extension_marketplace_preset.rb b/lib/web_ide/extension_marketplace_preset.rb new file mode 100644 index 0000000000000000000000000000000000000000..0a42371669a61012018f161d2455992231e17006 --- /dev/null +++ b/lib/web_ide/extension_marketplace_preset.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module WebIde + class ExtensionMarketplacePreset + CUSTOM_KEY = "custom" + + def self.all + [open_vsx] + end + + def self.open_vsx + # note: This is effectively a constant so lets memoize + @open_vsx ||= new( + "open_vsx", + "Open VSX", + # See https://open-vsx.org/swagger-ui/index.html?urls.primaryName=VSCode%20Adapter for OpenVSX Swagger API + service_url: "https://open-vsx.org/vscode/gallery", + item_url: "https://open-vsx.org/vscode/item", + resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}" + ) + end + + def initialize(key, name, service_url:, item_url:, resource_url_template:) + @key = key + @name = name + @values = { + service_url: service_url, + item_url: item_url, + resource_url_template: resource_url_template + }.freeze + end + + attr_reader :key, :name, :values + + def to_h + { + key: key, + name: name, + values: values + } + end + end +end diff --git a/lib/web_ide/settings/default_settings.rb b/lib/web_ide/settings/default_settings.rb index 19df3da3b065c27117a0704bed0af2b4f4b8def5..20a690034d4236ebae91ba4b71b9edee2e618934 100644 --- a/lib/web_ide/settings/default_settings.rb +++ b/lib/web_ide/settings/default_settings.rb @@ -8,24 +8,17 @@ class DefaultSettings def self.default_settings { vscode_extension_marketplace: [ - # See https://sourcegraph.com/github.com/microsoft/vscode@6979fb003bfa575848eda2d3966e872a9615427b/-/blob/src/vs/base/common/product.ts?L96 - # for the original source of settings entries in the VS Code source code. - # See https://open-vsx.org/swagger-ui/index.html?urls.primaryName=VSCode%20Adapter# - # for OpenVSX Swagger API - { - service_url: "https://open-vsx.org/vscode/gallery", - item_url: "https://open-vsx.org/vscode/item", - resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}", - control_url: "", - nls_base_url: "", - publisher_url: "" - }, + {}, # NOTE: There is no default, the value is always generated by ExtensionMarketplaceGenerator Hash ], vscode_extension_marketplace_metadata: [ { enabled: false, disabled_reason: :instance_disabled }, Hash ], + vscode_extension_marketplace_home_url: [ + "", + String + ], vscode_extension_marketplace_view_model: [ { enabled: false, reason: :instance_disabled, help_url: '' }, Hash diff --git a/lib/web_ide/settings/extension_marketplace_generator.rb b/lib/web_ide/settings/extension_marketplace_generator.rb new file mode 100644 index 0000000000000000000000000000000000000000..47de4e9a054688c3a08c214082b05fca2d80d6fc --- /dev/null +++ b/lib/web_ide/settings/extension_marketplace_generator.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module WebIde + module Settings + class ExtensionMarketplaceGenerator + # @param [Hash] context + # @return [Hash] + def self.generate(context) + return context unless context.fetch(:requested_setting_names).include?(:vscode_extension_marketplace) + + user = context.dig(:options, :user) + + context[:settings][:vscode_extension_marketplace] = extension_marketplace_from_application_settings(user) + context + end + + # @param [User, nil] user + # @return [Hash] + def self.extension_marketplace_from_application_settings(user) + unless Feature.enabled?(:vscode_extension_marketplace_settings, user) + return ::WebIde::ExtensionMarketplacePreset.open_vsx.values + end + + settings = Gitlab::CurrentSettings.vscode_extension_marketplace + preset_key = settings.fetch("preset", ::WebIde::ExtensionMarketplacePreset.open_vsx.key) + + if preset_key == ::WebIde::ExtensionMarketplacePreset::CUSTOM_KEY + settings.fetch("custom_values").deep_symbolize_keys + else + preset = ::WebIde::ExtensionMarketplacePreset.all.find { |x| x.key == preset_key } + preset&.values + end + end + + private_class_method :extension_marketplace_from_application_settings + end + end +end diff --git a/lib/web_ide/settings/extension_marketplace_home_url_generator.rb b/lib/web_ide/settings/extension_marketplace_home_url_generator.rb new file mode 100644 index 0000000000000000000000000000000000000000..d691e0477fd07df4f5e5e56af24299ab4e42d981 --- /dev/null +++ b/lib/web_ide/settings/extension_marketplace_home_url_generator.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module WebIde + module Settings + class ExtensionMarketplaceHomeUrlGenerator + # @param [Hash] context + # @return [Hash] + def self.generate(context) + return context unless context.fetch(:requested_setting_names).include?(:vscode_extension_marketplace_home_url) + + context[:settings][:vscode_extension_marketplace_home_url] = home_url(context) + context + end + + # @param [Hash] context + # @return [String] The URL to use for the extension marketplace home + def self.home_url(context) + context => { + settings: { + vscode_extension_marketplace: Hash => vscode_settings, + } + } + + item_url = vscode_settings&.fetch(:item_url, nil) + + return "" unless item_url + + base_url = ::Gitlab::UrlHelpers.normalized_base_url(item_url) + + # NOTE: It's possible for `normalized_base_url` to return something like `://` so let's go ahead and check + # that we actually start with `http` or `https`. + return base_url if /^https?:/.match?(base_url) + + "" + end + + private_class_method :home_url + end + end +end diff --git a/lib/web_ide/settings/extension_marketplace_metadata_generator.rb b/lib/web_ide/settings/extension_marketplace_metadata_generator.rb index af71de6b0c88a0e61ac61f8a8a0cb47442dea1aa..40fe374c4129f70303868fe58b7afc5bd671e273 100644 --- a/lib/web_ide/settings/extension_marketplace_metadata_generator.rb +++ b/lib/web_ide/settings/extension_marketplace_metadata_generator.rb @@ -48,6 +48,10 @@ def self.build_metadata(user:, flag_enabled:) return metadata_disabled(:no_flag) if flag_enabled.nil? return metadata_disabled(:instance_disabled) unless flag_enabled + unless ::WebIde::ExtensionMarketplace.feature_enabled_from_application_settings?(user: user) + return metadata_disabled(:instance_disabled) + end + build_metadata_for_user(user) end diff --git a/lib/web_ide/settings/main.rb b/lib/web_ide/settings/main.rb index 49fb2656f804a89a3175c1184864fe6906deda34..e845fd02ecbcb4e22e2011d5dde75621fe6bf365 100644 --- a/lib/web_ide/settings/main.rb +++ b/lib/web_ide/settings/main.rb @@ -17,6 +17,8 @@ def self.get_settings(context) result = initial_result .map(SettingsInitializer.method(:init)) + .map(ExtensionMarketplaceGenerator.method(:generate)) + .map(ExtensionMarketplaceHomeUrlGenerator.method(:generate)) .map(ExtensionMarketplaceMetadataGenerator.method(:generate)) # NOTE: EnvVarOverrideProcessor is inserted here to easily override settings for local or temporary testing # it should happen **before** validators. diff --git a/lib/web_ide/settings/settings_initializer.rb b/lib/web_ide/settings/settings_initializer.rb index ec29038f4aac94420cc903d6899b16b9c5f75f5a..26ae8b2e63fe04da936a18b35b4ff20d6c5aa6be 100644 --- a/lib/web_ide/settings/settings_initializer.rb +++ b/lib/web_ide/settings/settings_initializer.rb @@ -7,6 +7,9 @@ class SettingsInitializer vscode_extension_marketplace_view_model: [ :vscode_extension_marketplace_metadata, :vscode_extension_marketplace + ], + vscode_extension_marketplace_home_url: [ + :vscode_extension_marketplace ] }.freeze diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index 7c9e62c8e66cb17ac440b8e916c2dae04dbbeff8..acc24554db9c47bfb7526b2d890b997a5aea21cb 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -268,7 +268,13 @@ def stub_user(messages = {}) context 'when Web IDE Extension Marketplace feature is enabled' do before do - allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled?).with(user: user).and_return(true) + allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled_from_application_settings?) + .with(user: user) + .and_return(true) + + allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled?) + .with(user: user) + .and_return(true) end it 'includes extension marketplace integration' do diff --git a/spec/lib/web_ide/extension_marketplace_preset_spec.rb b/spec/lib/web_ide/extension_marketplace_preset_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a1d85b86f8213732c54bad9e6aa4a8d42dab9a8d --- /dev/null +++ b/spec/lib/web_ide/extension_marketplace_preset_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe WebIde::ExtensionMarketplacePreset, feature_category: :web_ide do + describe '.all' do + subject(:all) { described_class.all } + + it { is_expected.to eq([described_class.open_vsx]) } + end + + describe '.open_vsx' do + subject(:open_vsx) { described_class.open_vsx } + + it "has OpenVSX properties" do + is_expected.to have_attributes( + key: 'open_vsx', + name: "Open VSX", + values: { + service_url: "https://open-vsx.org/vscode/gallery", + item_url: "https://open-vsx.org/vscode/item", + resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}" + } + ) + end + end + + describe '#to_h' do + it 'returns hash of attributes' do + preset = described_class.new( + "test_key", + "Test Key", + service_url: "abc", + item_url: "def", + resource_url_template: "ghi" + ) + + expect(preset.to_h).to eq({ + key: "test_key", + name: "Test Key", + values: { + service_url: "abc", + item_url: "def", + resource_url_template: "ghi" + } + }) + end + end +end diff --git a/spec/lib/web_ide/extension_marketplace_spec.rb b/spec/lib/web_ide/extension_marketplace_spec.rb index 8f85e9090d99f7ec8d2c3560b3d09b07756fe9af..c3a001a8332247cb4bee66bb2f92b97d2eef8918 100644 --- a/spec/lib/web_ide/extension_marketplace_spec.rb +++ b/spec/lib/web_ide/extension_marketplace_spec.rb @@ -7,30 +7,41 @@ let(:help_url) { "/help/user/project/web_ide/_index.md#extension-marketplace" } let(:user_preferences_url) { "/-/profile/preferences#integrations" } - - let_it_be_with_reload(:current_user) { create(:user) } - let_it_be(:default_vscode_settings) do + let(:custom_app_setting) do { - item_url: 'https://open-vsx.org/vscode/item', - service_url: 'https://open-vsx.org/vscode/gallery', - resource_url_template: - 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}' + enabled: true, + preset: "custom", + custom_values: { + item_url: "https://example.com:8444/vscode/item", + service_url: "https://example.com:8444/vscode/service", + resource_url_template: "https://example.com:8444/vscode/resource" + } } end + let(:open_vsx_app_setting) { custom_app_setting.merge(preset: 'open_vsx') } + + let_it_be_with_reload(:current_user) { create(:user) } + describe 'feature enabled methods' do - where(:vscode_web_ide, :web_ide_extensions_marketplace, :expectation) do - ref(:current_user) | ref(:current_user) | true - ref(:current_user) | false | false - false | ref(:current_user) | false + where(:vscode_web_ide, :web_ide_extensions_marketplace, :vscode_extension_marketplace_settings, :app_setting, + :expectation) do + ref(:current_user) | ref(:current_user) | false | {} | true + ref(:current_user) | ref(:current_user) | true | {} | false + ref(:current_user) | ref(:current_user) | true | { enabled: true } | true + ref(:current_user) | false | false | { enabled: true } | false + false | ref(:current_user) | false | {} | false end with_them do before do stub_feature_flags( vscode_web_ide: vscode_web_ide, - web_ide_extensions_marketplace: web_ide_extensions_marketplace + web_ide_extensions_marketplace: web_ide_extensions_marketplace, + vscode_extension_marketplace_settings: vscode_extension_marketplace_settings ) + + stub_application_setting(vscode_extension_marketplace: app_setting) end describe '#feature_enabled?' do @@ -43,75 +54,89 @@ end end - describe '#marketplace_home_url' do - it { expect(described_class.marketplace_home_url).to eq('https://open-vsx.org') } - end + describe '#feature_enabled_from_application_settings?' do + where(:vscode_extension_marketplace_settings, :app_setting, :user_arg, :expectation) do + false | {} | ref(:current_user) | true + false | { enabled: true } | ref(:current_user) | true + false | { enabled: true } | nil | true + ref(:current_user) | { enabled: true } | nil | true + ref(:current_user) | { enabled: true } | ref(:current_user) | true + ref(:current_user) | {} | ref(:current_user) | false + ref(:current_user) | { enabled: false } | ref(:current_user) | false + end + + subject(:enabled) do + described_class.feature_enabled_from_application_settings?(user: user_arg) + end - describe '#help_url' do - it { expect(help_url).to match('/help/user/project/web_ide/_index.md#extension-marketplace') } + with_them do + before do + stub_feature_flags(vscode_extension_marketplace_settings: vscode_extension_marketplace_settings) + stub_application_setting(vscode_extension_marketplace: app_setting) + end + + it { is_expected.to be(expectation) } + end end - describe '#help_preferences_url' do - it 'returns expected url' do - expect(described_class.help_preferences_url).to match( - '/help/user/profile/preferences.md#integrate-with-the-extension-marketplace' - ) + describe '#marketplace_home_url' do + where(:vscode_extension_marketplace_settings, :app_setting, :expectation) do + false | {} | "https://open-vsx.org" + true | {} | "https://open-vsx.org" + true | ref(:custom_app_setting) | "https://example.com:8444" + true | ref(:open_vsx_app_setting) | "https://open-vsx.org" + end + + subject(:marketplace_home_url) do + described_class.marketplace_home_url(user: current_user) + end + + with_them do + before do + stub_feature_flags(vscode_extension_marketplace_settings: vscode_extension_marketplace_settings) + stub_application_setting(vscode_extension_marketplace: app_setting) + end + + it { is_expected.to eq(expectation) } end end - describe '#user_preferences_url' do - it { expect(user_preferences_url).to match('/-/profile/preferences#integrations') } + describe '#help_preferences_url' do + subject(:url) { described_class.help_preferences_url } + + it { is_expected.to match('/help/user/profile/preferences.md#integrate-with-the-extension-marketplace') } end describe '#webide_extension_marketplace_settings' do - subject(:webide_settings) { described_class.webide_extension_marketplace_settings(user: current_user) } + # rubocop:disable Layout/LineLength -- last parameter extens past line but is preferable to rubocop's suggestion + where(:web_ide_extensions_marketplace, :vscode_extension_marketplace_settings, :app_setting, :opt_in_status, :expectation) do + true | false | {} | :enabled | lazy { { enabled: true, vscode_settings: ::WebIde::ExtensionMarketplacePreset.open_vsx.values } } + true | false | {} | :unset | lazy { { enabled: false, reason: :opt_in_unset, help_url: /#{help_url}/, user_preferences_url: /#{user_preferences_url}/ } } + true | false | {} | :disabled | lazy { { enabled: false, reason: :opt_in_disabled, help_url: /#{help_url}/, user_preferences_url: /#{user_preferences_url}/ } } + false | false | {} | :enabled | lazy { { enabled: false, reason: :instance_disabled, help_url: /#{help_url}/ } } + true | true | {} | :enabled | lazy { { enabled: false, reason: :instance_disabled, help_url: /#{help_url}/ } } + true | true | { enabled: false } | :enabled | lazy { { enabled: false, reason: :instance_disabled, help_url: /#{help_url}/ } } + true | true | ref(:custom_app_setting) | :enabled | lazy { { enabled: true, vscode_settings: custom_app_setting[:custom_values] } } + true | true | ref(:open_vsx_app_setting) | :enabled | lazy { { enabled: true, vscode_settings: ::WebIde::ExtensionMarketplacePreset.open_vsx.values } } + end + # rubocop:enable Layout/LineLength - context 'when instance enabled' do - before do - stub_feature_flags( - web_ide_extensions_marketplace: current_user, - vscode_web_ide: current_user - ) - end + subject(:webide_settings) { described_class.webide_extension_marketplace_settings(user: current_user) } - it 'when user opt in enabled, returns enabled settings' do - current_user.update!(extensions_marketplace_opt_in_status: :enabled) + before do + stub_feature_flags( + vscode_extension_marketplace_settings: vscode_extension_marketplace_settings, + web_ide_extensions_marketplace: web_ide_extensions_marketplace, + vscode_web_ide: true + ) - expect(webide_settings).to match({ - enabled: true, - vscode_settings: hash_including(default_vscode_settings) - }) - end + stub_application_setting(vscode_extension_marketplace: app_setting) - context 'when user opt in disabled' do - where(:opt_in_status, :reason) do - :unset | :opt_in_unset - :disabled | :opt_in_disabled - end - - with_them do - it 'returns disabled settings' do - current_user.update!(extensions_marketplace_opt_in_status: opt_in_status) - - expect(webide_settings).to match({ - enabled: false, - reason: reason, - help_url: /#{help_url}/, - user_preferences_url: /#{user_preferences_url}/ - }) - end - end - end + current_user.update!(extensions_marketplace_opt_in_status: opt_in_status) end - context 'when instance disabled' do - it 'returns disabled settings and help url' do - expect(webide_settings).to match({ - enabled: false, - reason: :instance_disabled, - help_url: /#{help_url}/ - }) - end + with_them do + it { is_expected.to match(expectation) } end end end diff --git a/spec/lib/web_ide/settings/extension_marketplace_generator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_generator_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..891400204966fe71a0a08a39292ef4bf8a8ec870 --- /dev/null +++ b/spec/lib/web_ide/settings/extension_marketplace_generator_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "fast_spec_helper" + +RSpec.describe WebIde::Settings::ExtensionMarketplaceGenerator, feature_category: :web_ide do + using RSpec::Parameterized::TableSyntax + + let(:user_class) do + stub_const( + "User", + Class.new do + def flipper_id + "UserStub" + end + end + ) + end + + let(:user) { user_class.new } + let(:requested_setting_names) { [:vscode_extension_marketplace] } + let(:custom_app_settings) do + { + "enabled" => false, + "preset" => "custom", + "custom_values" => { + "item_url" => "abc", + "service_url" => "def", + "resource_url_template" => "ghi" + } + } + end + + let(:context) do + { + requested_setting_names: requested_setting_names, + options: { user: user }, + settings: {} + } + end + + subject(:result) { described_class.generate(context)[:settings][:vscode_extension_marketplace] } + + before do + allow(Feature).to receive(:enabled?).with(:vscode_extension_marketplace_settings, user).and_return(settings_flag) + allow(Gitlab::CurrentSettings).to receive(:vscode_extension_marketplace).and_return(app_setting) + end + + describe 'default (with setting requested)' do + where(:settings_flag, :app_setting, :expectation) do + false | {} | ::WebIde::ExtensionMarketplacePreset.open_vsx.values + true | {} | ::WebIde::ExtensionMarketplacePreset.open_vsx.values + true | { "preset" => 'open_vsx' } | ::WebIde::ExtensionMarketplacePreset.open_vsx.values + true | ref(:custom_app_settings) | { item_url: "abc", service_url: "def", resource_url_template: "ghi" } + # This should never happen, but lets test it anyways + true | { "preset" => 'DNE' } | nil + end + + with_them do + it { is_expected.to eq(expectation) } + end + end + + describe 'without setting requested' do + let(:requested_setting_names) { [] } + let(:settings_flag) { true } + let(:app_setting) { custom_app_settings } + + it { is_expected.to be_nil } + end +end diff --git a/spec/lib/web_ide/settings/extension_marketplace_home_url_generator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_home_url_generator_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..357515e289f61161559a7c09529d74f17a2a780b --- /dev/null +++ b/spec/lib/web_ide/settings/extension_marketplace_home_url_generator_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "fast_spec_helper" + +RSpec.describe WebIde::Settings::ExtensionMarketplaceHomeUrlGenerator, feature_category: :web_ide do + using RSpec::Parameterized::TableSyntax + + let(:vscode_extension_marketplace) { {} } + let(:requested_setting_names) { [:vscode_extension_marketplace_home_url] } + let(:context) do + { + requested_setting_names: requested_setting_names, + settings: { + vscode_extension_marketplace: vscode_extension_marketplace + } + } + end + + subject(:result) { described_class.generate(context)[:settings][:vscode_extension_marketplace_home_url] } + + where(:requested_setting_names, :vscode_extension_marketplace, :expectation) do + [:vscode_extension_marketplace_home_url] | {} | '' + [:vscode_extension_marketplace_home_url] | { item_url: 'https://example.com/foo/bar' } | 'https://example.com' + [:vscode_extension_marketplace_home_url] | { item_url: 'https://example.com:123/foo' } | 'https://example.com:123' + [:vscode_extension_marketplace_home_url] | { item_url: 'not really a thing...' } | '' + [] | { item_url: 'https://example.com:123/foo' } | nil + end + + with_them do + it { is_expected.to eq(expectation) } + end +end diff --git a/spec/lib/web_ide/settings/extension_marketplace_metadata_generator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_metadata_generator_spec.rb index 1e3f42e267e42dd7eef855b2e94b51156b3b9736..6f768dce9f5b221e2e3e423831491452be9036d3 100644 --- a/spec/lib/web_ide/settings/extension_marketplace_metadata_generator_spec.rb +++ b/spec/lib/web_ide/settings/extension_marketplace_metadata_generator_spec.rb @@ -46,25 +46,39 @@ :opt_in_status, :flag_exists, :flag_enabled, + :app_settings_enabled, :expected_vscode_extension_marketplace_metadata ) do # @formatter:off - Turn off RubyMine autoformatting - # user exists | opt_in_status | flag exists | flag_enabled | expected_settings - false | :undefined | false | :undefined | { enabled: false, disabled_reason: :no_user } - false | :undefined | true | true | { enabled: false, disabled_reason: :no_user } - true | :unset | false | :undefined | { enabled: false, disabled_reason: :no_flag } - true | :unset | true | false | { enabled: false, disabled_reason: :instance_disabled } - true | :unset | true | true | { enabled: false, disabled_reason: :opt_in_unset } - true | :disabled | true | true | { enabled: false, disabled_reason: :opt_in_disabled } - true | :enabled | true | true | { enabled: true } - true | :invalid | true | true | RuntimeError + # rubocop:disable Layout/LineLength -- Parameterized rows overflow and its better than the alternative + # user exists | opt_in_status | flag exists | flag_enabled | app_settings_enabled | expected_settings + false | :undefined | false | :undefined | true | { enabled: false, disabled_reason: :no_user } + false | :undefined | true | true | true | { enabled: false, disabled_reason: :no_user } + true | :unset | false | :undefined | true | { enabled: false, disabled_reason: :no_flag } + true | :unset | true | false | true | { enabled: false, disabled_reason: :instance_disabled } + true | :unset | true | true | true | { enabled: false, disabled_reason: :opt_in_unset } + true | :disabled | true | true | true | { enabled: false, disabled_reason: :opt_in_disabled } + true | :enabled | true | true | false | { enabled: false, disabled_reason: :instance_disabled } + true | :enabled | true | true | true | { enabled: true } + true | :invalid | true | true | true | RuntimeError + # rubocop:enable Layout/LineLength # @formatter:on end with_them do - let(:user_class) { stub_const('User', Class.new) } + let(:user_class) do + stub_const( + "User", + Class.new do + def flipper_id + "UserStub" + end + end + ) + end + let(:user) { user_class.new } let(:enums) { stub_const('Enums::WebIde::ExtensionsMarketplaceOptInStatus', Class.new) } @@ -80,6 +94,9 @@ # 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 }) + allow(::WebIde::ExtensionMarketplace).to receive(:feature_enabled_from_application_settings?) + .with(user: user) + .and_return(app_settings_enabled) end it_behaves_like "extension marketplace settings" diff --git a/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb b/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb index 38c2dfc051db5cc9ff715ee16154841b85aa5f2d..8fa1e69b0011fd42f9feb0a648cf8d9b4ce6ca6a 100644 --- a/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb +++ b/spec/lib/web_ide/settings/extension_marketplace_validator_spec.rb @@ -12,10 +12,7 @@ { service_url: service_url, item_url: item_url, - resource_url_template: resource_url_template, - control_url: "", - nls_base_url: "", - publisher_url: "" + resource_url_template: resource_url_template } end diff --git a/spec/lib/web_ide/settings/main_spec.rb b/spec/lib/web_ide/settings/main_spec.rb index 3629c4b71095235405ec4287d78708ec0e8821d3..613232c27340327461ba61b7be8b04d73e6a6177 100644 --- a/spec/lib/web_ide/settings/main_spec.rb +++ b/spec/lib/web_ide/settings/main_spec.rb @@ -9,6 +9,8 @@ let(:rop_steps) do [ [WebIde::Settings::SettingsInitializer, :map], + [WebIde::Settings::ExtensionMarketplaceGenerator, :map], + [WebIde::Settings::ExtensionMarketplaceHomeUrlGenerator, :map], [WebIde::Settings::ExtensionMarketplaceMetadataGenerator, :map], [Gitlab::Fp::Settings::EnvVarOverrideProcessor, :and_then], [WebIde::Settings::ExtensionMarketplaceValidator, :and_then], diff --git a/spec/lib/web_ide/settings/settings_initializer_spec.rb b/spec/lib/web_ide/settings/settings_initializer_spec.rb index e22a16f55a4abeec023c3d9a0a72b13370832a65..468b1138bbf48523ec3228b594434f06684f7752 100644 --- a/spec/lib/web_ide/settings/settings_initializer_spec.rb +++ b/spec/lib/web_ide/settings/settings_initializer_spec.rb @@ -18,17 +18,12 @@ requested_setting_names: [ :vscode_extension_marketplace, :vscode_extension_marketplace_metadata, + :vscode_extension_marketplace_home_url, :vscode_extension_marketplace_view_model ], settings: { - vscode_extension_marketplace: { - control_url: "", - item_url: "https://open-vsx.org/vscode/item", - nls_base_url: "", - publisher_url: "", - resource_url_template: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', - service_url: "https://open-vsx.org/vscode/gallery" - }, + vscode_extension_marketplace: {}, + vscode_extension_marketplace_home_url: "", vscode_extension_marketplace_metadata: { enabled: false, disabled_reason: :instance_disabled @@ -41,6 +36,7 @@ }, setting_types: { vscode_extension_marketplace: Hash, + vscode_extension_marketplace_home_url: String, vscode_extension_marketplace_metadata: Hash, vscode_extension_marketplace_view_model: Hash }, diff --git a/spec/lib/web_ide/settings/settings_integration_spec.rb b/spec/lib/web_ide/settings/settings_integration_spec.rb index 532d9ceb0b4420fe7c5703e0b6ba9a6a24319478..ddf8c8f526f427d5f7f097c0c44759ccb759c184 100644 --- a/spec/lib/web_ide/settings/settings_integration_spec.rb +++ b/spec/lib/web_ide/settings/settings_integration_spec.rb @@ -9,10 +9,7 @@ { service_url: "https://open-vsx.org/vscode/gallery", item_url: "https://open-vsx.org/vscode/item", - resource_url_template: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', - control_url: "", - nls_base_url: "", - publisher_url: "" + resource_url_template: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}' } end @@ -103,10 +100,7 @@ expected_value = { item_url: "https://open-vsx.org/vscode/item", resource_url_template: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}", - service_url: "https://open-vsx.org/vscode/gallery", - control_url: "", - nls_base_url: "", - publisher_url: "" + service_url: "https://open-vsx.org/vscode/gallery" } expect(vscode_extension_marketplace_setting).to eq(expected_value)