Skip to content

Commit fdd9e5a

Browse files
committed
feat(products): filter plans by product
## Context The product detail page has a Plans tab listing the plans that include a given product. The plans link to products through plan_products. ## Description Add an optional product_id filter to PlansQuery and the plans resolver so the collection can be scoped to a single product, reusing the existing pagination, ordering and search. Filtering is done through a plan_products subquery to avoid join duplicates.
1 parent df7fd8d commit fdd9e5a

6 files changed

Lines changed: 74 additions & 6 deletions

File tree

app/graphql/resolvers/plans_resolver.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ class PlansResolver < Resolvers::BaseResolver
1111

1212
argument :limit, Integer, required: false
1313
argument :page, Integer, required: false
14+
argument :product_id, ID, required: false
1415
argument :search_term, String, required: false
1516
argument :with_deleted, Boolean, required: false
1617

1718
type Types::Plans::Object.collection_type, null: false
1819

19-
def resolve(page: nil, limit: nil, search_term: nil, with_deleted: nil)
20+
def resolve(page: nil, limit: nil, search_term: nil, with_deleted: nil, product_id: nil)
2021
result = PlansQuery.call(
2122
organization: current_organization,
2223
search_term:,
2324
filters: {
24-
with_deleted:
25+
with_deleted:,
26+
product_id:
2527
},
2628
pagination: {
2729
page:,

app/queries/plans_query.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
class PlansQuery < BaseQuery
44
Result = BaseResult[:plans]
5-
Filters = BaseFilters[:with_deleted, :include_pending_deletion]
5+
Filters = BaseFilters[:with_deleted, :include_pending_deletion, :product_id]
66

77
def call
88
plans = base_scope.result
@@ -19,7 +19,15 @@ def call
1919
private
2020

2121
def base_scope
22-
Plan.parents.where(organization:).ransack(search_params)
22+
scope = Plan.parents.where(organization:)
23+
scope = with_product(scope)
24+
scope.ransack(search_params)
25+
end
26+
27+
def with_product(scope)
28+
return scope if filters.product_id.blank?
29+
30+
scope.where(id: PlanProduct.where(product_id: filters.product_id).select(:plan_id))
2331
end
2432

2533
def search_params

schema.graphql

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schema.json

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/graphql/resolvers/plans_resolver_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,38 @@
7777
expect(plans_response["metadata"]["totalCount"]).to eq(2)
7878
end
7979
end
80+
81+
context "when filtering by product_id" do
82+
let(:product) { create(:product, organization:) }
83+
let(:other_plan) { create(:plan, organization:) }
84+
let(:query) do
85+
<<~GQL
86+
query($productId: ID!) {
87+
plans(limit: 5, productId: $productId) {
88+
collection { id }
89+
metadata { totalCount }
90+
}
91+
}
92+
GQL
93+
end
94+
95+
before do
96+
other_plan
97+
create(:plan_product, organization:, plan:, product:)
98+
end
99+
100+
it "returns only the plans linked to the product" do
101+
result = execute_graphql(
102+
current_user: membership.user,
103+
current_organization: organization,
104+
permissions: required_permission,
105+
query:,
106+
variables: {productId: product.id}
107+
)
108+
109+
plans_response = result["data"]["plans"]
110+
expect(plans_response["collection"].map { |p| p["id"] }).to eq([plan.id])
111+
expect(plans_response["metadata"]["totalCount"]).to eq(1)
112+
end
113+
end
80114
end

spec/queries/plans_query_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,16 @@
8989
expect(returned_ids).not_to include(plan_third.id)
9090
end
9191
end
92+
93+
context "when filtering by product_id" do
94+
let(:product) { create(:product, organization:) }
95+
let(:filters) { {product_id: product.id} }
96+
97+
before { create(:plan_product, organization:, plan: plan_first, product:) }
98+
99+
it "returns only the plans linked to the product" do
100+
expect(result).to be_success
101+
expect(returned_ids).to eq([plan_first.id])
102+
end
103+
end
92104
end

0 commit comments

Comments
 (0)