From 8a0b22b2a4d6fdcffbf913663b0a32620541a25f Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Thu, 13 Mar 2025 11:43:08 -0500 Subject: [PATCH 1/3] Check marketplace_home_url with user opt in - If a user has opted-in to marketplace A, we DO NOT want this to transfer over to marketplace B. - For now, we store alongside the opt_in field, a url field, and check this field to determine the enablement. Changelog: changed --- .../mutations/user_preferences/update.rb | 6 ++ app/graphql/types/user_preferences_type.rb | 9 +++ app/models/user.rb | 2 +- app/models/user_preference.rb | 14 +--- app/services/users/update_service.rb | 14 ++++ app/views/profiles/preferences/show.html.haml | 4 +- ...rketplace_opt_in_url_to_user_preference.rb | 23 ++++++ db/schema_migrations/20250313163310 | 1 + db/structure.sql | 2 + ...xtension_marketplace_metadata_generator.rb | 2 +- ...ion_marketplace_metadata_generator_spec.rb | 8 +- .../settings/settings_integration_spec.rb | 2 +- lib/web_ide/extension_marketplace_opt_in.rb | 27 +++++++ ...xtension_marketplace_metadata_generator.rb | 21 +++-- lib/web_ide/settings/settings_initializer.rb | 3 + .../profiles/preferences_controller_spec.rb | 8 +- .../extension_marketplace_opt_in_spec.rb | 78 +++++++++++++++++++ .../lib/web_ide/extension_marketplace_spec.rb | 26 ++++--- ...ion_marketplace_metadata_generator_spec.rb | 7 +- spec/models/user_preference_spec.rb | 38 +++------ spec/models/user_spec.rb | 7 +- .../mutations/user_preferences/update_spec.rb | 13 ++++ 22 files changed, 250 insertions(+), 65 deletions(-) create mode 100644 db/migrate/20250313163310_add_extension_marketplace_opt_in_url_to_user_preference.rb create mode 100644 db/schema_migrations/20250313163310 create mode 100644 lib/web_ide/extension_marketplace_opt_in.rb create mode 100644 spec/lib/web_ide/extension_marketplace_opt_in_spec.rb diff --git a/app/graphql/mutations/user_preferences/update.rb b/app/graphql/mutations/user_preferences/update.rb index 69c81655410d48..4c1df3f22b4ee1 100644 --- a/app/graphql/mutations/user_preferences/update.rb +++ b/app/graphql/mutations/user_preferences/update.rb @@ -53,6 +53,12 @@ class Update < BaseMutation def resolve(**attributes) attributes.delete_if { |key, value| NON_NULLABLE_ARGS.include?(key) && value.nil? } + + if attributes.include?(:extensions_marketplace_opt_in_status) + attributes[:extensions_marketplace_opt_in_url] = + ::WebIde::ExtensionMarketplace.marketplace_home_url(user: current_user) + end + user_preferences = current_user.user_preference user_preferences.update(attributes) diff --git a/app/graphql/types/user_preferences_type.rb b/app/graphql/types/user_preferences_type.rb index 30ac5ae5df7fdb..cb8129f32a8757 100644 --- a/app/graphql/types/user_preferences_type.rb +++ b/app/graphql/types/user_preferences_type.rb @@ -64,5 +64,14 @@ def projects_sort def organization_groups_projects_sort user_preference.organization_groups_projects_sort&.to_sym end + + def extensions_marketplace_opt_in_status + user = user_preference.user + + ::WebIde::ExtensionMarketplaceOptIn.opt_in_status( + user: user, + marketplace_home_url: ::WebIde::ExtensionMarketplace.marketplace_home_url(user: user) + ) + end end end diff --git a/app/models/user.rb b/app/models/user.rb index c405cf5c8ab09f..53e02eccecdb3c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -443,9 +443,9 @@ def update_tracked_fields!(request) :sourcegraph_enabled, :sourcegraph_enabled=, :gitpod_enabled, :gitpod_enabled=, :extensions_marketplace_opt_in_status, :extensions_marketplace_opt_in_status=, + :extensions_marketplace_opt_in_url, :extensions_marketplace_opt_in_url=, :organization_groups_projects_sort, :organization_groups_projects_sort=, :organization_groups_projects_display, :organization_groups_projects_display=, - :extensions_marketplace_enabled, :extensions_marketplace_enabled=, :setup_for_company, :setup_for_company=, :project_shortcut_buttons, :project_shortcut_buttons=, :keyboard_shortcuts_enabled, :keyboard_shortcuts_enabled=, diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb index 9f2e6715c6a5fc..01259f993a947c 100644 --- a/app/models/user_preference.rb +++ b/app/models/user_preference.rb @@ -29,6 +29,7 @@ class UserPreference < ApplicationRecord validates :pinned_nav_items, json_schema: { filename: 'pinned_nav_items' } validates :time_display_format, inclusion: { in: TIME_DISPLAY_FORMATS.values }, presence: true + validates :extensions_marketplace_opt_in_url, length: { maximum: 512 } validate :user_belongs_to_home_organization, if: :home_organization_changed? @@ -90,16 +91,9 @@ def early_access_event_tracking? early_access_program_participant? && early_access_program_tracking? end - # NOTE: Despite this returning a boolean, it does not end in `?` out of - # symmetry with the other integration fields like `gitpod_enabled` - def extensions_marketplace_enabled - extensions_marketplace_opt_in_status == "enabled" - end - - def extensions_marketplace_enabled=(value) - status = ActiveRecord::Type::Boolean.new.cast(value) ? 'enabled' : 'disabled' - - self.extensions_marketplace_opt_in_status = status + def extensions_marketplace_opt_in_url + # To support existing records, this can be `nil` and it defaults to `https://open-vsx.org` + super || 'https://open-vsx.org' end def dpop_enabled=(value) diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb index 42ffc79b4c6c36..29559540defb60 100644 --- a/app/services/users/update_service.rb +++ b/app/services/users/update_service.rb @@ -29,6 +29,7 @@ def execute(validate: true, check_password: false, &block) @user.user_detail # prevent assignment discard_read_only_attributes + append_extension_marketplace_attributes assign_attributes if check_password && require_password_check? && !@user.valid_password?(@validation_password) @@ -141,6 +142,19 @@ def synced_attributes end end + def append_extension_marketplace_attributes + return unless params.has_key?(:extensions_marketplace_enabled) + + value = params.delete(:extensions_marketplace_enabled) + marketplace_home_url = ::WebIde::ExtensionMarketplace.marketplace_home_url(user: @user) + update_params = ::WebIde::ExtensionMarketplaceOptIn.params( + enabled: value, + marketplace_home_url: marketplace_home_url + ) + + params.merge!(update_params) + end + def assign_attributes @user.assign_attributes(params.except(*identity_attributes)) unless params.empty? end diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index b448cf5443f2b8..bdd44063db2cd1 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -3,7 +3,9 @@ - user_theme_id = Gitlab::Themes.for_user(@user).id - user_color_mode_id = Gitlab::ColorModes.for_user(@user).id - user_color_schema_id = Gitlab::ColorSchemes.for_user(@user).id -- user_fields = { color_mode_id: user_color_mode_id, theme: user_theme_id, gitpod_enabled: @user.gitpod_enabled, sourcegraph_enabled: @user.sourcegraph_enabled, extensions_marketplace_enabled: @user.extensions_marketplace_enabled }.to_json +- marketplace_home_url = ::WebIde::ExtensionMarketplace.marketplace_home_url(user: @user) +- extensions_marketplace_enabled = ::WebIde::ExtensionMarketplaceOptIn.enabled?(user: @user, marketplace_home_url: marketplace_home_url) +- user_fields = { color_mode_id: user_color_mode_id, theme: user_theme_id, gitpod_enabled: @user.gitpod_enabled, sourcegraph_enabled: @user.sourcegraph_enabled, extensions_marketplace_enabled: extensions_marketplace_enabled }.to_json - fixed_help_text = s_('Preferences|Content will be a maximum of 1280 pixels wide.') - fluid_help_text = s_('Preferences|Content will span %{percentage} of the page width.').html_safe % { percentage: '100%' } - plain_text_editor_help_text = s_('Preferences|Type in plain text, using Markdown.') diff --git a/db/migrate/20250313163310_add_extension_marketplace_opt_in_url_to_user_preference.rb b/db/migrate/20250313163310_add_extension_marketplace_opt_in_url_to_user_preference.rb new file mode 100644 index 00000000000000..8216d7acf4ae01 --- /dev/null +++ b/db/migrate/20250313163310_add_extension_marketplace_opt_in_url_to_user_preference.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class AddExtensionMarketplaceOptInUrlToUserPreference < Gitlab::Database::Migration[2.2] + milestone '17.10' + disable_ddl_transaction! + + def up + with_lock_retries do + add_column :user_preferences, :extensions_marketplace_opt_in_url, :text, null: true + end + + # This is well above the 253 full domain name limit. We go ahead and overshoot because + # we may need to store paths in here in the future. + # https://webmasters.stackexchange.com/a/16997 + add_text_limit :user_preferences, :extensions_marketplace_opt_in_url, 512 + end + + def down + with_lock_retries do + remove_column :user_preferences, :extensions_marketplace_opt_in_url, if_exists: true + end + end +end diff --git a/db/schema_migrations/20250313163310 b/db/schema_migrations/20250313163310 new file mode 100644 index 00000000000000..d8e5413b7d4abe --- /dev/null +++ b/db/schema_migrations/20250313163310 @@ -0,0 +1 @@ +ee39402909b559e8c4a4bd1194513df4885bf3bee9e4b8f2392fdd125c27cf61 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 8d988c071aa853..1f58ddbbe943d7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -23500,8 +23500,10 @@ CREATE TABLE user_preferences ( use_work_items_view boolean DEFAULT false NOT NULL, text_editor_type smallint DEFAULT 0 NOT NULL, merge_request_dashboard_list_type smallint DEFAULT 0 NOT NULL, + extensions_marketplace_opt_in_url text, CONSTRAINT check_1d670edc68 CHECK ((time_display_relative IS NOT NULL)), CONSTRAINT check_89bf269f41 CHECK ((char_length(diffs_deletion_color) <= 7)), + CONSTRAINT check_9b50d9f942 CHECK ((char_length(extensions_marketplace_opt_in_url) <= 512)), CONSTRAINT check_b1306f8875 CHECK ((char_length(organization_groups_projects_sort) <= 64)), CONSTRAINT check_b22446f91a CHECK ((render_whitespace_in_code IS NOT NULL)), CONSTRAINT check_d07ccd35f7 CHECK ((char_length(diffs_addition_color) <= 7)), diff --git a/ee/lib/ee/web_ide/settings/extension_marketplace_metadata_generator.rb b/ee/lib/ee/web_ide/settings/extension_marketplace_metadata_generator.rb index b87c82a6bd126a..878cb83f3c4cc3 100644 --- a/ee/lib/ee/web_ide/settings/extension_marketplace_metadata_generator.rb +++ b/ee/lib/ee/web_ide/settings/extension_marketplace_metadata_generator.rb @@ -20,7 +20,7 @@ def disabled_reasons end override :build_metadata_for_user - def build_metadata_for_user(user) + def build_metadata_for_user(user:, marketplace_home_url:) return metadata_disabled(:enterprise_group_disabled) unless enabled_for_enterprise_group?(user) super 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 46fa65abcae089..10c030a3f9630f 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,6 +5,7 @@ RSpec.describe WebIde::Settings::ExtensionMarketplaceMetadataGenerator, feature_category: :web_ide do using RSpec::Parameterized::TableSyntax + let(:marketplace_home_url) { "https://example.com" } let(:user_class) do stub_const( "User", @@ -26,7 +27,9 @@ def flipper_id user: user, vscode_extension_marketplace_feature_flag_enabled: true }, - settings: {} + settings: { + vscode_extension_marketplace_home_url: marketplace_home_url + } } end @@ -50,7 +53,8 @@ def flipper_id allow(user).to receive_messages( enterprise_user?: !!enterprise_group, enterprise_group: enterprise_group, - extensions_marketplace_opt_in_status: :unset + extensions_marketplace_opt_in_status: 'unset', + extensions_marketplace_opt_in_url: marketplace_home_url ) allow(group).to receive(:enterprise_users_extensions_marketplace_enabled?).and_return(enterprise_group_enabled) 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 72219f8b1a12bc..19d457a520daf8 100644 --- a/ee/spec/lib/web_ide/settings/settings_integration_spec.rb +++ b/ee/spec/lib/web_ide/settings/settings_integration_spec.rb @@ -22,7 +22,7 @@ ) 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) + user.update!(extensions_marketplace_opt_in_status: "enabled") end describe "default - enterprise group has extensions marketplace disabled" do diff --git a/lib/web_ide/extension_marketplace_opt_in.rb b/lib/web_ide/extension_marketplace_opt_in.rb new file mode 100644 index 00000000000000..45c37de3020710 --- /dev/null +++ b/lib/web_ide/extension_marketplace_opt_in.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module WebIde + class ExtensionMarketplaceOptIn + def self.opt_in_status(user:, marketplace_home_url:) + return 'unset' unless user && marketplace_home_url + return 'unset' unless user.extensions_marketplace_opt_in_url == marketplace_home_url + + user.extensions_marketplace_opt_in_status + end + + def self.enabled?(user:, marketplace_home_url:) + status = opt_in_status(user: user, marketplace_home_url: marketplace_home_url) + + status == 'enabled' + end + + def self.params(enabled:, marketplace_home_url:) + status = ::Gitlab::Utils.to_boolean(enabled) ? 'enabled' : 'disabled' + + { + extensions_marketplace_opt_in_status: status, + extensions_marketplace_opt_in_url: marketplace_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 40fe374c4129f7..fc53e9e1d06dab 100644 --- a/lib/web_ide/settings/extension_marketplace_metadata_generator.rb +++ b/lib/web_ide/settings/extension_marketplace_metadata_generator.rb @@ -23,7 +23,12 @@ class ExtensionMarketplaceMetadataGenerator def self.generate(context) return context unless context.fetch(:requested_setting_names).include?(:vscode_extension_marketplace_metadata) - context => { options: Hash => options } + context => { + options: Hash => options, + settings: { + vscode_extension_marketplace_home_url: String => marketplace_home_url, + } + } options_with_defaults = { user: nil, vscode_extension_marketplace_feature_flag_enabled: nil }.merge(options) options_with_defaults => { user: ::User | NilClass => user, @@ -33,7 +38,8 @@ def self.generate(context) extension_marketplace_metadata = build_metadata( user: user, - flag_enabled: extension_marketplace_feature_flag_enabled + flag_enabled: extension_marketplace_feature_flag_enabled, + marketplace_home_url: marketplace_home_url ) context[:settings][:vscode_extension_marketplace_metadata] = extension_marketplace_metadata @@ -43,7 +49,7 @@ def self.generate(context) # @param [User, nil] user # @param [Boolean, nil] flag_enabled # @return [Hash] - def self.build_metadata(user:, flag_enabled:) + def self.build_metadata(user:, flag_enabled:, marketplace_home_url:) return metadata_disabled(:no_user) unless user return metadata_disabled(:no_flag) if flag_enabled.nil? return metadata_disabled(:instance_disabled) unless flag_enabled @@ -52,7 +58,7 @@ def self.build_metadata(user:, flag_enabled:) return metadata_disabled(:instance_disabled) end - build_metadata_for_user(user) + build_metadata_for_user(user: user, marketplace_home_url: marketplace_home_url) end def self.disabled_reasons @@ -63,9 +69,12 @@ def self.disabled_reasons # # @param [User] user # @return [Hash] - def self.build_metadata_for_user(user) + def self.build_metadata_for_user(user:, marketplace_home_url:) # 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 + opt_in_status = ::WebIde::ExtensionMarketplaceOptIn.opt_in_status( + user: user, + marketplace_home_url: marketplace_home_url + ).to_sym case opt_in_status when :enabled diff --git a/lib/web_ide/settings/settings_initializer.rb b/lib/web_ide/settings/settings_initializer.rb index 26ae8b2e63fe04..7e358cb183865a 100644 --- a/lib/web_ide/settings/settings_initializer.rb +++ b/lib/web_ide/settings/settings_initializer.rb @@ -8,6 +8,9 @@ class SettingsInitializer :vscode_extension_marketplace_metadata, :vscode_extension_marketplace ], + vscode_extension_marketplace_metadata: [ + :vscode_extension_marketplace_home_url + ], vscode_extension_marketplace_home_url: [ :vscode_extension_marketplace ] diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb index 504c75aaf532a2..38607255a3e2fc 100644 --- a/spec/controllers/profiles/preferences_controller_spec.rb +++ b/spec/controllers/profiles/preferences_controller_spec.rb @@ -65,7 +65,13 @@ def go(params: {}, format: :json) extensions_marketplace_enabled: '1' }.with_indifferent_access - expect(user).to receive(:assign_attributes).with(ActionController::Parameters.new(prefs).permit!) + expected_params = prefs.except(:extensions_marketplace_enabled).merge( + extensions_marketplace_opt_in_status: "enabled", + # Default marketplace_home_url based on Open VSX + extensions_marketplace_opt_in_url: "https://open-vsx.org" + ) + + expect(user).to receive(:assign_attributes).with(ActionController::Parameters.new(expected_params).permit!) expect(user).to receive(:save) go params: prefs diff --git a/spec/lib/web_ide/extension_marketplace_opt_in_spec.rb b/spec/lib/web_ide/extension_marketplace_opt_in_spec.rb new file mode 100644 index 00000000000000..be27cd15363915 --- /dev/null +++ b/spec/lib/web_ide/extension_marketplace_opt_in_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe WebIde::ExtensionMarketplaceOptIn, feature_category: :web_ide do + using RSpec::Parameterized::TableSyntax + + let(:user_class) do + stub_const("User", Struct.new( + :extensions_marketplace_opt_in_status, + :extensions_marketplace_opt_in_url, + keyword_init: true + )) + end + + let(:user) do + User.new( + extensions_marketplace_opt_in_status: opt_in_status, + extensions_marketplace_opt_in_url: opt_in_url + ) + end + + describe '.opt_in_status' do + subject(:opt_in_status) do + described_class.opt_in_status(user: user, marketplace_home_url: marketplace_home_url) + end + + where(:opt_in_status, :opt_in_url, :marketplace_home_url, :expectation) do + 'enabled' | 'https://example.com' | nil | 'unset' + 'enabled' | 'https://example.com' | 'https://open-vsx.org' | 'unset' + 'enabled' | 'https://open-vsx.org' | 'https://open-vsx.org' | 'enabled' + 'disabled' | 'https://open-vsx.org' | 'https://open-vsx.org' | 'disabled' + 'unset' | 'https://open-vsx.org' | 'https://open-vsx.org' | 'unset' + end + + with_them do + it { is_expected.to eq(expectation) } + end + end + + describe '.enabled?' do + subject(:enabled) do + described_class.enabled?(user: user, marketplace_home_url: marketplace_home_url) + end + + where(:opt_in_status, :opt_in_url, :marketplace_home_url, :expectation) do + 'enabled' | 'https://open-vsx.org' | nil | false + 'enabled' | 'https://open-vsx.org' | 'https://example.com' | false + 'enabled' | 'https://example.com' | 'https://example.com' | true + 'disabled' | 'https://example.com' | 'https://example.com' | false + 'unset' | 'https://example.com' | 'https://example.com' | false + end + + with_them do + it { is_expected.to eq(expectation) } + end + end + + describe '.params' do + subject(:params) do + described_class.params(enabled: enabled, marketplace_home_url: marketplace_home_url) + end + + where(:enabled, :marketplace_home_url, :expected_status) do + true | 'https://example.com' | 'enabled' + false | 'https://example.com' | 'disabled' + end + + with_them do + it 'returns params for updating user_preferences' do + is_expected.to match( + extensions_marketplace_opt_in_status: expected_status, + extensions_marketplace_opt_in_url: marketplace_home_url + ) + end + end + end +end diff --git a/spec/lib/web_ide/extension_marketplace_spec.rb b/spec/lib/web_ide/extension_marketplace_spec.rb index c3a001a8332247..7d542d4c0724e6 100644 --- a/spec/lib/web_ide/extension_marketplace_spec.rb +++ b/spec/lib/web_ide/extension_marketplace_spec.rb @@ -7,6 +7,7 @@ let(:help_url) { "/help/user/project/web_ide/_index.md#extension-marketplace" } let(:user_preferences_url) { "/-/profile/preferences#integrations" } + let(:custom_home_url) { 'https://example.com:8444' } let(:custom_app_setting) do { enabled: true, @@ -109,15 +110,17 @@ describe '#webide_extension_marketplace_settings' do # 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 } } + where(:web_ide_extensions_marketplace, :vscode_extension_marketplace_settings, :app_setting, :opt_in_status, :opt_in_url, :expectation) do + # web_ide_extensions_marketplace | vscode_extension_marketplace_settings | app_setting | opt_in_status | opt_in_url | expectation + true | false | {} | :enabled | nil | lazy { { enabled: true, vscode_settings: ::WebIde::ExtensionMarketplacePreset.open_vsx.values } } + true | false | {} | :unset | nil | lazy { { enabled: false, reason: :opt_in_unset, help_url: /#{help_url}/, user_preferences_url: /#{user_preferences_url}/ } } + true | false | {} | :disabled | nil | lazy { { enabled: false, reason: :opt_in_disabled, help_url: /#{help_url}/, user_preferences_url: /#{user_preferences_url}/ } } + false | false | {} | :enabled | nil | lazy { { enabled: false, reason: :instance_disabled, help_url: /#{help_url}/ } } + true | true | {} | :enabled | nil | lazy { { enabled: false, reason: :instance_disabled, help_url: /#{help_url}/ } } + true | true | { enabled: false } | :enabled | nil | lazy { { enabled: false, reason: :instance_disabled, help_url: /#{help_url}/ } } + true | true | ref(:custom_app_setting) | :enabled | nil | lazy { { enabled: false, reason: :opt_in_unset, help_url: /#{help_url}/, user_preferences_url: /#{user_preferences_url}/ } } + true | true | ref(:custom_app_setting) | :enabled | ref(:custom_home_url) | lazy { { enabled: true, vscode_settings: custom_app_setting[:custom_values] } } + true | true | ref(:open_vsx_app_setting) | :enabled | nil | lazy { { enabled: true, vscode_settings: ::WebIde::ExtensionMarketplacePreset.open_vsx.values } } end # rubocop:enable Layout/LineLength @@ -132,7 +135,10 @@ stub_application_setting(vscode_extension_marketplace: app_setting) - current_user.update!(extensions_marketplace_opt_in_status: opt_in_status) + current_user.update!( + extensions_marketplace_opt_in_status: opt_in_status, + extensions_marketplace_opt_in_url: opt_in_url + ) end with_them do 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 6f768dce9f5b22..aae02ae3f6f12a 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 @@ -5,6 +5,7 @@ RSpec.describe WebIde::Settings::ExtensionMarketplaceMetadataGenerator, feature_category: :web_ide do using RSpec::Parameterized::TableSyntax + let(:marketplace_home_url) { "https://example.com" } let(:input_context) do { requested_setting_names: [:vscode_extension_marketplace_metadata], @@ -13,6 +14,7 @@ # NOTE: default value of 'vscode_extension_marketplace_metadata' is an empty hash. Include it here to # ensure that it always gets overwritten with the generated value vscode_extension_marketplace_metadata: {}, + vscode_extension_marketplace_home_url: marketplace_home_url, some_other_existing_setting_that_should_not_be_overwritten: "some context" } } @@ -90,7 +92,10 @@ def flipper_id end before do - allow(user).to receive(:extensions_marketplace_opt_in_status) { opt_in_status.to_s } + allow(::WebIde::ExtensionMarketplaceOptIn).to receive(:opt_in_status) + .with(user: user, marketplace_home_url: marketplace_home_url) + .and_return(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 }) diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb index d11be69a4710e2..d40a7f0218cf57 100644 --- a/spec/models/user_preference_spec.rb +++ b/spec/models/user_preference_spec.rb @@ -5,6 +5,7 @@ RSpec.describe UserPreference, feature_category: :user_profile do let_it_be(:user) { create(:user) } + let(:marketplace_home_url) { 'https://open-vsx.org' } let(:user_preference) { create(:user_preference, user: user) } describe 'validations' do @@ -254,41 +255,20 @@ end end - describe '#extensions_marketplace_enabled' do - where(:opt_in_status, :expected_value) do + describe '#extensions_marketplace_opt_in_url' do + where(:opt_in_url, :expectation) do [ - ["enabled", true], - ["disabled", false], - ["unset", false] + [nil, 'https://open-vsx.org'], + ['https://open-vsx.org', 'https://open-vsx.org'], + ['https://example.com', 'https://example.com'] ] end with_them do - it 'returns boolean from extensions_marketplace_opt_in_status' do - user_preference.update!(extensions_marketplace_opt_in_status: opt_in_status) + it 'reads attribute and defaults when nil' do + user_preference.update!(extensions_marketplace_opt_in_url: opt_in_url) - expect(user_preference.extensions_marketplace_enabled).to be expected_value - end - end - end - - describe '#extensions_marketplace_enabled=' do - where(:value, :expected_opt_in_status) do - [ - [true, "enabled"], - [false, "disabled"], - [0, "disabled"], - [1, "enabled"] - ] - end - - with_them do - it 'updates extensions_marketplace_opt_in_status' do - user_preference.update!(extensions_marketplace_opt_in_status: 'unset') - - user_preference.extensions_marketplace_enabled = value - - expect(user_preference.extensions_marketplace_opt_in_status).to be expected_opt_in_status + expect(user_preference.extensions_marketplace_opt_in_url).to eq expectation end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 75c554714d9621..755a256172ab1e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -89,8 +89,11 @@ it { is_expected.to delegate_method(:use_new_navigation).to(:user_preference) } it { is_expected.to delegate_method(:use_new_navigation=).to(:user_preference).with_arguments(:args) } - it { is_expected.to delegate_method(:extensions_marketplace_enabled).to(:user_preference) } - it { is_expected.to delegate_method(:extensions_marketplace_enabled=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:extensions_marketplace_opt_in_status).to(:user_preference) } + it { is_expected.to delegate_method(:extensions_marketplace_opt_in_status=).to(:user_preference).with_arguments(:args) } + + it { is_expected.to delegate_method(:extensions_marketplace_opt_in_url).to(:user_preference) } + it { is_expected.to delegate_method(:extensions_marketplace_opt_in_url=).to(:user_preference).with_arguments(:args) } it { is_expected.to delegate_method(:pinned_nav_items).to(:user_preference) } it { is_expected.to delegate_method(:pinned_nav_items=).to(:user_preference).with_arguments(:args) } diff --git a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb index d7f852b722ed0e..85be19c0027ca8 100644 --- a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb +++ b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb @@ -25,6 +25,18 @@ let(:mutation) { graphql_mutation(:userPreferencesUpdate, input) } let(:mutation_response) { graphql_mutation_response(:userPreferencesUpdate) } + before do + stub_application_setting(vscode_extension_marketplace: { + enabled: false, + preset: 'custom', + custom_values: { + item_url: 'https://example.com/item/url', + service_url: 'https://example.com/service/url', + resource_url_template: 'https://example.com/resource/url/template' + } + }) + end + context 'when user has no existing preference' do it 'creates the user preference record' do post_graphql_mutation(mutation, current_user: current_user) @@ -41,6 +53,7 @@ expect(current_user.user_preference.persisted?).to eq(true) expect(current_user.user_preference.extensions_marketplace_opt_in_status).to eq('enabled') + expect(current_user.user_preference.extensions_marketplace_opt_in_url).to eq('https://example.com') expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s) expect(current_user.user_preference.visibility_pipeline_id_type).to eq('iid') expect(current_user.user_preference.use_work_items_view).to eq(true) -- GitLab From 05a3348bdc40cae4fe0358231abcfbe755dc6bac Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Mon, 17 Mar 2025 12:25:20 -0500 Subject: [PATCH 2/3] Add validation spec for extensions_marketplace_opt_in_url --- spec/models/user_preference_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb index d40a7f0218cf57..5e4afb2b0c041e 100644 --- a/spec/models/user_preference_spec.rb +++ b/spec/models/user_preference_spec.rb @@ -83,6 +83,10 @@ end end + describe 'extensions_marketplace_opt_in_url' do + it { is_expected.to validate_length_of(:extensions_marketplace_opt_in_url).is_at_most(512) } + end + describe 'organization_groups_projects_display' do it 'is set to 0 by default' do pref = described_class.new -- GitLab From aeb2c1bec3e0835337ecca1d71d11c2f77fc6928 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Mon, 17 Mar 2025 17:46:21 +0000 Subject: [PATCH 3/3] Add if_not_exists to db migration --- ...0_add_extension_marketplace_opt_in_url_to_user_preference.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20250313163310_add_extension_marketplace_opt_in_url_to_user_preference.rb b/db/migrate/20250313163310_add_extension_marketplace_opt_in_url_to_user_preference.rb index 8216d7acf4ae01..abe4b6571e1cc1 100644 --- a/db/migrate/20250313163310_add_extension_marketplace_opt_in_url_to_user_preference.rb +++ b/db/migrate/20250313163310_add_extension_marketplace_opt_in_url_to_user_preference.rb @@ -6,7 +6,7 @@ class AddExtensionMarketplaceOptInUrlToUserPreference < Gitlab::Database::Migrat def up with_lock_retries do - add_column :user_preferences, :extensions_marketplace_opt_in_url, :text, null: true + add_column :user_preferences, :extensions_marketplace_opt_in_url, :text, null: true, if_not_exists: true end # This is well above the 253 full domain name limit. We go ahead and overshoot because -- GitLab