diff --git a/app/assets/images/close.svg b/app/assets/images/close.svg
new file mode 100644
index 000000000..878343d21
--- /dev/null
+++ b/app/assets/images/close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/images/filter.svg b/app/assets/images/filter.svg
new file mode 100644
index 000000000..b8865e243
--- /dev/null
+++ b/app/assets/images/filter.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/stylesheets/buttons.css b/app/assets/stylesheets/buttons.css
index ecba266af..df48c288b 100644
--- a/app/assets/stylesheets/buttons.css
+++ b/app/assets/stylesheets/buttons.css
@@ -211,6 +211,10 @@
--outline-color: var(--color-positive);
}
+.btn--remove {
+ --btn-icon-size: 0.7em;
+}
+
.btn--reversed {
--btn-background: var(--color-ink);
--btn-color: var(--color-bg);
diff --git a/app/assets/stylesheets/filters.css b/app/assets/stylesheets/filters.css
index 7ca1949ee..95d988c49 100644
--- a/app/assets/stylesheets/filters.css
+++ b/app/assets/stylesheets/filters.css
@@ -1,50 +1,31 @@
-.filter {
- display: inline-flex;
- position: relative;
-
+.filters {
#header:has(&) {
position: relative;
z-index: 1;
}
}
-.filter--active {
- li {
- display: none;
-
- &.selected {
- display: flex;
- }
- }
-}
-
.filter__button {
- --hover-size: 0;
+ border-radius: 0.5em;
+ color: var(--color-ink);
+ display: block;
+ overflow: hidden;
+ padding: 0.3em 0.7em;
+ text-decoration: none;
+ text-overflow: ellipsis;
+ white-space: nowrap;
- color: var(--color-link);
- text-decoration: underline;
- text-decoration-skip-ink: auto;
-}
-
-.filter__menu {
- a {
- border-radius: 0.5em;
- color: var(--color-ink);
- display: block;
- overflow: hidden;
- padding: 0.3em 0.7em;
- text-decoration: none;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- @media (hover: hover) {
- &:hover {
- background-color: var(--color-selected);
- }
+ @media (hover: hover) {
+ &:hover {
+ background-color: var(--color-selected);
}
}
}
+.filter__label {
+ padding: 0.3em 0.7em;
+}
+
.filter__popup {
--panel-border-radius: 0.5em;
--panel-padding: 0.5em;
diff --git a/app/controllers/bubbles_controller.rb b/app/controllers/bubbles_controller.rb
index 7765be897..2c8cf0be5 100644
--- a/app/controllers/bubbles_controller.rb
+++ b/app/controllers/bubbles_controller.rb
@@ -8,7 +8,6 @@ class BubblesController < ApplicationController
def index
@bubbles = @filter.bubbles
- @bubbles = @bubbles.mentioning(params[:term]) if params[:term].present?
end
def create
@@ -30,7 +29,7 @@ class BubblesController < ApplicationController
private
def set_filter
- @filter = Current.user.filters.build params.permit(*Filter::KNOWN_PARAMS)
+ @filter = Current.user.filters.build params.permit(*Filter::PERMITTED_PARAMS)
end
def set_bubble
diff --git a/app/controllers/filter_chips_controller.rb b/app/controllers/filter_chips_controller.rb
new file mode 100644
index 000000000..d08c906f3
--- /dev/null
+++ b/app/controllers/filter_chips_controller.rb
@@ -0,0 +1,4 @@
+class FilterChipsController < ApplicationController
+ def create
+ end
+end
diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb
index 34a020c4f..1274f11d8 100644
--- a/app/controllers/filters_controller.rb
+++ b/app/controllers/filters_controller.rb
@@ -17,7 +17,7 @@ class FiltersController < ApplicationController
end
def filter_params
- params.permit(*Filter::KNOWN_PARAMS).compact_blank
+ params.permit(*Filter::PERMITTED_PARAMS).compact_blank
end
def redirect_after_destroy
diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb
index 05caac3d0..1b763e665 100644
--- a/app/helpers/filters_helper.rb
+++ b/app/helpers/filters_helper.rb
@@ -1,27 +1,22 @@
module FiltersHelper
- def buckets_filter_text(filter)
- if filter.buckets.present?
- filter.buckets.map(&:name).to_choice_sentence
- else
- "all projects"
+ def filter_chip_id(name, value)
+ "#{name}__filter--#{value}"
+ end
+
+ def filter_chip_tag(text, name:, value:, **options)
+ tag.button id: filter_chip_id(name, value), class: [ "btn txt-small btn--remove", options.delete(:class) ],
+ data: { action: "filter-form#removeFilter form#submit", filter_form_target: "button" } do
+ concat hidden_field_tag(name, value, id: nil)
+ concat tag.span(text)
+ concat image_tag("close.svg", aria: { hidden: true }, size: 24)
end
end
- def assignments_filter_text(filter)
- if filter.assignees.present?
- "assigned to #{filter.assignees.map(&:name).to_choice_sentence}"
- elsif filter.assignments.unassigned?
- "assigned to no one"
+ def button_to_chip(text, params: {}, data: {})
+ if params.present?
+ button_to text, filter_chips_path, method: :post, class: "btn btn--plain filter__button", params: params, data: data
else
- "assigned to anyone"
- end
- end
-
- def tags_filter_text(filter)
- if filter.tags.present?
- filter.tags.map(&:hashtag).to_choice_sentence
- else
- "any tag"
+ button_tag text, type: :button, class: "btn btn--plain filter__button", data: data
end
end
end
diff --git a/app/javascript/controllers/filter_form_controller.js b/app/javascript/controllers/filter_form_controller.js
new file mode 100644
index 000000000..65ac570a1
--- /dev/null
+++ b/app/javascript/controllers/filter_form_controller.js
@@ -0,0 +1,28 @@
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+ static targets = [ "button" ]
+
+ connect() {
+ this.buttonTargets.forEach(button => this.#showButton(button))
+ }
+
+ removeFilter(event) {
+ event.preventDefault()
+ this.#hideButton(event.target.closest("button"))
+ }
+
+ clearCategory({ params: { name } }) {
+ this.element.querySelectorAll(`input[name="${name}"]`).forEach(input => this.#hideButton(input.closest("button")))
+ }
+
+ #showButton(button) {
+ button.querySelector("input").disabled = false
+ button.hidden = false
+ }
+
+ #hideButton(button) {
+ button.querySelector("input").disabled = true
+ button.hidden = true
+ }
+}
diff --git a/app/models/bubble/assignable.rb b/app/models/bubble/assignable.rb
index ac121da87..0b6dc3f9a 100644
--- a/app/models/bubble/assignable.rb
+++ b/app/models/bubble/assignable.rb
@@ -6,7 +6,8 @@ module Bubble::Assignable
has_many :assignees, through: :assignments
scope :unassigned, -> { where.missing :assignments }
- scope :assigned_to, ->(users) { joins(:assignments).where(assignments: { assignee: users }) }
+ scope :assigned_to, ->(users) { joins(:assignments).where(assignments: { assignee: users }).distinct }
+ scope :assigned_by, ->(users) { joins(:assignments).where(assignments: { assigner: users }).distinct }
end
def assign(users, assigner: Current.user)
diff --git a/app/models/filter.rb b/app/models/filter.rb
index 58edae90f..cfaadb8ed 100644
--- a/app/models/filter.rb
+++ b/app/models/filter.rb
@@ -6,12 +6,18 @@ class Filter < ApplicationRecord
class << self
def persist!(attrs)
- filter = new(attrs)
- filter.save!
- filter
+ create!(attrs)
rescue ActiveRecord::RecordNotUnique
- find_by!(params: filter.params).tap(&:touch)
+ find_by!(params_digest: digest_params(attrs)).tap(&:touch)
end
+
+ def digest_params(params)
+ Digest::MD5.hexdigest params.sort.to_json
+ end
+ end
+
+ def empty?
+ as_params.blank?
end
def bubbles
@@ -20,8 +26,13 @@ class Filter < ApplicationRecord
result = result.active unless indexed_by.popped?
result = result.unassigned if assignments.unassigned?
result = result.assigned_to(assignees.ids) if assignees.present?
+ result = result.assigned_by(assigners.ids) if assigners.present?
result = result.in_bucket(buckets.ids) if buckets.present?
result = result.tagged_with(tags.ids) if tags.present?
+ result = terms.reduce(result) do |result, term|
+ result.mentioning(term)
+ end
+
result
end
end
diff --git a/app/models/filter/fields.rb b/app/models/filter/fields.rb
index 2db92f0e3..a1b385a06 100644
--- a/app/models/filter/fields.rb
+++ b/app/models/filter/fields.rb
@@ -1,28 +1,28 @@
module Filter::Fields
extend ActiveSupport::Concern
- INDEXES = %w[ most_active most_discussed most_boosted newest oldest popped ]
+ INDEXES = %w[ most_discussed most_boosted newest oldest popped ]
class_methods do
def default_fields
- { "indexed_by" => "most_active" }
+ { indexed_by: "most_active" }
end
end
- def assignments=(value)
- fields["assignments"] = value
- end
+ included do
+ store_accessor :fields, :indexed_by, :assignments, :terms
- def assignments
- fields["assignments"].to_s.inquiry
- end
+ def indexed_by
+ (super || default_fields[:indexed_by]).inquiry
+ end
- def indexed_by=(value)
- fields["indexed_by"] = value
- end
+ def assignments
+ super.to_s.inquiry
+ end
- def indexed_by
- (fields["indexed_by"] || default_fields["indexed_by"]).inquiry
+ def terms
+ Array(super)
+ end
end
private
diff --git a/app/models/filter/params.rb b/app/models/filter/params.rb
index 2726e153a..71be9eb85 100644
--- a/app/models/filter/params.rb
+++ b/app/models/filter/params.rb
@@ -1,37 +1,27 @@
module Filter::Params
extend ActiveSupport::Concern
- KNOWN_PARAMS = [ :indexed_by, :assignments, bucket_ids: [], assignee_ids: [], tag_ids: [] ]
+ PERMITTED_PARAMS = [ :indexed_by, :assignments, bucket_ids: [], assignee_ids: [], assigner_ids: [], tag_ids: [], terms: [] ]
included do
- after_initialize :derive_params
+ before_save { self.params_digest = self.class.digest_params(as_params) }
+ end
+
+ def as_params
+ @as_params ||= {
+ terms: terms,
+ tag_ids: tags.ids,
+ indexed_by: indexed_by,
+ bucket_ids: buckets.ids,
+ assignments: assignments,
+ assignee_ids: assignees.ids,
+ assigner_ids: assigners.ids
+ }.reject { |k, v| default_fields[k] == v }.compact_blank
end
def to_params
- ActionController::Parameters.new(params).permit(*KNOWN_PARAMS).tap do |params|
+ ActionController::Parameters.new(as_params).permit(*PERMITTED_PARAMS).tap do |params|
params[:filter_id] = id if persisted?
end
end
-
- private
- # `derive_params` stores a denormalized version of the filter in `params` to
- # 1) Enforce uniqueness via db constraints
- # 2) Look up identical filters by a single column
- # 3) Easily turn all filter params into a query string
- def derive_params
- derive_params_from_resource_ids
- derive_params_from_fields
- params.compact_blank!
- end
- alias_method :derived_params, :derive_params
-
- def derive_params_from_resource_ids
- params["tag_ids"] = tags.ids
- params["bucket_ids"] = buckets.ids
- params["assignee_ids"] = assignees.ids
- end
-
- def derive_params_from_fields
- self.params.merge! fields.reject { |k, v| default_fields[k] == v }
- end
end
diff --git a/app/models/filter/resources.rb b/app/models/filter/resources.rb
index 5215087f6..78c862c9d 100644
--- a/app/models/filter/resources.rb
+++ b/app/models/filter/resources.rb
@@ -5,11 +5,12 @@ module Filter::Resources
has_and_belongs_to_many :tags
has_and_belongs_to_many :buckets
has_and_belongs_to_many :assignees, class_name: "User", join_table: "assignees_filters", association_foreign_key: "assignee_id"
+ has_and_belongs_to_many :assigners, class_name: "User", join_table: "assigners_filters", association_foreign_key: "assigner_id"
end
def resource_removed(resource)
kind = resource.class.model_name.plural
send "#{kind}=", send(kind).without(resource)
- derived_params.blank? ? destroy! : save!
+ empty? ? destroy! : save!
end
end
diff --git a/app/models/filter/summarized.rb b/app/models/filter/summarized.rb
index 67f194e81..2d406dceb 100644
--- a/app/models/filter/summarized.rb
+++ b/app/models/filter/summarized.rb
@@ -1,6 +1,6 @@
module Filter::Summarized
def summary
- [ index_summary, tag_summary, assignee_summary ].compact.to_sentence + " #{bucket_summary}"
+ [ index_summary, tag_summary, assignee_summary, assigner_summary, terms_summary ].compact.to_sentence + " #{bucket_summary}"
end
def plain_summary
@@ -26,6 +26,12 @@ module Filter::Summarized
end
end
+ def assigner_summary
+ if assigners.any?
+ "assigned by #{assigners.pluck(:name).to_choice_sentence}"
+ end
+ end
+
def bucket_summary
if buckets.any?
"in #{buckets.pluck(:name).to_choice_sentence}"
@@ -33,4 +39,10 @@ module Filter::Summarized
"in all projects"
end
end
+
+ def terms_summary
+ if terms.any?
+ "matching #{terms.map { |term| %Q("#{term}") }.to_sentence}"
+ end
+ end
end
diff --git a/app/views/bubbles/_filters.html.erb b/app/views/bubbles/_filters.html.erb
index 2efc451c8..49057a0b3 100644
--- a/app/views/bubbles/_filters.html.erb
+++ b/app/views/bubbles/_filters.html.erb
@@ -1,16 +1,211 @@
-
+
- <%= render "bubbles/filters/index", filter: filter %>
- in
- <%= render "bubbles/filters/buckets", filter: filter %>
-
- <%= render "bubbles/filters/bookmark", filter: filter %>
+ <%= filter.buckets.first&.name || "All projects" %>
-
- <%= filter.tags.present? ? "Tagged" : "with" %>
- <%= render "bubbles/filters/tags", filter: filter %>
+
+
- and <%= render "bubbles/filters/assignments", filter: filter %>
-
+ <%= form_with url: bubbles_path, method: :get, class: "flex-inline center align-center gap-half", data: { controller: "form" } do %>
+ <%= filter_chip_tag filter.indexed_by.humanize, name: "indexed_by", value: filter.indexed_by %>
+
+ <% filter.tags.each do |tag| %>
+ <%= filter_chip_tag tag.hashtag, name: "tag_ids[]", value: tag.id %>
+ <% end %>
+
+ <% filter.assignees.each do |assignee| %>
+ <%= filter_chip_tag "for #{assignee.name}", name: "assignee_ids[]", value: assignee.id %>
+ <% end %>
+
+ <% if filter.assignments.present? %>
+ <%= filter_chip_tag filter.assignments.humanize, name: "assignments", value: filter.assignments %>
+ <% end %>
+
+ <% filter.assigners.each do |assigner| %>
+ <%= filter_chip_tag "by #{assigner.name}", name: "assigner_ids[]", value: assigner.id %>
+ <% end %>
+
+ <% filter.buckets.each do |bucket| %>
+ <%= filter_chip_tag "in #{bucket.name}", name: "bucket_ids[]", value: bucket.id %>
+ <% end %>
+
+ <% filter.terms.each do |term| %>
+ <%= filter_chip_tag %Q("#{term}"), name: "terms[]", value: term %>
+ <% end %>
+ <% end %>
+
+
+
diff --git a/app/views/bubbles/filters/_assignments.html.erb b/app/views/bubbles/filters/_assignments.html.erb
deleted file mode 100644
index fb893ebb4..000000000
--- a/app/views/bubbles/filters/_assignments.html.erb
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- <% if [ filter.assignees, filter.assignments ].any?(&:present?) %>
- <%= link_to bubbles_path(filter.to_params.without(:assignments, :assignee_ids)), class: "btn", style: "font-size: 0.4em;" do %>
- <%= image_tag "remove.svg", aria: { hidden: true }, size: 24 %>
- Clear
- <% end %>
- <% end %>
-
diff --git a/app/views/bubbles/filters/_bookmark.html.erb b/app/views/bubbles/filters/_bookmark.html.erb
deleted file mode 100644
index 5b7b962dc..000000000
--- a/app/views/bubbles/filters/_bookmark.html.erb
+++ /dev/null
@@ -1,34 +0,0 @@
-<% if params[:filter_id] %>
- <%= button_to filter_path(params[:filter_id]), method: :delete, class: "btn txt-small borderless" do %>
- <%= image_tag "bookmark.svg", aria: { hidden: true }, size: 24 %>
-
Delete this filter
- <% end %>
-<% else %>
- <%= form_with url: filters_path, method: :post do %>
- <%= hidden_field_tag :indexed_by, params[:indexed_by] if params[:indexed_by].present? %>
- <%= hidden_field_tag :assignments, params[:assignments] if params[:assignments].present? %>
-
- <% if values = params[:bucket_ids].presence %>
- <% values.each do |value| %>
- <%= hidden_field_tag "bucket_ids[]", value, id: nil %>
- <% end %>
- <% end %>
-
- <% if values = params[:tag_ids].presence %>
- <% values.each do |value| %>
- <%= hidden_field_tag "tag_ids[]", value, id: nil %>
- <% end %>
- <% end %>
-
- <% if values = params[:assignee_ids].presence %>
- <% values.each do |value| %>
- <%= hidden_field_tag "assignee_ids[]", value, id: nil %>
- <% end %>
- <% end %>
-
- <%= button_tag type: :submit, class: "btn txt-small borderless" do %>
- <%= image_tag "bookmark-outline.svg", aria: { hidden: true }, size: 24 %>
-
Save this filter
- <% end %>
- <% end %>
-<% end %>
diff --git a/app/views/bubbles/filters/_buckets.html.erb b/app/views/bubbles/filters/_buckets.html.erb
deleted file mode 100644
index d5537aa07..000000000
--- a/app/views/bubbles/filters/_buckets.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
diff --git a/app/views/bubbles/filters/_index.html.erb b/app/views/bubbles/filters/_index.html.erb
deleted file mode 100644
index ff83a8640..000000000
--- a/app/views/bubbles/filters/_index.html.erb
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
diff --git a/app/views/bubbles/filters/_tags.html.erb b/app/views/bubbles/filters/_tags.html.erb
deleted file mode 100644
index 7972e7e2f..000000000
--- a/app/views/bubbles/filters/_tags.html.erb
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
- <% if filter.tags.present? %>
- <%= link_to bubbles_path(filter.to_params.without(:tag_ids)), class: "btn", style: "font-size: 0.4em;" do %>
- <%= image_tag "remove.svg", aria: { hidden: true }, size: 24 %>
- Clear
- <% end %>
- <% end %>
-
diff --git a/app/views/filter_chips/create.turbo_stream.erb b/app/views/filter_chips/create.turbo_stream.erb
new file mode 100644
index 000000000..17d0d268f
--- /dev/null
+++ b/app/views/filter_chips/create.turbo_stream.erb
@@ -0,0 +1,7 @@
+<% text = params[:text] || %Q("#{params[:value]}") %>
+
+<%= turbo_stream.remove filter_chip_id(params[:name], params[:value]) %>
+
+<%= turbo_stream.append params[:frame] do %>
+ <%= filter_chip_tag text, name: params[:name], value: params[:value], class: "fill-selected" %>
+<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 0778fb130..dffa263f2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -33,6 +33,7 @@ Rails.application.routes.draw do
end
resources :filters
+ resources :filter_chips
resource :first_run
resource :session
diff --git a/db/migrate/20241108205445_hash_filter_params.rb b/db/migrate/20241108205445_hash_filter_params.rb
new file mode 100644
index 000000000..e403598d0
--- /dev/null
+++ b/db/migrate/20241108205445_hash_filter_params.rb
@@ -0,0 +1,7 @@
+class HashFilterParams < ActiveRecord::Migration[8.0]
+ def change
+ change_column :filters, :params, :string, null: false
+ change_column_default :filters, :params, from: {}, to: nil
+ rename_column :filters, :params, :params_digest
+ end
+end
diff --git a/db/migrate/20241113185136_create_filter_assigner_join_table.rb b/db/migrate/20241113185136_create_filter_assigner_join_table.rb
new file mode 100644
index 000000000..0ba861f47
--- /dev/null
+++ b/db/migrate/20241113185136_create_filter_assigner_join_table.rb
@@ -0,0 +1,8 @@
+class CreateFilterAssignerJoinTable < ActiveRecord::Migration[8.0]
+ def change
+ create_join_table :filters, :assigners do |t|
+ t.index :filter_id
+ t.index :assigner_id
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index bbb0daf2c..a6bac56a8 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.0].define(version: 2024_11_05_224305) do
+ActiveRecord::Schema[8.0].define(version: 2024_11_13_185136) do
create_table "accesses", force: :cascade do |t|
t.integer "bucket_id", null: false
t.integer "user_id", null: false
@@ -65,6 +65,13 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_05_224305) do
t.index ["filter_id"], name: "index_assignees_filters_on_filter_id"
end
+ create_table "assigners_filters", id: false, force: :cascade do |t|
+ t.integer "filter_id", null: false
+ t.integer "assigner_id", null: false
+ t.index ["assigner_id"], name: "index_assigners_filters_on_assigner_id"
+ t.index ["filter_id"], name: "index_assigners_filters_on_filter_id"
+ end
+
create_table "assignments", force: :cascade do |t|
t.integer "assignee_id", null: false
t.integer "bubble_id", null: false
@@ -131,11 +138,11 @@ ActiveRecord::Schema[8.0].define(version: 2024_11_05_224305) do
create_table "filters", force: :cascade do |t|
t.integer "creator_id", null: false
- t.json "params", default: {}, null: false
+ t.string "params_digest", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.json "fields", default: {}, null: false
- t.index ["creator_id", "params"], name: "index_filters_on_creator_id_and_params", unique: true
+ t.index ["creator_id", "params_digest"], name: "index_filters_on_creator_id_and_params_digest", unique: true
end
create_table "filters_tags", id: false, force: :cascade do |t|
diff --git a/test/controllers/filter_chips_controller_test.rb b/test/controllers/filter_chips_controller_test.rb
new file mode 100644
index 000000000..8be4c3b6c
--- /dev/null
+++ b/test/controllers/filter_chips_controller_test.rb
@@ -0,0 +1,16 @@
+require "test_helper"
+
+class FilterChipsControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ sign_in_as :kevin
+ end
+
+ test "create" do
+ post filter_chips_url(format: :turbo_stream), params: { text: "for David", name: "assignee_ids[]", value: users(:david).id, frame: "assignee_chips" }
+ assert_response :success
+
+ assert_turbo_stream action: :remove, target: "assignee_ids[]__filter--#{users(:david).id}"
+ assert_turbo_stream action: :append, target: "assignee_chips"
+ assert_select "button", text: "for David"
+ end
+end
diff --git a/test/controllers/filters_controller_test.rb b/test/controllers/filters_controller_test.rb
index 2522fde07..8e422fae2 100644
--- a/test/controllers/filters_controller_test.rb
+++ b/test/controllers/filters_controller_test.rb
@@ -28,6 +28,6 @@ class FiltersControllerTest < ActionDispatch::IntegrationTest
assert_difference "users(:david).filters.count", -1 do
delete filter_url(filters(:jz_assignments))
end
- assert_redirected_to bubbles_path(filters(:jz_assignments).params)
+ assert_redirected_to bubbles_path(filters(:jz_assignments).as_params)
end
end
diff --git a/test/fixtures/filters.yml b/test/fixtures/filters.yml
index b80de3a69..e7134b059 100644
--- a/test/fixtures/filters.yml
+++ b/test/fixtures/filters.yml
@@ -3,4 +3,4 @@ jz_assignments:
tags: mobile
assignees: jz
fields: <%= { indexed_by: :most_discussed }.to_json %>
- params: <%= { indexed_by: :most_discussed, tag_ids: [ ActiveRecord::FixtureSet.identify(:mobile) ], assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }.to_json %>
+ params_digest: <%= Filter.digest_params({ indexed_by: :most_discussed, tag_ids: [ ActiveRecord::FixtureSet.identify(:mobile) ], assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }) %>
diff --git a/test/models/bubble_test.rb b/test/models/bubble_test.rb
index 11e50d926..eae16426a 100644
--- a/test/models/bubble_test.rb
+++ b/test/models/bubble_test.rb
@@ -62,6 +62,10 @@ class BubbleTest < ActiveSupport::TestCase
assert_equal bubbles(:logo, :layout), Bubble.assigned_to(users(:jz))
end
+ test "assigned by" do
+ assert_equal bubbles(:layout, :logo), Bubble.assigned_by(users(:david))
+ end
+
test "in bucket" do
new_bucket = accounts("37s").buckets.create! name: "New Bucket", creator: users(:david)
assert_equal bubbles(:logo, :shipping, :layout, :text), Bubble.in_bucket(buckets(:writebook))
diff --git a/test/models/filter_test.rb b/test/models/filter_test.rb
index 49c88942d..3b1ec136b 100644
--- a/test/models/filter_test.rb
+++ b/test/models/filter_test.rb
@@ -3,10 +3,10 @@ require "test_helper"
class FilterTest < ActiveSupport::TestCase
test "persistence" do
assert_difference "users(:david).filters.count", +1 do
- filter = users(:david).filters.persist!(indexed_by: "most_boosted")
+ filter = users(:david).filters.persist!(indexed_by: "most_boosted", tag_ids: [ tags(:mobile).id ])
assert_changes "filter.reload.updated_at" do
- assert_equal filter, users(:david).filters.persist!(indexed_by: "most_boosted")
+ assert_equal filter, users(:david).filters.persist!(indexed_by: "most_boosted", tag_ids: [ tags(:mobile).id ])
end
end
end
@@ -15,6 +15,9 @@ class FilterTest < ActiveSupport::TestCase
Current.set session: sessions(:david) do
@new_bucket = accounts("37s").buckets.create! name: "Inaccessible Bucket"
@new_bubble = @new_bucket.bubbles.create!
+
+ bubbles(:layout).capture Comment.new(body: "I hate haggis")
+ bubbles(:logo).capture Comment.new(body: "I love haggis")
end
assert_not_includes users(:kevin).filters.new.bubbles, @new_bubble
@@ -22,9 +25,18 @@ class FilterTest < ActiveSupport::TestCase
filter = users(:david).filters.new indexed_by: "most_discussed", assignee_ids: [ users(:jz).id ], tag_ids: [ tags(:mobile).id ]
assert_equal [ bubbles(:layout) ], filter.bubbles
+ filter = users(:david).filters.new assigner_ids: [ users(:david).id ], tag_ids: [ tags(:mobile).id ]
+ assert_equal [ bubbles(:layout) ], filter.bubbles
+
filter = users(:david).filters.new assignments: "unassigned", bucket_ids: [ @new_bucket.id ]
assert_equal [ @new_bubble ], filter.bubbles
+ filter = users(:david).filters.new terms: [ "haggis" ]
+ assert_equal bubbles(:logo, :layout), filter.bubbles
+
+ filter = users(:david).filters.new terms: [ "haggis", "love" ]
+ assert_equal [ bubbles(:logo) ], filter.bubbles
+
filter = users(:david).filters.new indexed_by: "popped"
assert_equal [ bubbles(:shipping) ], filter.bubbles
end
@@ -37,7 +49,7 @@ class FilterTest < ActiveSupport::TestCase
test "param sanitization" do
filter = users(:david).filters.new indexed_by: "most_active", tag_ids: "", assignee_ids: [ users(:jz).id ], bucket_ids: [ buckets(:writebook).id ]
expected = { assignee_ids: [ users(:jz).id ], bucket_ids: [ buckets(:writebook).id ] }
- assert_equal expected.stringify_keys, filter.params
+ assert_equal expected, filter.as_params
end
test "cacheable" do
@@ -45,18 +57,35 @@ class FilterTest < ActiveSupport::TestCase
assert users(:david).filters.create!(bucket_ids: [ buckets(:writebook).id ]).cacheable?
end
+ test "default fields" do
+ assert_equal "most_active", users(:david).filters.new.indexed_by
+ end
+
+ test "indexed by" do
+ assert_predicate users(:david).filters.new(indexed_by: "most_discussed").indexed_by, :most_discussed?
+ end
+
+ test "assignments" do
+ assert_predicate users(:david).filters.new(assignments: "unassigned").assignments, :unassigned?
+ end
+
+ test "terms" do
+ assert_empty users(:david).filters.new.terms
+ assert_includes users(:david).filters.new(terms: [ "haggis" ]).terms, "haggis"
+ end
+
test "resource removal" do
filter = users(:david).filters.create! tag_ids: [ tags(:mobile).id ], bucket_ids: [ buckets(:writebook).id ]
- assert_includes filter.params["tag_ids"], tags(:mobile).id
+ assert_includes filter.as_params[:tag_ids], tags(:mobile).id
assert_includes filter.tags, tags(:mobile)
- assert_includes filter.params["bucket_ids"], buckets(:writebook).id
+ assert_includes filter.as_params[:bucket_ids], buckets(:writebook).id
assert_includes filter.buckets, buckets(:writebook)
assert_changes "filter.reload.updated_at" do
tags(:mobile).destroy!
end
- assert_nil filter.reload.params["tag_ids"]
+ assert_nil Filter.find(filter.id).as_params["tag_ids"] # can't reload because as_params is memoized
assert_changes "Filter.exists?(filter.id)" do
buckets(:writebook).destroy!