Commit 3899f83e authored by Gabriel Mazetto's avatar Gabriel Mazetto Committed by Doug Stull

Refactor `_gl_toggle` partial to ViewComponent

- introduce ViewComponent
parent 94d3458f
......@@ -46,6 +46,10 @@ AllCops:
CacheRootDirectory: <%= Dir.getwd %>/tmp
MaxFilesInCache: 30000
Metrics/ParameterLists:
Exclude:
- 'app/components/**/*'
Cop/AvoidKeywordArgumentsInSidekiqWorkers:
Enabled: true
Include:
......
......@@ -11,6 +11,8 @@ gem 'responders', '~> 3.0'
gem 'sprockets', '~> 3.7.0'
gem 'view_component', '~> 2.50.0'
# Default values for AR models
gem 'default_value_for', '~> 3.4.0'
......
......@@ -645,7 +645,7 @@ GEM
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.9.1)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
i18n_data (0.8.0)
icalendar (2.4.1)
......@@ -1362,6 +1362,9 @@ GEM
activerecord (>= 3.0)
activesupport (>= 3.0)
version_sorter (2.2.4)
view_component (2.50.0)
activesupport (>= 5.0.0, < 8.0)
method_source (~> 1.0)
vmstat (2.3.0)
warden (1.2.8)
rack (>= 2.0.6)
......@@ -1682,6 +1685,7 @@ DEPENDENCIES
valid_email (~> 0.1)
validates_hostname (~> 1.0.11)
version_sorter (~> 2.2.4)
view_component (~> 2.50.0)
vmstat (~> 2.3.0)
warning (~> 1.2.0)
webauthn (~> 2.3)
......
# frozen_string_literal: true
module Pajamas
class Component < ViewComponent::Base
private
# :nocov:
# Filter a given a value against a list of allowed values
# If no value is given or value is not allowed return default one
#
# @param [Object] value
# @param [Enumerable] allowed_values
# @param [Object] default
def filter_attribute(value, allowed_values, default: nil)
return default unless value
return value if allowed_values.include?(value)
default
end
# :nocov:
end
end
%span{ class: @classes,
data: { name: @name,
id: @id,
is_checked: @is_checked.to_s,
disabled: @is_disabled.to_s,
is_loading: @is_loading.to_s,
label: @label,
help: @help,
label_position: @label_position,
**@data } }
-# Leverage this block to render a rich help text. To render a plain text help text,
-# prefer the `help` parameter.
- if content.present?
.gl-text-secondary.gl-mt-1
= content
# frozen_string_literal: true
# Renders a GlToggle root element
# To actually initialize the component, make sure to call the initToggle helper from ~/toggles.
class Pajamas::ToggleComponent < Pajamas::Component
LABEL_POSITION_OPTIONS = [:top, :left, :hidden].freeze
# @param [String] classes
# @param [String] label
# @param [Symbol] label_position :top, :left or :hidden
# @param [String] id
# @param [String] name
# @param [String] help
# @param [Hash] data
# @param [Boolean] is_disabled
# @param [Boolean] is_checked
# @param [Boolean] is_loading
def initialize(
classes:, label: nil, label_position: nil,
id: nil, name: nil, help: nil, data: {},
is_disabled: false, is_checked: false, is_loading: false)
@id = id
@name = name
@classes = classes
@label = label
@label_position = filter_attribute(label_position, LABEL_POSITION_OPTIONS)
@help = help
@data = data
@is_disabled = is_disabled
@is_checked = is_checked
@is_loading = is_loading
end
end
......@@ -14,12 +14,11 @@
- lets_encrypt_link_start = "<a href=\"%{lets_encrypt_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { lets_encrypt_link_url: lets_encrypt_link_url }
- lets_encrypt_link_end = "</a>".html_safe
= _("Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: lets_encrypt_link_end }
= render "shared/gl_toggle",
id: "pages_domain_auto_ssl_enabled_button",
is_checked: auto_ssl_available_and_enabled,
classes: "js-project-feature-toggle js-enable-ssl-gl-toggle mt-2",
label: _("Automatic certificate management using Let's Encrypt"),
label_position: 'hidden'
= render Pajamas::ToggleComponent.new(id: 'pages_domain_auto_ssl_enabled_button',
classes: 'js-project-feature-toggle js-enable-ssl-gl-toggle mt-2',
is_checked: auto_ssl_available_and_enabled,
label: _("Automatic certificate management using Let's Encrypt"),
label_position: :hidden)
= f.hidden_field :auto_ssl_enabled?, class: "js-project-feature-toggle-input"
%p.gl-text-secondary.gl-mt-1
- docs_link_url = help_page_path("user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md")
......
......@@ -24,10 +24,9 @@
.form-group.row
= f.label :allow_force_push, s_("ProtectedBranch|Allowed to force push:"), class: 'col-md-2 gl-text-left text-md-right'
.col-md-10
= render "shared/gl_toggle",
classes: 'js-force-push-toggle',
= render Pajamas::ToggleComponent.new(classes: 'js-force-push-toggle',
label: s_("ProtectedBranch|Allowed to force push"),
label_position: 'hidden' do
label_position: :hidden) do
- force_push_docs_url = help_page_url('topics/git/git_rebase', anchor: 'force-push')
- force_push_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: force_push_docs_url }
= (s_("ProtectedBranch|Allow all users with push access to %{tag_start}force push%{tag_end}.") % { tag_start: force_push_link_start, tag_end: '</a>' }).html_safe
......
-# This partial renders a GlToggle root element.
-# To actually initialize the component, make sure to call the initToggle helper from ~/toggles.
- classes = local_assigns.fetch(:classes)
- name = local_assigns.fetch(:name, nil)
- id = local_assigns.fetch(:id, nil)
- is_checked = local_assigns.fetch(:is_checked, false).to_s
- disabled = local_assigns.fetch(:disabled, false).to_s
- is_loading = local_assigns.fetch(:is_loading, false).to_s
- label = local_assigns.fetch(:label, nil)
- help = local_assigns.fetch(:help, nil)
- label_position = local_assigns.fetch(:label_position, nil)
- data = local_assigns.fetch(:data, {})
%span{ class: classes,
data: { name: name,
id: id,
is_checked: is_checked,
disabled: disabled,
is_loading: is_loading,
label: label,
help: help,
label_position: label_position,
**data } }
-# Leverage this block to render a rich help text. To render a plain text help text,
-# prefer the `help` parameter.
- if yield.present?
.gl-text-secondary.gl-mt-1
= yield
......@@ -34,8 +34,7 @@
= _('Members of %{group} can also push to this branch: %{branch}') % { group: (group_push_access_levels.size > 1 ? 'these groups' : 'this group'), branch: group_push_access_levels.map(&:humanize).to_sentence }
%td
= render "shared/gl_toggle",
classes: 'js-force-push-toggle',
= render Pajamas::ToggleComponent.new(classes: 'js-force-push-toggle',
label: s_("ProtectedBranch|Toggle allowed to force push"),
is_checked: protected_branch.allow_force_push,
label_position: 'hidden'
label_position: :hidden)
......@@ -18,6 +18,8 @@ module Gitlab
class Application < Rails::Application
config.load_defaults 6.1
config.view_component.preview_route = "/-/view_component/previews"
# This section contains configuration from Rails upgrades to override the new defaults so that we
# keep existing behavior.
#
......
......@@ -2,9 +2,8 @@
.form-group.row
= f.label :code_owner_approval_required, s_("ProtectedBranch|Require approval from code owners:"), class: 'col-md-2 text-left text-md-right'
.col-md-10
= render "shared/gl_toggle",
classes: 'js-code-owner-toggle',
= render Pajamas::ToggleComponent.new(classes: 'js-code-owner-toggle',
label: s_("ProtectedBranch|Toggle code owner approval"),
is_checked: true,
help: s_("ProtectedBranch|Reject code pushes that change files listed in the CODEOWNERS file."),
label_position: 'hidden'
label_position: :hidden)
- if @project.feature_available?(:code_owner_approval_required)
%td
= render "shared/gl_toggle",
classes: 'js-code-owner-toggle gl-mr-5',
= render Pajamas::ToggleComponent.new(classes: 'js-code-owner-toggle gl-mr-5',
label: s_("ProtectedBranch|Toggle code owner approval"),
is_checked: protected_branch.code_owner_approval_required,
label_position: 'hidden',
data: { qa_selector: 'code_owner_toggle_button', qa_branch_name: protected_branch.name }
label_position: :hidden,
data: { qa_selector: 'code_owner_toggle_button', qa_branch_name: protected_branch.name })
......@@ -7,8 +7,7 @@
= render partial: 'projects/settings/ee/access_level_dropdown', locals: { protected_branch: protected_branch, access_levels: protected_branch.push_access_levels, level_frequencies: access_level_frequencies(protected_branch.push_access_levels), input_basic_name: 'push_access_levels', disabled: !can_unprotect, toggle_class: 'js-allowed-to-push' }
%td
= render "shared/gl_toggle",
classes: 'js-force-push-toggle',
= render Pajamas::ToggleComponent.new(classes: 'js-force-push-toggle',
label: s_("ProtectedBranch|Toggle allowed to force push"),
is_checked: protected_branch.allow_force_push,
label_position: 'hidden'
label_position: :hidden)
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Pajamas::Component do
describe '#filter_attribute' do
let(:allowed) { %w[default something] }
it 'returns default value when no value is given' do
value = subject.send(:filter_attribute, nil, allowed, default: 'default')
expect(value).to eq('default')
end
it 'returns default value when invalid value is given' do
value = subject.send(:filter_attribute, 'invalid', allowed, default: 'default')
expect(value).to eq('default')
end
it 'returns given value when it is part of allowed list' do
value = subject.send(:filter_attribute, 'something', allowed, default: 'default')
expect(value).to eq('something')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require "spec_helper"
RSpec.describe 'shared/_gl_toggle.html.haml' do
context 'defaults' do
RSpec.describe Pajamas::ToggleComponent, type: :component do
context 'with defaults' do
before do
render partial: 'shared/gl_toggle', locals: {
classes: '.js-gl-toggle'
}
render_inline described_class.new(classes: 'js-feature-toggle')
end
it 'renders a toggle container with provided class' do
expect(rendered_component).to have_selector "[class='js-feature-toggle']"
end
it 'does not set a name' do
expect(rendered).not_to have_selector('[data-name]')
expect(rendered_component).not_to have_selector('[data-name]')
end
it 'sets default is-checked attributes' do
expect(rendered).to have_selector('[data-is-checked="false"]')
expect(rendered_component).to have_selector('[data-is-checked="false"]')
end
it 'sets default disabled attributes' do
expect(rendered).to have_selector('[data-disabled="false"]')
expect(rendered_component).to have_selector('[data-disabled="false"]')
end
it 'sets default is-loading attributes' do
expect(rendered).to have_selector('[data-is-loading="false"]')
expect(rendered_component).to have_selector('[data-is-loading="false"]')
end
it 'does not set a label' do
expect(rendered).not_to have_selector('[data-label]')
expect(rendered_component).not_to have_selector('[data-label]')
end
it 'does not set a label position' do
expect(rendered).not_to have_selector('[data-label-position]')
expect(rendered_component).not_to have_selector('[data-label-position]')
end
end
context 'with custom options' do
before do
render partial: 'shared/gl_toggle', locals: {
render_inline described_class.new(
classes: 'js-custom-gl-toggle',
name: 'toggle-name',
is_checked: true,
disabled: true,
is_disabled: true,
is_loading: true,
label: 'Custom label',
label_position: 'top',
label_position: :top,
data: {
foo: 'bar'
}
}
})
end
it 'sets the custom class' do
expect(rendered).to have_selector('.js-custom-gl-toggle')
expect(rendered_component).to have_selector('.js-custom-gl-toggle')
end
it 'sets the custom name' do
expect(rendered).to have_selector('[data-name="toggle-name"]')
expect(rendered_component).to have_selector('[data-name="toggle-name"]')
end
it 'sets the custom is-checked attributes' do
expect(rendered).to have_selector('[data-is-checked="true"]')
expect(rendered_component).to have_selector('[data-is-checked="true"]')
end
it 'sets the custom disabled attributes' do
expect(rendered).to have_selector('[data-disabled="true"]')
expect(rendered_component).to have_selector('[data-disabled="true"]')
end
it 'sets the custom is-loading attributes' do
expect(rendered).to have_selector('[data-is-loading="true"]')
expect(rendered_component).to have_selector('[data-is-loading="true"]')
end
it 'sets the custom label' do
expect(rendered).to have_selector('[data-label="Custom label"]')
expect(rendered_component).to have_selector('[data-label="Custom label"]')
end
it 'sets the custom label position' do
expect(rendered_component).to have_selector('[data-label-position="top"]')
end
it 'sets the cutom label position' do
expect(rendered).to have_selector('[data-label-position="top"]')
it 'sets custom data attributes' do
expect(rendered_component).to have_selector('[data-foo="bar"]')
end
end
context 'with setting label_position' do
using RSpec::Parameterized::TableSyntax
where(:position, :count) do
:top | 1
:left | 1
:hidden | 1
:bogus | 0
'bogus' | 0
nil | 0
end
before do
render_inline described_class.new(classes: '_class_', label_position: position)
end
it 'sets cutom data attributes' do
expect(rendered).to have_selector('[data-foo="bar"]')
with_them do
it { expect(rendered_component).to have_selector("[data-label-position='#{position}']", count: count) }
end
end
end
# frozen_string_literal: true
require 'view_component/test_helpers'
RSpec.configure do |config|
config.include ViewComponent::TestHelpers, type: :component
config.include Capybara::RSpecMatchers, type: :component
end
......@@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
.to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,events,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
.to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,events,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling,component}{,/**/}*_spec.rb")
end
end
......@@ -110,7 +110,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
.to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|events|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
.to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|events|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling|component)})
end
end
......
......@@ -55,6 +55,7 @@ module Quality
views
workers
tooling
component
],
integration: %w[
commands
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment