Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
34b1c65
Revert "Revert "Add view component previews using Lookbook" (#6004)"
aduth Oct 7, 2022
a6d4da9
Add changelog
aduth Oct 7, 2022
5928f26
Move Lookbook/ViewComponent preview config to application.rb
aduth Oct 7, 2022
7443e22
Add additional button variations
aduth Oct 7, 2022
4ef49be
Fix border-box bug
aduth Oct 7, 2022
2a25a88
Render default and component JavaScripts
aduth Oct 7, 2022
6356607
Add Accordion component preview
aduth Oct 7, 2022
8474949
Add Barcode component preview
aduth Oct 7, 2022
b0c62c7
Add Block Link component preview
aduth Oct 7, 2022
4537889
Use custom preview controller for synchronous script enqueues
aduth Oct 7, 2022
c304b52
Add Clipboard Button component preview
aduth Oct 7, 2022
02be2d9
Try handling CSP overrides in preview controller
aduth Oct 11, 2022
f17d286
Revert to referencing CSP from request
aduth Oct 11, 2022
564fedb
Add countdown component preview
aduth Oct 11, 2022
c629b7f
Add icon component preview
aduth Oct 11, 2022
99e7ee6
Add basic theming configuration for Lookbook
aduth Oct 11, 2022
8561fc7
Update Lookbook and remove workaround for now-fixed bug
aduth Oct 12, 2022
dd4f16f
Revert (back) to current_content_security_policy
aduth Oct 12, 2022
1e54b09
Set X-Frame-Options based on component preview config
aduth Oct 12, 2022
612539f
Manage component CSP with Rack middleware
aduth Oct 20, 2022
425d990
Fix download button space encoding
aduth Oct 20, 2022
fdf8bf0
Add preview for DownloadButton component
aduth Oct 20, 2022
cf2fff9
Temporary: Remove mounting
aduth Oct 20, 2022
fce4dba
Revert "Temporary: Remove mounting"
aduth Oct 20, 2022
f3a950c
Upgrade Lookbook to latest
aduth Oct 20, 2022
dc71883
Try loading on demand
aduth Oct 21, 2022
1807e82
Require at config
aduth Oct 21, 2022
460292f
Fix standalone MemorableDateComponent missing dependencies
aduth Oct 21, 2022
0bfb987
Add MemorableDateComponent component preview
aduth Oct 21, 2022
251c609
Add PasswordToggleComponent component preview
aduth Oct 21, 2022
1a33473
Add PhoneInputComponent component preview
aduth Oct 21, 2022
aecd839
Add SpinnerButtonComponent component preview
aduth Oct 21, 2022
5c805d5
Add StatusPageComponent component preview
aduth Oct 21, 2022
5196680
Add StepIndicatorComponent component preview
aduth Oct 21, 2022
570ca4d
Add TimeComponent component preview
aduth Oct 21, 2022
f3e40a0
Add TroubleshootingOptionsComponent component preview
aduth Oct 21, 2022
76a07d3
Add ValidatedFieldComponent component preview
aduth Oct 21, 2022
ccac220
Swap "Kitchen Sink" and "Playground" with "Preview" and "Workbench"
aduth Oct 21, 2022
c8c3194
Move Lookbook gem back into alphabetized list
aduth Oct 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ gem 'jsbundling-rails', '~> 1.0.0'
gem 'jwe'
gem 'jwt'
gem 'lograge', '>= 0.11.2'
gem 'lookbook', '~> 1.2.1', require: false
gem 'lru_redux'
gem 'maxminddb'
gem 'multiset'
Expand Down
17 changes: 17 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ GEM
thor
highline (2.0.3)
html_tokenizer (0.0.7)
htmlbeautifier (1.4.2)
htmlentities (4.3.4)
http_accept_language (2.1.1)
i18n (1.12.0)
Expand Down Expand Up @@ -384,6 +385,19 @@ GEM
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lookbook (1.2.1)
actioncable
activemodel
css_parser
htmlbeautifier (~> 1.3)
htmlentities (~> 4.3.4)
listen (~> 3.0)
railties (>= 5.0)
redcarpet (~> 3.5)
rouge (>= 3.26, < 5.0)
view_component (~> 2.0)
yard (~> 0.9.25)
zeitwerk (~> 2.5)
lru_redux (1.1.0)
lumberjack (1.2.8)
macaddr (1.7.2)
Expand Down Expand Up @@ -531,6 +545,7 @@ GEM
rb-inotify (0.10.1)
ffi (~> 1.0)
redacted_struct (1.1.0)
redcarpet (3.5.1)
redis (4.7.1)
redis-namespace (1.8.1)
redis (>= 3.0.4)
Expand All @@ -548,6 +563,7 @@ GEM
retries (0.0.5)
rexml (3.2.5)
rotp (6.2.0)
rouge (4.0.0)
rqrcode (2.1.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
Expand Down Expand Up @@ -767,6 +783,7 @@ DEPENDENCIES
jwt
knapsack
lograge (>= 0.11.2)
lookbook (~> 1.2.1)
lru_redux
maxminddb
multiset
Expand Down
2 changes: 1 addition & 1 deletion app/components/block_link_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
<path
d="M5.11 4.66L1 8.82a.36.36 0 01-.21.09.31.31 0 01-.2-.09l-.5-.45a.29.29 0 01-.09-.2A.36.36 0 01.09 8L3.6 4.45.09 1A.36.36 0 010 .74a.31.31 0 01.09-.2L.54.09A.31.31 0 01.74 0 .36.36 0 011 .09l4.11 4.16a.31.31 0 01.09.2.31.31 0 01-.09.21z"
fill="currentColor"
/>
></path>
</svg>
<% end %>
2 changes: 1 addition & 1 deletion app/components/download_button_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def initialize(file_data:, file_name:, **tag_options)
icon: :file_download,
action: ->(**tag_options, &block) do
link_to(
"data:text/plain;charset=utf-8,#{CGI.escape(file_data)}",
"data:text/plain;charset=utf-8,#{ERB::Util.url_encode(file_data)}",
download: file_name,
**tag_options,
&block
Expand Down
4 changes: 4 additions & 0 deletions app/components/memorable_date_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def initialize(
@range_errors = range_errors
end

def self.scripts
super + ValidatedFieldComponent.scripts
end

# Get error messages to be provided to the component.
# Includes both a hash lookup for general error messages
# and an array lookup for custom range error messages.
Expand Down
8 changes: 7 additions & 1 deletion app/components/validated_field_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ def type_mismatch_error_message
end

def inferred_input_type
form.send(:default_input_type, name, form.send(:find_attribute_column, name), tag_options)
if form.respond_to?(:default_input_type)
form.send(:default_input_type, name, form.send(:find_attribute_column, name), tag_options)
elsif tag_options.key?(:as)
tag_options[:as]
else
:text
end
end
end
9 changes: 9 additions & 0 deletions app/controllers/component_preview_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class ComponentPreviewController < ViewComponentsController
include ActionView::Helpers::AssetTagHelper
helper Lookbook::PreviewHelper
include Lookbook::PreviewController
include ScriptHelper

helper_method :enqueue_component_scripts
alias_method :enqueue_component_scripts, :render_javascript_pack_once_tags
end
21 changes: 21 additions & 0 deletions app/views/layouts/component_preview.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<title>Component Preview</title>
<%= stylesheet_link_tag 'application', media: 'all' %>
</head>
<body class="padding-2">
<% if params[:lookbook][:display][:form] == true %>
<form>
<%= yield %>
<%= render SubmitButtonComponent.new(class: 'margin-top-5') do %>
<%= t('forms.buttons.submit.default') %>
<% end %>
</form>
<% else %>
<%= yield %>
<% end %>
<%= javascript_packs_tag_once('application', prepend: true) %>
<%= render_javascript_pack_once_tags %>
</body>
</html>
15 changes: 15 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,20 @@ class Application < Rails::Application
# explicitly remove it when we want to disable it
config.middleware.delete Rack::Attack
end

config.view_component.show_previews = IdentityConfig.store.component_previews_enabled
if IdentityConfig.store.component_previews_enabled
require 'lookbook'

config.view_component.preview_controller = 'ComponentPreviewController'
config.view_component.preview_paths = [Rails.root.join('spec', 'components', 'previews')]
config.view_component.default_preview_layout = 'component_preview'
config.lookbook.auto_refresh = false
config.lookbook.project_name = "#{APP_NAME} Component Previews"
config.lookbook.ui_theme = 'blue'

require 'component_preview_csp'
config.middleware.insert_after ActionDispatch::Static, ComponentPreviewCsp
end
end
end
2 changes: 2 additions & 0 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ aws_kms_multi_region_enabled: false
backup_code_cost: '2000$8$1$'
broken_personal_key_window_start: '2021-07-29T00:00:00Z'
broken_personal_key_window_finish: '2021-09-22T00:00:00Z'
component_previews_enabled: false
country_phone_number_overrides: '{}'
doc_auth_error_dpi_threshold: 290
doc_auth_error_sharpness_threshold: 40
Expand Down Expand Up @@ -313,6 +314,7 @@ development:
attribute_encryption_key_queue: '[{ "key": "11111111111111111111111111111111" }, { "key": "22222222222222222222222222222222" }]'
aws_logo_bucket: ''
aws_kms_regions: '["us-west-2", "us-east-1"]'
component_previews_enabled: true
dashboard_api_token: test_token
dashboard_url: http://localhost:3001/api/service_providers
database_host: ''
Expand Down
5 changes: 4 additions & 1 deletion config/initializers/secure_headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
hsts: { preload: true, expires: 1.year, subdomains: true },
}

previews_enabled = IdentityConfig.store.rails_mailer_previews_enabled ||
IdentityConfig.store.component_previews_enabled

config.action_dispatch.default_headers.merge!(
'X-Frame-Options' => IdentityConfig.store.rails_mailer_previews_enabled ? 'SAMEORIGIN' : 'DENY',
'X-Frame-Options' => previews_enabled ? 'SAMEORIGIN' : 'DENY',
'X-XSS-Protection' => '1; mode=block',
'X-Download-Options' => 'noopen',
)
Expand Down
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@
end
end

if IdentityConfig.store.component_previews_enabled
require 'lookbook'
mount Lookbook::Engine, at: '/components'
end

if IdentityConfig.store.lexisnexis_threatmetrix_mock_enabled
get '/test/device_profiling' => 'test/device_profiling#index',
as: :test_device_profiling_iframe
Expand Down
26 changes: 26 additions & 0 deletions lib/component_preview_csp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class ComponentPreviewCsp
COMPONENT_REQUEST_PATH = /^\/components(\/|$)/

def initialize(app)
@app = app
end

def call(env)
status, headers, body = @app.call(env)
request = Rack::Request.new(env)

if headers['Content-Security-Policy'].present? && request.path.match?(COMPONENT_REQUEST_PATH)
headers['Content-Security-Policy'] = headers['Content-Security-Policy'].
split(';').
map(&:strip).
map do |directive|
directive.
sub(/^script-src .+/, "script-src * 'unsafe-eval' 'unsafe-inline'").
sub(/^style-src .+/, "style-src * 'unsafe-inline'")
end.
join(';')
end

[status, headers, body]
end
end
1 change: 1 addition & 0 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def self.build_store(config_map)
config.add(:backup_code_cost, type: :string)
config.add(:broken_personal_key_window_start, type: :timestamp)
config.add(:broken_personal_key_window_finish, type: :timestamp)
config.add(:component_previews_enabled, type: :boolean)
config.add(:country_phone_number_overrides, type: :json)
config.add(:dashboard_api_token, type: :string)
config.add(:dashboard_url, type: :string)
Expand Down
3 changes: 2 additions & 1 deletion spec/components/download_button_component_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
subject(:rendered) { render_inline instance }

it 'renders link with data and file name' do
expect(rendered).to have_css('lg-download-button')
expect(rendered).to have_css(
"lg-download-button a[href*='#{CGI.escape(file_data)}'][download='#{file_name}']",
"a[href='data:text/plain;charset=utf-8,Downloaded%20Text'][download='#{file_name}']",
text: t('components.download_button.label'),
)
end
Expand Down
27 changes: 27 additions & 0 deletions spec/components/previews/accordion_component_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class AccordionComponentPreview < BaseComponentPreview
# @!group Preview
def default
render(AccordionComponent.new) do |c|
c.header { 'Header' }
'Content'
end
end

def unbordered
render(AccordionComponent.new(bordered: false)) do |c|
c.header { 'Header' }
'Content'
end
end
# @!endgroup

# @param header text
# @param content text
# @param bordered toggle
def workbench(header: 'Header', content: 'Content', bordered: true)
render(AccordionComponent.new(bordered: bordered)) do |c|
c.header { header }
content
end
end
end
41 changes: 41 additions & 0 deletions spec/components/previews/alert_component_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class AlertComponentPreview < BaseComponentPreview
# @!group Preview
def default
render(AlertComponent.new(message: 'A default message'))
end

def info
render(AlertComponent.new(message: 'An info message', type: :info))
end

def success
render(AlertComponent.new(message: 'A success message', type: :success))
end

def warning
render(AlertComponent.new(message: 'A warning message', type: :warning))
end

def error
render(AlertComponent.new(message: 'An error message', type: :error))
end

def emergency
render(AlertComponent.new(message: 'An emergency message', type: :emergency))
end

def other
render(AlertComponent.new(message: 'An other message', type: :other))
end

def with_custom_text_tag
render(AlertComponent.new(type: :success, message: 'A custom message', text_tag: 'div'))
end
# @!endgroup

# @param message text
# @param type select [info, success, warning, error, emergency, other]
def workbench(message: 'An important message', type: :info)
render(AlertComponent.new(message: message, type: type))
end
end
13 changes: 13 additions & 0 deletions spec/components/previews/barcode_component_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class BarcodeComponentPreview < BaseComponentPreview
# @!group Preview
def default
render(BarcodeComponent.new(barcode_data: '1234567812345678', label: 'Barcode'))
end
# @!endgroup

# @param barcode_data text
# @param label text
def workbench(barcode_data: '1234567812345678', label: 'Barcode')
render(BarcodeComponent.new(barcode_data: barcode_data, label: label))
end
end
26 changes: 26 additions & 0 deletions spec/components/previews/base_component_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class BaseComponentPreview < ViewComponent::Preview
private

def form_builder
@form_builder ||= SimpleForm::FormBuilder.new(
'',
form_instance,
ActionView::Base.new(
ActionView::LookupContext.new(ActionController::Base.view_paths),
{},
nil,
),
{},
)
end

def form_instance
nil
end

# rubocop:disable Layout/LineLength
def example_long_content
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc et tincidunt libero, quis eleifend dui. Quisque dui velit, euismod ac arcu in, vehicula suscipit dui. Vivamus sed justo justo. Nunc a feugiat libero. Nulla dapibus blandit nisl, ac ultrices sapien dapibus ut. Vivamus convallis elementum mi pulvinar elementum. Quisque at aliquet nibh. Donec sed magna ut ipsum auctor dapibus. Proin leo metus, placerat eu finibus sed, consequat eu urna. Nunc tristique purus sollicitudin, luctus nisi eu, commodo tortor. Praesent mattis dictum diam ac sodales.'
end
# rubocop:enable Layout/LineLength
end
18 changes: 18 additions & 0 deletions spec/components/previews/block_link_component_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class BlockLinkComponentPreview < BaseComponentPreview
# @!group Preview
def default
render(BlockLinkComponent.new(url: '', new_tab: false).with_content('Link text'))
end

def new_tab
render(BlockLinkComponent.new(url: '', new_tab: true).with_content('Link text'))
end
# @!endgroup

# @param content text
# @param url text
# @param new_tab toggle
def workbench(content: 'Link text', url: '', new_tab: false)
render(BlockLinkComponent.new(url: url, new_tab: new_tab).with_content(content))
end
end
Loading