diff --git a/app/assets/javascripts/vscode_extension_marketplace/components/settings_app.vue b/app/assets/javascripts/vscode_extension_marketplace/components/settings_app.vue index 0b620281aa7c7758a81377f5f1b86573cadd7511..6b314770b3359dd8bda1d3760eebdacd1df62ea7 100644 --- a/app/assets/javascripts/vscode_extension_marketplace/components/settings_app.vue +++ b/app/assets/javascripts/vscode_extension_marketplace/components/settings_app.vue @@ -1,20 +1,25 @@ diff --git a/app/assets/javascripts/vscode_extension_marketplace/components/settings_form.vue b/app/assets/javascripts/vscode_extension_marketplace/components/settings_form.vue new file mode 100644 index 0000000000000000000000000000000000000000..0d502984e97504130868f95c1a31e382b32ba053 --- /dev/null +++ b/app/assets/javascripts/vscode_extension_marketplace/components/settings_form.vue @@ -0,0 +1,215 @@ + + + diff --git a/app/assets/javascripts/vscode_extension_marketplace/constants.js b/app/assets/javascripts/vscode_extension_marketplace/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..527a1926e82379d41b18a25680d396ac4f8d4298 --- /dev/null +++ b/app/assets/javascripts/vscode_extension_marketplace/constants.js @@ -0,0 +1,2 @@ +export const PRESET_OPEN_VSX = 'open_vsx'; +export const PRESET_CUSTOM = 'custom'; diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 74cae536d62392930d04674fe1bba3de5a483cb2..235e5438b27fe5f7bd478938cd1411a9e9ecbae6 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -587,7 +587,8 @@ def visible_attributes :global_search_users_enabled, :global_search_issues_enabled, :global_search_merge_requests_enabled, - :vscode_extension_marketplace + :vscode_extension_marketplace, + :vscode_extension_marketplace_enabled ].tap do |settings| unless Gitlab.com? settings << :resource_usage_limits @@ -689,10 +690,15 @@ def vscode_extension_marketplace_settings_view # NOTE: This is intentionally not scoped to a specific actor since it affects instance-level settings. return unless Feature.enabled?(:vscode_extension_marketplace_settings, nil) + presets = ::WebIde::ExtensionMarketplacePreset.all.map do |preset| + preset.to_h.deep_transform_keys { |key| key.to_s.camelize(:lower) } + end + { title: _('VS Code Extension Marketplace'), description: vscode_extension_marketplace_settings_description, view_model: { + presets: presets, initialSettings: @application_setting.vscode_extension_marketplace || {} } } diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 5e9a8a2af0b5f9e04357b1fb66663843335e853b..9b6c02694ebd94bd5fa0551c78bb8189887da661 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -120,7 +120,6 @@ def self.kroki_formats_attributes attribute :repository_storages_weighted, default: -> { {} } attribute :kroki_formats, default: -> { {} } attribute :default_branch_protection_defaults, default: -> { {} } - attribute :vscode_extension_marketplace, default: -> { {} } chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds @@ -950,6 +949,9 @@ def self.kroki_formats_attributes validates :vscode_extension_marketplace, json_schema: { filename: "application_setting_vscode_extension_marketplace", detail_errors: true } + jsonb_accessor :vscode_extension_marketplace, + vscode_extension_marketplace_enabled: [:boolean, { default: false, store_key: :enabled }] + before_validation :ensure_uuid! before_validation :coerce_repository_storages_weighted, if: :repository_storages_weighted_changed? before_validation :normalize_default_branch_name diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index df5e00bca0632f5667d551cc2db08a3ac393cd62..3c7a75d8140c93e69bee1ff37906c345f89aa789 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -319,7 +319,7 @@ def defaults # rubocop:disable Metrics/AbcSize seat_control: 0, show_migrate_from_jenkins_banner: true, ropc_without_client_credentials: true, - vscode_extension_marketplace: {} + vscode_extension_marketplace_enabled: false }.tap do |hsh| hsh.merge!(non_production_defaults) unless Rails.env.production? end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ff94a0a92f1ac9dcce9b5aa7a713f142f41cf8ad..45b5c60d9846d3777e8ef225e29210645f5ea074 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24489,18 +24489,48 @@ msgstr "" msgid "Exported requirements" msgstr "" +msgid "ExtensionMarketplace|A valid URL is required." +msgstr "" + msgid "ExtensionMarketplace|An unknown error occurred. Please try again." msgstr "" +msgid "ExtensionMarketplace|Disable Open VSX extension registry to set a custom value for this field." +msgstr "" + +msgid "ExtensionMarketplace|Enable Extension Marketplace" +msgstr "" + +msgid "ExtensionMarketplace|Enable the VS Code extension marketplace for all users." +msgstr "" + msgid "ExtensionMarketplace|Extension marketplace settings updated." msgstr "" +msgid "ExtensionMarketplace|Extension registry settings" +msgstr "" + msgid "ExtensionMarketplace|Failed to update extension marketplace settings." msgstr "" msgid "ExtensionMarketplace|Failed to update extension marketplace settings. %{message}" msgstr "" +msgid "ExtensionMarketplace|Item URL" +msgstr "" + +msgid "ExtensionMarketplace|Learn more about the %{linkStart}Open VSX Registry%{linkEnd}" +msgstr "" + +msgid "ExtensionMarketplace|Resource URL Template" +msgstr "" + +msgid "ExtensionMarketplace|Service URL" +msgstr "" + +msgid "ExtensionMarketplace|Use Open VSX extension registry" +msgstr "" + msgid "External URL" msgstr "" diff --git a/spec/frontend/vscode_extension_marketplace/components/settings_app_spec.js b/spec/frontend/vscode_extension_marketplace/components/settings_app_spec.js index a2f64ee30906132d620ccb355d8fa1756c9c45cd..ab5eb5ca4bb9028410ebd9986e6bb3ddf2e5bc8c 100644 --- a/spec/frontend/vscode_extension_marketplace/components/settings_app_spec.js +++ b/spec/frontend/vscode_extension_marketplace/components/settings_app_spec.js @@ -1,18 +1,29 @@ import { nextTick } from 'vue'; -import { GlAlert, GlButton, GlForm, GlFormFields, GlFormTextarea } from '@gitlab/ui'; +import { GlAlert, GlAccordion, GlAccordionItem, GlToggle } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import { logError } from '~/lib/logger'; import SettingsApp from '~/vscode_extension_marketplace/components/settings_app.vue'; +import SettingsForm from '~/vscode_extension_marketplace/components/settings_form.vue'; import toast from '~/vue_shared/plugins/global_toast'; +import { PRESETS } from '../mock_data'; jest.mock('~/lib/logger'); jest.mock('~/vue_shared/plugins/global_toast'); jest.mock('lodash/uniqueId', () => (x) => `${x}testUnique`); -const TEST_NEW_SETTINGS = { enabled: false, preset: 'open_vsx' }; -const EXPECTED_FORM_ID = 'extension-marketplace-settings-form-testUnique'; +const TEST_NEW_SETTINGS = { preset: 'open_vsx' }; +const TEST_INIT_SETTINGS = { + enabled: true, + preset: 'custom', + custom_values: { + item_url: 'abc', + service_url: 'def', + resource_url_template: 'ghi', + }, +}; describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { let wrapper; @@ -22,18 +33,16 @@ describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { const createComponent = (props = {}) => { wrapper = shallowMount(SettingsApp, { propsData: { + presets: PRESETS, ...props, }, - stubs: { - GlFormFields, - }, }); }; - const findForm = () => wrapper.findComponent(GlForm); - const findFormFields = () => wrapper.findComponent(GlFormFields); - const findTextarea = () => wrapper.findComponent(GlFormTextarea); - const findSaveButton = () => wrapper.findComponent(GlButton); + const findAccordion = () => wrapper.findComponent(GlAccordion); + const findAccordionItem = () => findAccordion().findComponent(GlAccordionItem); + const findSettingsForm = () => findAccordionItem().findComponent(SettingsForm); + const findToggle = () => wrapper.findComponent(GlToggle); const findErrorAlert = () => wrapper.findComponent(GlAlert); const findErrorAlertItems = () => findErrorAlert() @@ -58,63 +67,67 @@ describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { createComponent(); }); - it('renders form', () => { - expect(findForm().attributes('id')).toBe(EXPECTED_FORM_ID); + it('renders toggle', () => { + expect(findToggle().props()).toMatchObject({ + value: false, + isLoading: false, + label: 'Enable Extension Marketplace', + help: 'Enable the VS Code extension marketplace for all users.', + labelPosition: 'top', + }); }); - it('renders form fields', () => { - expect(findFormFields().props()).toMatchObject({ - formId: EXPECTED_FORM_ID, - values: { - settings: {}, - }, - fields: SettingsApp.FIELDS, + it('renders accordion and accordion item', () => { + expect(findAccordion().props()).toMatchObject({ + headerLevel: 3, }); - }); - it('renders settings textarea', () => { - expect(findTextarea().attributes()).toMatchObject({ - id: 'gl-form-field-testUnique', - value: '{}', + expect(findAccordionItem().props()).toMatchObject({ + title: 'Extension registry settings', }); }); - it('renders save button', () => { - expect(findSaveButton().attributes()).toMatchObject({ - type: 'submit', - variant: 'confirm', - category: 'primary', - 'aria-describedby': 'extensions-marketplace-settings-error-alert', + it('renders inner form', () => { + expect(findSettingsForm().props()).toEqual({ + initialSettings: {}, + presets: PRESETS, + submitButtonAttrs: { + 'aria-describedby': 'extension-marketplace-settings-error-alert', + loading: false, + }, }); - expect(findSaveButton().props('loading')).toBe(false); - expect(findSaveButton().text()).toBe('Save changes'); }); }); - describe('when submitted', () => { - beforeEach(async () => { + describe('when enablement toggle is changed', () => { + beforeEach(() => { createComponent(); - findTextarea().vm.$emit('input', JSON.stringify(TEST_NEW_SETTINGS)); - await nextTick(); - - findFormFields().vm.$emit('submit'); + findToggle().vm.$emit('change', true); }); it('triggers loading', () => { - expect(findSaveButton().props('loading')).toBe(true); + expect(findSettingsForm().props('submitButtonAttrs')).toEqual({ + 'aria-describedby': 'extension-marketplace-settings-error-alert', + loading: true, + }); + + expect(findToggle().props()).toMatchObject({ + value: false, + isLoading: true, + }); }); it('makes submit request', () => { expect(submitSpy).toHaveBeenCalledTimes(1); expect(submitSpy).toHaveBeenCalledWith({ - vscode_extension_marketplace: TEST_NEW_SETTINGS, + vscode_extension_marketplace_enabled: true, }); }); it('while loading, prevents extra submit', () => { - findFormFields().vm.$emit('submit'); - findFormFields().vm.$emit('submit'); + findToggle().vm.$emit('change', true); + findToggle().vm.$emit('change', true); expect(submitSpy).toHaveBeenCalledTimes(1); }); @@ -126,7 +139,7 @@ describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { expect(toast).toHaveBeenCalledTimes(1); expect(toast).toHaveBeenCalledWith('Extension marketplace settings updated.'); - expect(findSaveButton().props('loading')).toBe(false); + expect(findToggle().props('isLoading')).toBe(false); }); it('does not show error alert', () => { @@ -134,13 +147,49 @@ describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { }); }); + describe('with initial settings', () => { + beforeEach(() => { + createComponent({ + initialSettings: TEST_INIT_SETTINGS, + }); + }); + + it('initializes settings in toggle', () => { + expect(findToggle().props('value')).toBe(true); + }); + + it('initializes settings in form', () => { + expect(findSettingsForm().props('initialSettings')).toBe(TEST_INIT_SETTINGS); + }); + + it('when submitted, submits settings', async () => { + expect(submitSpy).not.toHaveBeenCalled(); + + findSettingsForm().vm.$emit('submit', TEST_NEW_SETTINGS); + await waitForPromises(); + + expect(submitSpy).toHaveBeenCalledTimes(1); + expect(submitSpy).toHaveBeenCalledWith({ + vscode_extension_marketplace: { + enabled: true, + preset: 'open_vsx', + custom_values: { + item_url: 'abc', + service_url: 'def', + resource_url_template: 'ghi', + }, + }, + }); + }); + }); + describe('when submitted and errored', () => { beforeEach(() => { submitSpy.mockReturnValue([400]); createComponent(); - findFormFields().vm.$emit('submit'); + findSettingsForm().vm.$emit('submit', {}); }); it('shows error message', async () => { @@ -149,7 +198,7 @@ describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { await axios.waitForAll(); expect(findErrorAlert().exists()).toBe(true); - expect(findErrorAlert().attributes('id')).toBe('extensions-marketplace-settings-error-alert'); + expect(findErrorAlert().attributes('id')).toBe('extension-marketplace-settings-error-alert'); expect(findErrorAlert().props('dismissible')).toBe(false); expect(findErrorAlert().text()).toBe( 'Failed to update extension marketplace settings. An unknown error occurred. Please try again.', @@ -168,20 +217,12 @@ describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { ); }); - it('updates state on textarea', async () => { - expect(findTextarea().attributes('state')).toBe('true'); - - await axios.waitForAll(); - - expect(findTextarea().attributes('state')).toBeUndefined(); - }); - it('hides error message with another submit', async () => { await axios.waitForAll(); expect(findErrorAlert().exists()).toBe(true); - findFormFields().vm.$emit('submit'); + findSettingsForm().vm.$emit('submit', TEST_NEW_SETTINGS); await nextTick(); expect(findErrorAlert().exists()).toBe(false); @@ -197,7 +238,7 @@ describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { createComponent(); - findFormFields().vm.$emit('submit'); + findSettingsForm().vm.$emit('submit', TEST_NEW_SETTINGS); }); it('shows error message', async () => { @@ -213,16 +254,4 @@ describe('~/vscode_extension_marketplace/components/settings_app.vue', () => { ]); }); }); - - describe('with initialSettings', () => { - beforeEach(() => { - createComponent({ - initialSettings: TEST_NEW_SETTINGS, - }); - }); - - it('initializes the form with given settings', () => { - expect(findTextarea().props('value')).toBe(JSON.stringify(TEST_NEW_SETTINGS, null, 2)); - }); - }); }); diff --git a/spec/frontend/vscode_extension_marketplace/components/settings_form_spec.js b/spec/frontend/vscode_extension_marketplace/components/settings_form_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..a0faca78763b6f3ebbdc73ba99aeb5210879606d --- /dev/null +++ b/spec/frontend/vscode_extension_marketplace/components/settings_form_spec.js @@ -0,0 +1,240 @@ +import { GlForm, GlFormFields, GlToggle, GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import SettingsForm from '~/vscode_extension_marketplace/components/settings_form.vue'; +import { PRESETS } from '../mock_data'; + +jest.mock('lodash/uniqueId', () => (x) => `${x}uniqueId`); + +const TEST_FORM_ID = 'extension-marketplace-settings-form-uniqueId'; +const TEST_SUBMIT_BUTTON_ATTRS = { + 'aria-describedby': 'extension-marketplace-settings-error-alert', +}; +const TEST_CUSTOM_VALUES = { + item_url: 'abc', + service_url: 'def', + resource_url_template: 'ghi', +}; + +describe('~/vscode_extension_marketplace/components/settings_form.vue', () => { + let wrapper; + + const findForm = () => wrapper.findComponent(GlForm); + const findFormFields = () => findForm().findComponent(GlFormFields); + const findOpenVsxToggle = () => findFormFields().findComponent(GlToggle); + const findButton = () => findForm().findComponent(GlButton); + + const createComponent = (props = {}) => { + wrapper = shallowMount(SettingsForm, { + propsData: { + presets: PRESETS, + submitButtonAttrs: TEST_SUBMIT_BUTTON_ATTRS, + ...props, + }, + stubs: { + GlFormFields, + }, + }); + }; + + beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + describe('default', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders form', () => { + expect(findForm().attributes('id')).toBe(TEST_FORM_ID); + }); + + it('renders form fields', () => { + const expectedInputAttrs = { + readonly: true, + 'aria-description': + 'Disable Open VSX extension registry to set a custom value for this field.', + width: 'lg', + }; + + expect(findFormFields().props()).toEqual({ + formId: TEST_FORM_ID, + serverValidations: {}, + fields: { + useOpenVsx: { + label: 'Use Open VSX extension registry', + }, + presetItemUrl: { + label: 'Item URL', + inputAttrs: expectedInputAttrs, + }, + presetServiceUrl: { + label: 'Service URL', + inputAttrs: expectedInputAttrs, + }, + presetResourceUrlTemplate: { + label: 'Resource URL Template', + inputAttrs: expectedInputAttrs, + }, + }, + values: { + useOpenVsx: true, + presetItemUrl: 'https://open-vsx.org/vscode/item', + presetResourceUrlTemplate: + 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', + presetServiceUrl: 'https://open-vsx.org/vscode/gallery', + }, + }); + }); + + it('renders open vsx toggle', () => { + expect(findOpenVsxToggle().props('value')).toEqual(true); + expect(findOpenVsxToggle().attributes()).toMatchObject({ + id: 'gl-form-field-uniqueId', + label: 'Use Open VSX extension registry', + labelposition: 'hidden', + }); + }); + + it('renders save button', () => { + expect(findButton().attributes()).toMatchObject({ + type: 'submit', + variant: 'confirm', + category: 'primary', + ...TEST_SUBMIT_BUTTON_ATTRS, + }); + }); + }); + + describe('with preset=open_vsx and custom_values', () => { + beforeEach(() => { + createComponent({ + initialSettings: { + custom_values: TEST_CUSTOM_VALUES, + }, + }); + }); + + it('changes values when openvsx is toggled', async () => { + // NOTE: gl-form-fields emits `input` on mount to only include fiels created with + expect(findFormFields().props('values')).toEqual({ + useOpenVsx: true, + presetItemUrl: 'https://open-vsx.org/vscode/item', + presetServiceUrl: 'https://open-vsx.org/vscode/gallery', + presetResourceUrlTemplate: + 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', + }); + + await findOpenVsxToggle().vm.$emit('change', false); + + expect(findFormFields().props('values')).toEqual({ + useOpenVsx: false, + presetItemUrl: 'https://open-vsx.org/vscode/item', + presetServiceUrl: 'https://open-vsx.org/vscode/gallery', + presetResourceUrlTemplate: + 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', + itemUrl: 'abc', + serviceUrl: 'def', + resourceUrlTemplate: 'ghi', + }); + }); + }); + + describe('with preset=custom and custom_values', () => { + beforeEach(() => { + createComponent({ + initialSettings: { + custom_values: TEST_CUSTOM_VALUES, + preset: 'custom', + }, + }); + }); + + it('stores custom values when preset is changed back and forth', async () => { + await findFormFields().vm.$emit('input', { + useOpenVsx: false, + itemUrl: 'xyz', + serviceUrl: 'xyz', + resourceUrlTemplate: 'xyz', + }); + await findOpenVsxToggle().vm.$emit('change', true); + + expect(findFormFields().props('values')).toEqual({ + useOpenVsx: true, + itemUrl: 'xyz', + serviceUrl: 'xyz', + resourceUrlTemplate: 'xyz', + presetItemUrl: 'https://open-vsx.org/vscode/item', + presetServiceUrl: 'https://open-vsx.org/vscode/gallery', + presetResourceUrlTemplate: + 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', + }); + + await findOpenVsxToggle().vm.$emit('change', false); + + expect(findFormFields().props('values')).toEqual({ + useOpenVsx: false, + itemUrl: 'xyz', + serviceUrl: 'xyz', + resourceUrlTemplate: 'xyz', + presetItemUrl: 'https://open-vsx.org/vscode/item', + presetServiceUrl: 'https://open-vsx.org/vscode/gallery', + presetResourceUrlTemplate: + 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', + }); + }); + + it('renders customizable fields', () => { + expect(findFormFields().props('fields')).toEqual( + expect.objectContaining({ + itemUrl: { + label: 'Item URL', + inputAttrs: { + placeholder: 'https://...', + width: 'lg', + }, + validators: expect.any(Array), + }, + serviceUrl: { + label: 'Service URL', + inputAttrs: { + placeholder: 'https://...', + width: 'lg', + }, + validators: expect.any(Array), + }, + resourceUrlTemplate: { + label: 'Resource URL Template', + inputAttrs: { + placeholder: 'https://...', + width: 'lg', + }, + validators: expect.any(Array), + }, + }), + ); + }); + + it.each` + fieldName | value | expectation + ${'itemUrl'} | ${''} | ${'A valid URL is required.'} + ${'itemUrl'} | ${'abc def'} | ${'A valid URL is required.'} + ${'itemUrl'} | ${'https://example.com'} | ${''} + ${'serviceUrl'} | ${''} | ${'A valid URL is required.'} + ${'serviceUrl'} | ${'abc def'} | ${'A valid URL is required.'} + ${'serviceUrl'} | ${'https://example.com'} | ${''} + ${'resourceUrlTemplate'} | ${''} | ${'A valid URL is required.'} + ${'resourceUrlTemplate'} | ${'abc def'} | ${'A valid URL is required.'} + ${'resourceUrlTemplate'} | ${'https://example.com'} | ${''} + `( + 'validates $fieldName where $value is "$expectation"', + ({ fieldName, value, expectation }) => { + const field = findFormFields().props('fields')[fieldName]; + + const result = field.validators.reduce((msg, validator) => msg || validator(value), ''); + + expect(result).toBe(expectation); + }, + ); + }); +}); diff --git a/spec/frontend/vscode_extension_marketplace/mock_data.js b/spec/frontend/vscode_extension_marketplace/mock_data.js new file mode 100644 index 0000000000000000000000000000000000000000..5f0ea03fe135e44b19cbf346665ec0bfebba9b16 --- /dev/null +++ b/spec/frontend/vscode_extension_marketplace/mock_data.js @@ -0,0 +1,11 @@ +export const PRESETS = [ + { + key: 'open_vsx', + name: 'Open VSX', + values: { + serviceUrl: 'https://open-vsx.org/vscode/gallery', + itemUrl: 'https://open-vsx.org/vscode/item', + resourceUrlTemplate: 'https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}', + }, + }, +]; diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index 485728c007e9f3d0d12f2f91b8bd635f4dc0e4f7..b44344926f7642bee864c511b023d5df05c31da1 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -462,11 +462,14 @@ context 'with flag on' do it 'returns hash of view properties' do - expect(helper.vscode_extension_marketplace_settings_view).to eq({ + expect(helper.vscode_extension_marketplace_settings_view).to match({ title: _('VS Code Extension Marketplace'), description: _('Enable VS Code Extension Marketplace and configure the extensions registry for Web IDE.'), view_model: { - initialSettings: vscode_extension_marketplace + initialSettings: vscode_extension_marketplace, + presets: [ + hash_including("key" => "open_vsx") + ] } }) end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 4d90c8de03979d82df34951bf5820e1aed8971dc..f5397bb73c670a473b31f0662c92bb90dcb9482b 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -70,7 +70,8 @@ it { expect(setting.global_search_merge_requests_enabled).to be(true) } it { expect(setting.global_search_snippet_titles_enabled).to be(true) } it { expect(setting.global_search_users_enabled).to be(true) } - it { expect(setting.vscode_extension_marketplace).to eq({}) } + it { expect(setting.vscode_extension_marketplace).to eq({ "enabled" => false }) } + it { expect(setting.vscode_extension_marketplace_enabled?).to be(false) } it do expect(setting.sign_in_restrictions).to eq({ @@ -1776,7 +1777,7 @@ def expect_invalid end end - describe 'vscode_extension_marketplace' do + describe '#vscode_extension_marketplace' do let(:invalid_custom) { { enabled: false, preset: "custom", custom_values: {} } } let(:valid_open_vsx) { { enabled: true, preset: "open_vsx" } } let(:valid_custom) do @@ -1802,6 +1803,24 @@ def expect_invalid it { is_expected.not_to allow_value(invalid_custom).for(:vscode_extension_marketplace) } end + describe '#vscode_extension_marketplace_enabled' do + it 'is updated when underlying vscode_extension_marketplace changes' do + expect(setting.vscode_extension_marketplace_enabled).to be(false) + + setting.vscode_extension_marketplace = { enabled: true, preset: "open_vsx" } + + expect(setting.vscode_extension_marketplace_enabled).to be(true) + end + + it 'updates the underlying vscode_extension_marketplace when changed' do + setting.vscode_extension_marketplace = { enabled: true, preset: "open_vsx" } + + setting.vscode_extension_marketplace_enabled = false + + expect(setting.vscode_extension_marketplace).to eq({ "enabled" => false, "preset" => "open_vsx" }) + end + end + describe '#static_objects_external_storage_auth_token=', :aggregate_failures do subject { setting.static_objects_external_storage_auth_token = token } diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index f0ef531bf83a20c0e525149e88f72b32639932b3..4026efcdce64b5e7280e9a833556f0789acba9a0 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -1221,5 +1221,16 @@ expect(json_response['resource_usage_limits']).to eq(hash) end end + + context 'with vscode_extension_marketplace_enabled' do + it 'updates underlying vscode_extension_marketplace field' do + put api("/application/settings", admin), + params: { vscode_extension_marketplace_enabled: true } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['vscode_extension_marketplace_enabled']).to eq(true) + expect(json_response['vscode_extension_marketplace']).to eq({ "enabled" => true }) + end + end end end diff --git a/spec/views/admin/application_settings/_extension_marketplace.html.haml_spec.rb b/spec/views/admin/application_settings/_extension_marketplace.html.haml_spec.rb index e8b32a2305d42bde4cd4c3f7bd5ae8052bf530da..c4379ad581d246866fe6a3728bdf1b8a92f1f1c7 100644 --- a/spec/views/admin/application_settings/_extension_marketplace.html.haml_spec.rb +++ b/spec/views/admin/application_settings/_extension_marketplace.html.haml_spec.rb @@ -31,7 +31,22 @@ it 'renders data-view-model for vue app' do vue_app = page.at('#js-extension-marketplace-settings-app') - expected_json = { initialSettings: {} }.to_json + expected_presets = ::WebIde::ExtensionMarketplacePreset.all.map do |x| + { + key: x.key, + name: x.name, + values: { + serviceUrl: x.values[:service_url], + itemUrl: x.values[:item_url], + resourceUrlTemplate: x.values[:resource_url_template] + } + } + end + + expected_json = { + presets: expected_presets, + initialSettings: { enabled: false } + }.to_json expect(vue_app).not_to be_nil expect(vue_app['data-view-model']).to eq(expected_json)