-
Notifications
You must be signed in to change notification settings - Fork 135
Redirects UI #7433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Redirects UI #7433
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ff9ad7a
Make BasedNearFieldBehavior compose via super
laritakr adc28d1
Persist redirects as plain hashes
laritakr 9cacd92
Add the Aliases tab and validator wiring
laritakr b26e83e
Document the Field Behavior pattern
laritakr 6c73a4e
Merge branch 'main' into redirects-ui
orangewolf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module Hyrax | ||
| # Form-side handling for the `redirects` nested-attribute field. | ||
| # | ||
| # Submitted form payloads arrive under `redirects_attributes` and are | ||
| # turned into plain hashes (the persisted shape — see | ||
| # `config/metadata/redirects.yaml`, `type: hash`) by the populator. | ||
| # On render, the prepopulator wraps each persisted hash in a | ||
| # `Hyrax::Redirect` value object so the view can call `.path` / | ||
| # `.canonical` / `.sequence`. | ||
| # | ||
| # The `deserialize!` override removes the renamed `redirects` key | ||
| # before Reform's `from_hash` runs, so the form's `redirects` | ||
| # property is written exclusively by the populator. See | ||
| # `Hyrax::BasedNearFieldBehavior` for the parallel pattern. | ||
| # | ||
| # ## Feature gating | ||
| # | ||
| # `self.included` runs at class load time and uses the structural | ||
| # gate `Hyrax.config.redirects_enabled?`. The runtime Flipflop | ||
| # check isn't meaningful here because the form class is being | ||
| # defined, not handling a request. | ||
| # | ||
| # All runtime-side methods (`deserialize!`, populator, prepopulator) | ||
| # delegate to `Hyrax.config.redirects_active?`, the two-gate | ||
| # combinator (`redirects_enabled? && Flipflop.redirects?`). Calling | ||
| # `Flipflop.redirects?` directly is unsafe when the config is off | ||
| # because the feature isn't registered in that case. | ||
| module RedirectsFieldBehavior | ||
| def self.included(descendant) | ||
| return unless Hyrax.config.redirects_enabled? | ||
| descendant.property :redirects_attributes, | ||
| virtual: true, | ||
| populator: :redirects_attributes_populator, | ||
| prepopulator: :redirects_attributes_prepopulator | ||
| end | ||
|
|
||
| # Reform's FormBuilderMethods rewrites `redirects_attributes` → | ||
| # `redirects` before `from_hash` runs. Strip the renamed key so | ||
| # the populator on `redirects_attributes` is the single entry | ||
| # point for form-driven writes. | ||
| def deserialize!(params) | ||
| result = super | ||
| if Hyrax.config.redirects_active? && result.respond_to?(:delete) | ||
| result.delete('redirects') | ||
| result.delete(:redirects) | ||
| end | ||
| result | ||
| end | ||
|
|
||
| private | ||
|
|
||
| # Builds plain hashes (the persisted shape) from the submitted | ||
| # `redirects_attributes` payload. Drops rows marked for destruction | ||
| # or with a blank path. Normalizes paths to the canonical form | ||
| # stored in the uniqueness ledger. | ||
| def redirects_attributes_populator(fragment:, **_options) | ||
| return unless respond_to?(:redirects) | ||
| return unless Hyrax.config.redirects_active? | ||
| entries = Array(fragment&.values) | ||
| .reject { |row| row['_destroy'].to_s == 'true' || row['path'].to_s.strip.empty? } | ||
| .each_with_index.map do |row, i| | ||
| { 'path' => Hyrax::RedirectPathNormalizer.call(row['path']), | ||
| 'canonical' => row['canonical'].to_s == 'true', | ||
| 'sequence' => row['sequence'].presence&.to_i || i } | ||
| end | ||
| self.redirects = entries | ||
| end | ||
|
|
||
| # Wraps each persisted hash in a `Hyrax::Redirect` value object for | ||
| # the form view. Mirrors how `BasedNearFieldBehavior` hydrates URI | ||
| # strings into `ControlledVocabularies::Location` instances. | ||
| def redirects_attributes_prepopulator | ||
| return unless respond_to?(:redirects) | ||
| return unless Hyrax.config.redirects_active? | ||
| self.redirects = Array(redirects).map { |entry| Hyrax::Redirect.wrap(entry) } | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module Hyrax | ||
| ## | ||
| # Decides whether the Aliases tab should appear on a work or | ||
| # collection edit form. Included into `WorkFormHelper` and | ||
| # `CollectionsHelper` so both forms share one rule. | ||
| # | ||
| # The tab appears only when the redirects feature is fully active | ||
| # (`Hyrax.config.redirects_active?`), the form is a ResourceForm | ||
| # (the only form pipeline with the redirects populator, validator, | ||
| # and prepopulator wired in), AND the form's underlying resource | ||
| # carries the `redirects` attribute. The structural check guards | ||
| # against adopter work or collection classes that don't include | ||
| # the redirects schema; without it the tab would render and crash | ||
| # on `f.object.redirects`. | ||
| module RedirectsTabHelper | ||
| def redirects_tab?(form) | ||
| return false unless Hyrax.config.redirects_active? | ||
| return false unless redirects_supported_form?(form) | ||
| target = form.respond_to?(:model) ? form.model : form | ||
| target.respond_to?(:redirects) | ||
| end | ||
|
|
||
| def redirects_supported_form?(form) | ||
| form.is_a?(Hyrax::Forms::ResourceForm) | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| <%# Aliases tab — see documentation/redirects.md %> | ||
| <% return unless redirects_tab?(f.object) %> | ||
| <h2 class="h3 mt-4"><%= t('hyrax.works.form.redirects.heading') %></h2> | ||
|
|
||
| <p><%= sanitize t('hyrax.works.form.redirects.help_html'), tags: %w[code] %></p> | ||
|
|
||
| <table class="table table-striped"> | ||
| <caption class="sr-only"><%= t('hyrax.works.form.redirects.caption') %></caption> | ||
| <thead> | ||
| <tr> | ||
| <th scope="col"><%= t('hyrax.works.form.redirects.header.path') %></th> | ||
| <th scope="col" class="sr-only"><%= t('hyrax.works.form.redirects.header.actions') %></th> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <%# Wrap each row through Hyrax::Redirect.wrap so the view can rely on | ||
| the presenter API (.path, .canonical, .sequence). On initial render | ||
| the prepopulator already wrapped them; on a re-render after a | ||
| failed save, f.object.redirects holds the populator's plain hashes, | ||
| and we wrap them here. %> | ||
| <% rows = Array(f.object.redirects).map { |e| Hyrax::Redirect.wrap(e) } + [Hyrax::Redirect.new(path: nil)] %> | ||
| <% rows.each_with_index do |entry, index| %> | ||
| <% input_id = "#{f.object_name}_redirects_attributes_#{index}_path" %> | ||
| <% label_text = entry.path.present? ? t('hyrax.works.form.redirects.path_label') : t('hyrax.works.form.redirects.new_path_label') %> | ||
| <tr> | ||
| <td> | ||
| <%= label_tag input_id, label_text, class: 'sr-only' %> | ||
| <div class="input-group"> | ||
| <div class="input-group-prepend"> | ||
| <span class="input-group-text"><%= request.base_url %></span> | ||
| </div> | ||
| <%= text_field_tag "#{f.object_name}[redirects_attributes][#{index}][path]", | ||
| entry.path, | ||
| id: input_id, | ||
| class: 'form-control', | ||
| placeholder: t('hyrax.works.form.redirects.placeholder.path') %> | ||
|
laritakr marked this conversation as resolved.
|
||
| </div> | ||
| </td> | ||
| <td class="text-right" style="width: 1%;"> | ||
| <% if entry.path.present? %> | ||
| <button type="button" | ||
| class="btn close" | ||
| aria-label="<%= t('hyrax.works.form.redirects.remove_with_path_label', path: entry.path) %>" | ||
| onclick="this.closest('tr').remove()"> | ||
| <span aria-hidden="true">×</span> | ||
| </button> | ||
| <% end %> | ||
| </td> | ||
| </tr> | ||
| <% end %> | ||
| </tbody> | ||
| </table> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.