Split indexed_by into two filters: indexed by and sort by

https://3.basecamp.com/2914079/buckets/37331921/todos/8877489555#__recording_8987808963
This commit is contained in:
Jorge Manrubia
2025-08-21 19:12:54 +02:00
parent 311fc20837
commit c8569ce8b9
15 changed files with 100 additions and 31 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ module FilterScoped
end
private
DEFAULT_PARAMS = { indexed_by: "latest" }
DEFAULT_PARAMS = { indexed_by: "all", sorted_by: "latest" }
def set_filter
if params[:filter_id].present?
+11 -4
View File
@@ -20,13 +20,20 @@ class Card < ApplicationRecord
scope :indexed_by, ->(index) do
case index
when "stalled" then stalled
when "closing_soon" then closing_soon
when "falling_back_soon" then falling_back_soon
when "closed" then closed.recently_closed_first
else all
end
end
scope :sorted_by, ->(sort) do
case sort
when "newest" then reverse_chronologically
when "oldest" then chronologically
when "latest" then latest
when "stalled" then stalled.chronologically
when "closing_soon" then closing_soon.chronologically
when "falling_back_soon" then falling_back_soon.chronologically
when "closed" then closed.recently_closed_first
else latest
end
end
+1 -1
View File
@@ -18,7 +18,7 @@ class Filter < ApplicationRecord
def cards
@cards ||= begin
result = creator.accessible_cards.indexed_by(indexed_by)
result = creator.accessible_cards.indexed_by(indexed_by).sorted_by(sorted_by)
result = result.where(id: card_ids) if card_ids.present?
result = result.open unless include_closed_cards?
result = result.by_engagement_status(engagement_status) if engagement_status.present?
+16 -3
View File
@@ -1,13 +1,14 @@
module Filter::Fields
extend ActiveSupport::Concern
INDEXES = %w[ newest oldest latest stalled closing_soon falling_back_soon ]
INDEXES = %w[ all stalled closing_soon falling_back_soon ]
SORTED_BY = %w[ newest oldest latest ]
delegate :default_value?, to: :class
class_methods do
def default_values
{ indexed_by: "latest" }
{ indexed_by: "all", sorted_by: "latest" }
end
def default_value?(key, value)
@@ -16,7 +17,7 @@ module Filter::Fields
end
included do
store_accessor :fields, :assignment_status, :indexed_by, :terms,
store_accessor :fields, :assignment_status, :indexed_by, :sorted_by, :terms,
:engagement_status, :card_ids, :creation, :closure
def assignment_status
@@ -27,6 +28,10 @@ module Filter::Fields
(super || default_indexed_by).inquiry
end
def sorted_by
(super || default_sorted_by).inquiry
end
def engagement_status
super&.inquiry
end
@@ -59,4 +64,12 @@ module Filter::Fields
def default_indexed_by?
default_value?(:indexed_by, indexed_by)
end
def default_sorted_by
self.class.default_values[:sorted_by]
end
def default_sorted_by?
default_value?(:sorted_by, sorted_by)
end
end
+2
View File
@@ -4,6 +4,7 @@ module Filter::Params
PERMITTED_PARAMS = [
:assignment_status,
:indexed_by,
:sorted_by,
:engagement_status,
:creation,
:closure,
@@ -40,6 +41,7 @@ module Filter::Params
def as_params
{}.tap do |params|
params[:indexed_by] = indexed_by
params[:sorted_by] = sorted_by
params[:engagement_status] = engagement_status
params[:creation] = creation
params[:closure] = closure
+8 -2
View File
@@ -1,15 +1,21 @@
module Filter::Summarized
def summary
[ index_summary, tag_summary, assignee_summary, creator_summary, stage_summary, terms_summary ].compact.to_sentence
[ index_summary, sort_summary, tag_summary, assignee_summary, creator_summary, stage_summary, terms_summary ].compact.to_sentence
end
private
def index_summary
unless indexed_by.latest?
unless indexed_by.all?
indexed_by.humanize
end
end
def sort_summary
unless sorted_by.latest?
sorted_by.humanize
end
end
def tag_summary
if tags.any?
"#{tags.map(&:hashtag).to_choice_sentence}"
+6 -6
View File
@@ -44,11 +44,15 @@ class User::Filtering
def any?
filter.tags.any? || filter.assignees.any? || filter.creators.any? || filter.closers.any? ||
filter.stages.any? || filter.terms.any? || filter.card_ids&.any? ||
filter.assignment_status.unassigned? || !filter.indexed_by.latest?
filter.assignment_status.unassigned? || !filter.indexed_by.all? || !filter.sorted_by.latest?
end
def show_indexed_by?
expanded? || !filter.indexed_by.latest?
expanded? || !filter.indexed_by.all?
end
def show_sorted_by?
expanded? || !filter.sorted_by.latest?
end
def show_tags?
@@ -67,10 +71,6 @@ class User::Filtering
expanded? || filter.closers.any?
end
def show_time_window?(name)
expanded? || filter.public_send("#{name}_window").present?
end
def enable_collection_filtering(&block)
@collection_filtering_route_resolver = block
end
+1
View File
@@ -2,6 +2,7 @@
<div class="flex-inline center align-center gap-half">
<%= render "filters/settings/toggle", user_filtering: user_filtering %>
<%= render "filters/settings/sorted_by", user_filtering: user_filtering %>
<%= render "filters/settings/indexed_by", user_filtering: user_filtering %>
<%= render "filters/settings/tags", user_filtering: user_filtering %>
<%= render "filters/settings/assignees", user_filtering: user_filtering %>
@@ -14,7 +14,7 @@
<% end %>
<% end %>
<%= link_to cards_path(filter.as_params.except(:assignee_ids, :assignment_status, :card_ids, :creator_ids, :closer_ids, :stage_ids, :tag_ids, :terms, :indexed_by, :creation, :closure)), class: "btn btn--remove txt-x-small" do %>
<%= link_to cards_path(filter.as_params.except(:assignee_ids, :assignment_status, :card_ids, :creator_ids, :closer_ids, :stage_ids, :tag_ids, :terms, :indexed_by, :sorted_by, :creation, :closure)), class: "btn btn--remove txt-x-small" do %>
<%= icon_tag "close" %>
<span class="for-screen-reader">Clear all</span>
<% end %>
@@ -1,15 +1,15 @@
<% filter = user_filtering.filter %>
<% if user_filtering.show_indexed_by? %>
<div class="quick-filter position-relative <%= "default-value" if filter.indexed_by.latest? %>" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<div class="quick-filter position-relative <%= "default-value" if filter.indexed_by.all? %>" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<button class="btn input input--select flex-inline txt-x-small" data-action="click->dialog#open:stop">
<span class="overflow-ellipsis">
<%= filter.indexed_by.humanize %>
<%= filter.indexed_by == "all" ? "Status" : filter.indexed_by.humanize %>
</span>
</button>
<%= filter_dialog "Sort by…" do %>
<strong class="popup__title txt-nowrap">Sort by…</strong>
<%= filter_dialog "Filter by…" do %>
<strong class="popup__title txt-nowrap">Filter by…</strong>
<ul class="popup__list" data-filter-target="list">
<% Filter::INDEXES.each do |index| %>
@@ -0,0 +1,40 @@
<% filter = user_filtering.filter %>
<% if user_filtering.show_sorted_by? %>
<div class="quick-filter position-relative <%= "default-value" if filter.sorted_by.latest? %>" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<button class="btn input input--select flex-inline txt-x-small" data-action="click->dialog#open:stop">
<span class="overflow-ellipsis">
<%= filter.sorted_by.humanize %>
</span>
</button>
<%= filter_dialog "Sort by…" do %>
<strong class="popup__title txt-nowrap">Sort by…</strong>
<ul class="popup__list" data-filter-target="list">
<% Filter::SORTED_BY.each do |sort| %>
<li class="popup__group" data-navigable-list-target="item">
<%= form_with url: cards_path, method: :get, class: "full-width", data: { controller: "form" } do |form| %>
<% filter.as_params.except(:sorted_by).each do |key, value| %>
<%= filter_hidden_field_tag key, value %>
<% end %>
<%= hidden_field_tag :sorted_by, sort %>
<% if user_filtering.expanded? %>
<%= hidden_field_tag :expand_all, user_filtering.expanded? %>
<% end %>
<%= form.button type: "submit", class: "popup__item btn" do %>
<span class="overflow-ellipsis flex-item-grow"><%= sort.humanize %></span>
<% if filter.sorted_by == sort %>
<%= icon_tag "check", class: "checked flex-item-justify-end" %>
<% end %>
<% end %>
<% end %>
</li>
<% end %>
</ul>
<% end %>
</div>
<% end %>
@@ -1,5 +1,5 @@
<% filter = user_filtering.filter %>
<% if user_filtering.show_time_window?(name) %>
<% if filter.public_send("#{name}_window").present? %>
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<button class="btn input input--select flex-inline txt-x-small" data-action="click->dialog#open:stop">
<span class="overflow-ellipsis">
+2 -2
View File
@@ -35,8 +35,8 @@
</div>
<div class="flex-inline center justify-center flex-wrap gap">
<%= link_to "Which cards are assigned to #{ Current.user == @user ? "you" : @user.first_name }?", cards_path(assignee_ids: [@user.id], indexed_by: "newest"), class: "btn", data: { turbo_frame: "_top" } %>
<%= link_to "Which cards were added by #{ Current.user == @user ? "you" : @user.first_name }?", cards_path(creator_ids: [@user.id], indexed_by: "newest"), class: "btn", data: { turbo_frame: "_top" } %>
<%= link_to "Which cards are assigned to #{ Current.user == @user ? "you" : @user.first_name }?", cards_path(assignee_ids: [@user.id], sorted_by: "newest"), class: "btn", data: { turbo_frame: "_top" } %>
<%= link_to "Which cards were added by #{ Current.user == @user ? "you" : @user.first_name }?", cards_path(creator_ids: [@user.id], sorted_by: "newest"), class: "btn", data: { turbo_frame: "_top" } %>
</div>
</div>
</div>
+2 -2
View File
@@ -2,5 +2,5 @@ jz_assignments:
creator: david
tags: mobile
assignees: jz
fields: <%= { indexed_by: :newest }.to_json %>
params_digest: <%= Filter.digest_params({ indexed_by: :newest, tag_ids: [ ActiveRecord::FixtureSet.identify(:mobile) ], assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }) %>
fields: <%= { indexed_by: :all, sorted_by: :newest }.to_json %>
params_digest: <%= Filter.digest_params({ indexed_by: :all, sorted_by: :newest, tag_ids: [ ActiveRecord::FixtureSet.identify(:mobile) ], assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }) %>
+4 -4
View File
@@ -53,7 +53,7 @@ class FilterTest < ActiveSupport::TestCase
test "remembering equivalent filters" do
assert_difference "Filter.count", +1 do
filter = users(:david).filters.remember(indexed_by: "latest", assignment_status: "unassigned", tag_ids: [ tags(:mobile).id ])
filter = users(:david).filters.remember(sorted_by: "latest", assignment_status: "unassigned", tag_ids: [ tags(:mobile).id ])
assert_changes "filter.reload.updated_at" do
assert_equal filter, users(:david).filters.remember(tag_ids: [ tags(:mobile).id ], assignment_status: "unassigned")
@@ -69,7 +69,7 @@ class FilterTest < ActiveSupport::TestCase
end
test "turning into params" do
filter = users(:david).filters.new indexed_by: "latest", tag_ids: "", assignee_ids: [ users(:jz).id ], collection_ids: [ collections(:writebook).id ]
filter = users(:david).filters.new sorted_by: "latest", tag_ids: "", assignee_ids: [ users(:jz).id ], collection_ids: [ collections(:writebook).id ]
expected = { assignee_ids: [ users(:jz).id ], collection_ids: [ collections(:writebook).id ] }
assert_equal expected, filter.as_params
end
@@ -122,12 +122,12 @@ class FilterTest < ActiveSupport::TestCase
filters(:jz_assignments).update!(stages: [], assignees: [], tags: [], collections: [ collections(:writebook) ])
assert_equal "Newest", filters(:jz_assignments).summary
filters(:jz_assignments).update!(indexed_by: "stalled")
filters(:jz_assignments).update!(indexed_by: "stalled", sorted_by: "latest")
assert_equal "Stalled", filters(:jz_assignments).summary
end
test "get a clone with some changed params" do
seed_filter = users(:david).filters.new indexed_by: "active", terms: [ "haggis" ]
seed_filter = users(:david).filters.new indexed_by: "all", terms: [ "haggis" ]
filter = seed_filter.with(indexed_by: "closed")
assert filter.indexed_by.closed?