-
Notifications
You must be signed in to change notification settings - Fork 29
[#137627] add links to 10 most recently purchased products to home page #1429
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
Conversation
meara
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey Gilad--great start! Let me know if you have any questions about the comments I left you--we can also go through this together or pair a bit if that's helpful.
Also, can you add the redmine ticket number to the PR title? We usually do [#123456] pull request title as the format.
config/locales/en.yml
Outdated
| facility_downcase: facility | ||
| facilities_downcase: facilities | ||
|
|
||
| Product: "!activerecord.models.product.one!" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need these i18n fields for products. The reason we have them for facility is because Dartmouth uses "Resource" instead of "Facility" to refer to the facility model.
| facilities: | ||
| index: | ||
| recently_used: Recently Used !Facilities! | ||
| recently_purchased: Recently Purchased !Products! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the ticket, I think this text should be "Recently Used Products"--you don't need to use the !Products! notation.
app/models/product.rb
Outdated
| scope :not_archived, -> { where(is_archived: false) } | ||
| scope :mergeable_into_order, -> { not_archived.where(type: mergeable_types) } | ||
| scope :in_active_facility, -> { joins(:facility).where(facilities: { is_active: true }) } | ||
| scope :recently_purchased, -> { joins(order_details: :order).order('ordered_at DESC').distinct.limit(10) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does .order(ordered_at: :desc) work here? If so, it would be better to use that format to take advantage of ActiveRecord.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That syntax results in a Unknown column 'products.orders' error, but I think I figured out how to do it with .merge
app/views/facilities/index.html.haml
Outdated
|
|
||
| - if @products.any? | ||
| %h4= text(".recently_purchased") | ||
| %table.table.table-striped.table-hover.product_list |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely get why you're using a table here; however, we don't typically use tables when listing products in this type of view, so it kind of breaks the style idiom for users, if that makes sense.
I would suggest modeling this after https://github.com/tablexi/nucore-open/blob/master/app/views/facilities/_product_list.html.haml#L7 -- that's what we use in facilities/show, so the link locations and style will match what the user expects.
app/views/facilities/index.html.haml
Outdated
| %tr | ||
| %td | ||
| - if product.is_a?(Instrument) | ||
| = link_to product.name, [product.facility, product, :schedule] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this link location, and the link on line 38, are actually linking to the admin views for the product. We want to link to the ordering views (see the product list partial I linked above), since this feature is for regular users trying to buy things, not admins wanting to manage products.
|
Oh also, one more small note: I think we want the "Recently Used Products" to appear at the top of the page, above the list of all facilities. |
app/models/product.rb
Outdated
| scope :not_archived, -> { where(is_archived: false) } | ||
| scope :mergeable_into_order, -> { not_archived.where(type: mergeable_types) } | ||
| scope :in_active_facility, -> { joins(:facility).where(facilities: { is_active: true }) } | ||
| scope :recently_purchased, ->(user) { joins(order_details: :order).merge(Order.where(user: user, state: :purchased).group(:product_id).order('MAX(orders.ordered_at) DESC')).limit(10) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're not still using this, right?
|
Also @giladshanan looks like you probably want to run |
jhanggi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, great work. I like the separate object and the queries are definitely nicer now.
| def recently_used_facilities(limit = 5) | ||
| return [] unless user | ||
|
|
||
| Facility.active.joins(products: { order_details: :order }).merge(Order.for_user(user).group("orders.facility_id").order('MAX(orders.ordered_at) DESC')).limit(limit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a good way to add some line breaks in here to make the line not so long and easier to read?
Also, use single quotes. And can you say group(orders: :facility_id)? (It’s very likely that doesn’t work, but would probably be cleaner)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So group(orders: :facility_id) doesn't work (the group method doesn't accept hash arguments) but group(:id) does the trick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I struggle with understanding why group(:id) works. Might just be how the two scopes get merged. I think I would lean towards keeping the string in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@meara it looks like Oracle doesn't like the new AR queries so we're gonna have to break it up into two steps like it was before.
app/views/facilities/index.html.haml
Outdated
|
|
||
| .alert.alert-info= text(".welcome") | ||
|
|
||
| - if @products.any? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be nicer to call this @recent_products.
| end | ||
| end | ||
|
|
||
| end No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You use sublime, right? I would recommend adding the "ensure_newline_at_eof_on_save": true and ”trim_trailing_white_space_on_save": true settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, thanks!
app/views/facilities/index.html.haml
Outdated
|
|
||
| - if @products.any? | ||
| %h4= text(".recently_purchased") | ||
| .product_list{ class: @products.first.class.model_name.to_s.pluralize.downcase } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we can’t use the product_list partial itself?
Part of it may be related to sorting, and if that’s the case, we could pull the sorting out of that and do it in facilities/show, though to be honest, I think we should sort the products alphabetically. I think it’ll be easier to use that way.
If we do decide to decide to leave this here, there’s probably some cleanup we can do: I don’t think the classes here actually do anything.
It might also be nice to add the facility name. But I could go either way.
Also, as noted in the ticket, we want to show recent facilities first, then products.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The product_list partial expects a current_facility, which we don't have in this context. Is there an easy workaround that would let us use the partial anyway?
Sorting alphabetically seems fine to me. What do you mean about adding the facility name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the facility name to the product link? Something like: Product name (Facility name)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So facilities have a sorted scope and products have an alphabetized scope, they do very similar things. I lean towards alphabetized and sorted is only used in a handful of places. Do we want to change this or leave it alone?
jhanggi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor comments.
| # GET /facilities | ||
| def index | ||
| @facilities = Facility.active.sorted | ||
| @recently_used_facilities = MostRecentlyUsedSearcher.new(acting_user).recently_used_facilities.sorted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we create an aliased scope alphabetized for facilities? I do like that better than sorted. Probably want to leave sorted around for now because other stuff probably uses it. I'd be fine if we did that separately, though.
app/views/facilities/index.html.haml
Outdated
| %ul | ||
| - @recent_products.each do |product| | ||
| %li | ||
| = link_to "#{product.name}" + " - #{product.facility}", facility_product_path(product.facility, product) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpicky, but I would do this all in one string: `"#{product.name} - #{product.facility}"
|
@meara @jhanggi It looks like |
meara
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This generally looks good, just a few small notes/questions.
Also, can you attach a screenshot of what it looks like post switch to product list partial?
| @facilities = Facility.active.alphabetized | ||
| @recently_used_facilities = MostRecentlyUsedSearcher.new(acting_user).recently_used_facilities.alphabetized | ||
| @active_tab = "home" | ||
| @recent_products = MostRecentlyUsedSearcher.new(acting_user).recently_used_products.alphabetized |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure that including the alphabetized scope here doesn't affect which products are selected as recently used? I'm a little leery of adding additional sorting in here because of the way active record chains scopes. Can we test this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The alphabetized scope adds a secondary column to sort by, in this case the end of the query looks like this: ORDER BY MAX(orders.ordered_at) DESC, lower(products.name). Testing it out it doesn't seem to change the order at all. In fact, if you take a closer look at the first screenshot above, you'll see the products are not listed in alpha order. The ordering in the two more recent screenshots comes from the partial, which uses Array#sort on the products collection returned by @recent_products.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we ditch the alphabetized scope for products since it isn't doing much?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just about to suggest that 😀 . Leaving it in seems misleading, since the actual ordering is provided by the Array#sort in the partial.
It might be nice to pull the sort out of the partial and into the controller (and add a comment on why we're doing the sort in ruby and not in SQL)--you'd need to make sure you made the change for everywhere the partial is used, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, it looks like this would mean putting 10 .sorts in the facilities/show view. Is there another way to clarify intent here? Maybe a code comment in the facilities controller where we call the MostRecentlyUsedSearcher's?
| - products.sort.each do |product| | ||
| %li{ class: product.class.model_name.to_s.downcase } | ||
| = public_calendar_link(product) | ||
| - if current_facility.present? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does public_calendar_link require the facility? I think we can leave that in, I don't think having the calendar icon appear hurts anything, and it might be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, if we can include it here, I say we do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The public_calendar_link does require a facility, it links to facility_instrument_public_schedule_path. There's a way to make it work though. I'll put it in my next commit with the other changes so you can let me know what you think.
| - if current_facility.present? | ||
| %h3= product_list_title products, local_assigns[:title_extra] | ||
|
|
||
| - if products.first.class.model_name == "TimedService" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the TimedService note ought to be nested under the if current_facility.present? as well. Try adding a Timed Service product type to see how it shows up.
| = builder.text_field :quantity, value: 0, class: "product_quantity", index: nil | ||
| = builder.hidden_field :product_id, value: product.id, index: nil | ||
| = link_to product.name + (product.is_hidden? ? ' (hidden)' : ''), facility_product_path(current_facility, product) | ||
| = link_to product.name + (product.is_hidden? ? ' (hidden)' : ''), facility_product_path(current_facility || product.facility, product) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can just be product.facility, rather than using the or.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also we should check for N+1s here.
jhanggi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple minor style notes, but otherwise I agree with all Meara's comments
| .joins(products: { order_details: :order }) | ||
| .merge(Order.for_user(user) | ||
| .group(:id) | ||
| .order('MAX(orders.ordered_at) DESC')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use double quotes for strings.
| .joins(order_details: :order) | ||
| .merge(Order.for_user(user) | ||
| .group(:id) | ||
| .order('MAX(orders.ordered_at) DESC')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double quotes
| - products.sort.each do |product| | ||
| %li{ class: product.class.model_name.to_s.downcase } | ||
| = public_calendar_link(product) | ||
| - if current_facility.present? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, if we can include it here, I say we do it.
meara
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting so close!
app/helpers/products_helper.rb
Outdated
|
|
||
| def public_calendar_options(product) | ||
| if current_facility.show_instrument_availability? | ||
| if current_facility&.show_instrument_availability? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would switch this to current_facility&.show_instrument_availability? || !current_facility && product.facility.show_instrument_availability?, which is kind of gross, but ensures that we avoid N+1s in the facility context by using current_facility, and when we're in the home page context it will display with colors or not as appropriate (which is what show_instrument_availability? is for).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does current_facility&.show_instrument_availability? || product.facility.show_instrument_availability? do the trick?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is we only want to call product.facility if current_facility is not present, rather than any time current_facility&.show_instrument_availability? evaluates to false. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that does make sense, thanks.
| def recently_used_products(limit = 10) | ||
| return [] unless user | ||
|
|
||
| Product.active.in_active_facility |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would put includes(:facility) explicitly here, rather than in the in_active_facility scope.
| = builder.text_field :quantity, value: 0, class: "product_quantity", index: nil | ||
| = builder.hidden_field :product_id, value: product.id, index: nil | ||
| = link_to product.name + (product.is_hidden? ? ' (hidden)' : ''), facility_product_path(current_facility, product) | ||
| = link_to product.name + (product.is_hidden? ? ' (hidden)' : ''), facility_product_path(product.facility, product) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, I think what you had with current_facility || product.facility is better, because it lets us avoid all the includes we would need in the facility-specific context to prevent N+1s.
Specifically on this page: https://github.com/tablexi/nucore-open/blob/master/app/views/facilities/show.html.haml
jhanggi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had some thoughts about how we're doing the sorting, but we can deal with that later.
| @recently_used_facilities = MostRecentlyUsedSearcher.new(acting_user).recently_used_facilities.alphabetized | ||
| @active_tab = "home" | ||
| # recent products get sorted alphabetically in the product_list partial | ||
| @recent_products = MostRecentlyUsedSearcher.new(acting_user).recently_used_products.includes(:facility) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any downside to adding the alphabetization here, even if it's still re-sorted in the partial. We don't need to do it now, but I think it would be nice to not do the sort in the product_list partial (facilities/show is kind of nasty already, but could defiitely be cleaned up), but that can be for another time.
meara
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 pending circle
|
🙌 |



Release Notes
Add a list of the user's 10 most recently purchased products to the user's home page.
Screenshots
Context
Allow users to access frequently used products faster.
Refactorings
sortedscope on Facilitiy model toalphabetizedMostRecentlyUsedSearcherservice object for Products and Facilitiespublic_calendarview helper to handle non-specific facility context