Improve rules - add name, allow sorting, improve UI #2177
Reference in New Issue
Block a user
Delete Branch "rule-name"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR adds and improves a bunch of stuff around rules! I've been playing around with these more recently and thought they could use a few upgrades
Arel(), so I opted to just keep the default behavior, which is null values first. Also happy to switch this up if that makes more sense. A checkbox option as a filter might be a middle ground here.IFthis,THENthis,FORthis period of time with some icons. This carries over to the form as well.As a side note - I see that #2154 is in progress and will probably change a bunch of things (like the
icon()helper). If it makes more sense to wait for this (and my other PRs) until after that is merged, I can handle the changes to cut down on conflicts/ongoing changes.Rule page:

Form:

Video showing creation of a rule + sorting:
https://github.com/user-attachments/assets/da47dd8b-2ad1-4834-bc97-3aec9af7bb29
@@ -35,4 +32,2 @@<div class="bg-container shadow-border-xs rounded-xl p-4"><% if @rules.any? %>For some reason brakeman started warning about these lines so instead of a dynamic render I'm doing it a bit more explicitly. This isn't something I changed directly, although maybe the sorting changed how this works here. I don't quite understand this - I'd love some more insight here from someone who more ruby/rails than I do.
@ahatzz11 overall looking good! I'll take a closer look at this PR and #2174, #2175 once I get our pre-launch stuff done today/tomorrow!
I agree the sorting isn't the best in the existing UI. The Brakeman warning you're getting is because you're dynamically constructing the sort query. Even though you're safe-listing values, Brakeman seems to always flag anything custom passed to ActiveRecord query builders.
That aside, I'd actually propose a much simpler sorting convention that will naturally clear out the brakeman warning—what if we just sorted by "most recently edited" (
order(updated_at: :desc))? I think that's probably more helpful than sorting alphabetically based on my own usage patterns of this feature.In regards to the design changes + name addition, I'll have to defer to @justinfar on that. I know he has an official Figma spec for this and I want to stick with that whenever possible. That said, we've had a few things in motion around the design of this feature so I'll let @justinfar speak to that!
Good to know about Brakeman!
I do like like
updated_atovercreated_atas the default, I always like seeing more recently touched things as well - that has been updated now.Personally, I think being able to sort alphabetically, especially when tied to a name, would be super helpful. Some of this comes down to how people use the naming convention, but I can see myself having a lot of rules and being able to give them meaningful order other than a timestamp would be great, e.g.
I think it's probably a bit hard to see how alphabetical sorting would fit into usage today since there aren't any names at this point. There was also a feature request for this today - #2198, so I think others would have similar cases. Having ability to search would be an awesome additional feature.
Would love to hear design feedback! I think it looks great, but don't want to veer too far off a larger vision 🙂
@ahatzz11 those are some valid points. I do agree if we had a
nameproperty in there then alphabetical sorting could allow users to create their own naming convention for organization.@justinfar thoughts on all of this?
Fantastic work here @ahatzz11, I made some slight adustments in terms of the UI (so that it's more consistent with other views across the product) but other than that not much else from my end in terms of feedback.
Changes made:
text/secondarycolorLink to the the Figma file:
https://www.figma.com/design/lonJmVk3HYkwZoIO7xYP2w/Maybe-App--Community-?node-id=6370-1960
Preview of the changes made:
@justinfar thanks for the feedback! I'll work on this over the next couple of days. Can you link the figma file? I think a copy/paste got missed in your comment.
One question I see right away - on your screenshot the
font-monoon theIF/THIS/FORis gone, do you want that to be normal font on the edit screen? Or should we keep those on the edit to match the list page? (I personally like the font-mono, but again happy to do whatever you think is best for the bigger vision!)@ahatzz11 Ah thanks for letting me know, just updated the original comment, here's the link again just in case:
https://www.figma.com/design/lonJmVk3HYkwZoIO7xYP2w/Maybe-App--Community-?node-id=6370-1960
Yep I'd like us to keep using the normal font on the edit rules view because I'd like us to use mono only for cases where we have numericals/data viz/value comparison etc (where it makes sense)
@justinfar While I'm working on this I've got a few design questions!
bg-transparent, but do we want to keep that consistent with other dropdowns and have it look something like this? I can't seem to find another example. The transparent does look nice, but without cursor pointer I think is tough to notice it's a clickable area.cursor-pointer- is that something we should have across the board?Alright the current code is now updated with most of the design changes:
@zachgoll I could use some help/advice on the implementation on displaying the rules with a divider. I messed around with this for quite a while and couldn't get to anything perfect or that I really liked, but the current code is about as close as I could get it. Theshadow-border-xsutility is great for being around a single thing (like the div that holds all the cards), but once I add it to each card with 0 spacing between it, I get a sloppy border because the border is in the margin and it becomes 2× bigger than intended. I also tried to make some newshadow-divider-{size}utilities, but didn't want to start making new utilities if we didn't think there would be other reasons for them. I ended up landing on using the divider from the sidebar. It isn't either (although close enough that most eyes wouldn't notice)<div class="h-px bg-alpha-black-100 w-full"></div>Big edit: I decided to forego the shadow-box here because we're not really making a shadow around a card anymore, but instead having a border around each rule. I ended up just using
<div class="border-b border-subdued"></div>to make the divider line, utilizing the existing border styling from the border-utils. I think this turned out excellent:One note - the figma says the border is
subduedbut that color seemed to not match very well, so I opted forborder-secondary. I'm not sure if this is just a mismatch between figma and the design system or if I'm missing something.Additionally, I updated some
text-andbg-attributes and rules now has a much nicer dark modeOne weird thing I've found with sorting - because it's within a form right now, changing either option submits the form. A side effect of this, because of the direction toggle, is that the direction always toggles. This means if you're on name/asc and you move to updated_at, the direction becomes desc. If you're on name/desc and you move to updated_at, the direction becomes asc.
This is a bit strange and not quite what I expect to happen. I've got a couple ways to solve this, and wanted some feedback:
Move from a button and a
form.hidden_fieldto aLinkComponentthat just changes the route:This has a slightly odd side effect that it's a link, so browsers show the link on hover. I'm not too bothers by this (obviously there are a lot of other links in the app).
Add a new controller (
sort_direction_controller):In theory this could be reused in other places that had sorting direction, but I don't think that exists anywhere else at the moment.
@ahatzz11
Would rather keep it matching the design (as it'll stand out a bit too much within this context if we use the same input style).
As for the selects not having
cursor-pointerthat's definitely something we should address and have across the board, as it's an affordance that lets people know they can interact.I think the easiest way to handle this would be to keep the
sort_byselect field inside the form and move the direction toggle outside the form as a link.You could still keep the
form.hidden_field :directioninside the form, and then you could additionally include thesort_byin the link:That way, you let the URL deal with syncing the state between the two
This could be from some stale migrations, but I believe we need to delete this. You can run
bin/rails db:migrate:resetto rerun everything from a clean slate, thenrake demo_data:emptyto generate the dev user againHi @ahatzz11,
As we discussed earlier in #2198, I found another bug in the conditional group rule UI. I’ve created a new issue with a screen recording and explanation (#2221).
Since you're working on improving the UI as part of this task, could you take a look and see if the issue is resolved by your changes? If not, perhaps you can identify the root cause and address it?
Thanks!
Good catch - fixed this!
Awesome - fixed this!
@justinfar
I have updated the dropdown to match the figma design! I do like it blending in a bit more.
One final question on the design - The figma for rules currently has a full width border separating out each rule. I have an implementation for this with border instead of a shadowbox, but as I was clicking around the site I noticed it's a bit different than the other pages where there are a list of many items of the same type, e.g. tags and categories. Specifically - these use a shadowbox around the main card and then a not-quite-full-width divider (which alleviates the issue of two shadow borders overlapping and changing the colors a bit):
I ended up changing the rules list to match tags/categories in my latest push, but wanted to run this change by you before saying this PR is 100% ready to go. I personally like the exact match to the other pages. Here is a comparison:
Let me know what you think!
I added a few code improvement ideas, but I the functionality looks good to me.
Once @justinfar approves, we'll be all set.
@@ -1,5 +1,5 @@class Rule::Condition < ApplicationRecordbelongs_to :rule, optional: -> { where.not(parent_id: nil) }belongs_to :rule, touch: true, optional: -> { where.not(parent_id: nil) }belongs_to :parent, class_name: "Rule::Condition", optional: true, inverse_of: :sub_conditionsFor the
touch: trueon bothConditionandAction, what specifically is this achieving? I'm wondering if this may be a symptom of a domain invariant that we've missed during the rule building@@ -7,0 +23,4 @@</p></div><% end %><div class="flex items-center gap-2 mt-1">I had thrown a lot of this checking in a bit hastily for a quick fix a few weeks back and I'm totally fine keeping it as is for now. But probably worth noting that we'll eventually need to capture this a bit more clearly. Just an idea, but something that looks more like:
@@ -1,5 +1,5 @@class Rule::Condition < ApplicationRecordbelongs_to :rule, optional: -> { where.not(parent_id: nil) }belongs_to :rule, touch: true, optional: -> { where.not(parent_id: nil) }belongs_to :parent, class_name: "Rule::Condition", optional: true, inverse_of: :sub_conditionsThe reason I added these was for showing a proper
updated_atstatus when a condition or action changed. When adding that sorting I noticed an updated action on a rule wasn't making it appear as the latest updated. I looked in rails console and theupdated_attimestamp wouldn't change when editing an action or a condition, but it would on a name change.If there's a better way to solve this happy to work on that!
@@ -7,0 +23,4 @@</p></div><% end %><div class="flex items-center gap-2 mt-1">Agreed! I have seen some other weird stuff on this condition thing too so I threw this comment and my other experience into https://github.com/maybe-finance/maybe/issues/2230
@@ -1,5 +1,5 @@class Rule::Condition < ApplicationRecordbelongs_to :rule, optional: -> { where.not(parent_id: nil) }belongs_to :rule, touch: true, optional: -> { where.not(parent_id: nil) }belongs_to :parent, class_name: "Rule::Condition", optional: true, inverse_of: :sub_conditionsTo prove this out a bit I removed the
touch: trueon both thecondition.rbandaction.rband made the following video . Notice that the name and timeframe both create a new updated timestamp, but changing an action and condition done. I threw in the rulesupdated_atfield on the rule card just as a demonstration.https://github.com/user-attachments/assets/dce001e7-9cae-4ecb-8350-1e0122eb7eca
@@ -1,5 +1,5 @@class Rule::Condition < ApplicationRecordbelongs_to :rule, optional: -> { where.not(parent_id: nil) }belongs_to :rule, touch: true, optional: -> { where.not(parent_id: nil) }belongs_to :parent, class_name: "Rule::Condition", optional: true, inverse_of: :sub_conditionsAhh that makes sense! I had incorrectly assumed that since we're updating the Rule in
rules_controllervia theaccepts_nested_attributes_forit would automatically inherit this touch behavior, but guess not!@justinfar we all set on this one?
@zachgoll I'll fix this merge conflict here quick so it's ready once justin gives a 👍
sorry just seeing this now!
This works better than my solution, didn't even notice the shadowbox overlap so thank you for bringing it to my attention! Appreciate you looking out for consistency :)
all good to go on this one, excellent work @ahatzz11!
@zachgoll Alright this one is ready to go from my side - I added a new
bg-dividerutility that we can use on dividers/rulers so dark mode is handled nicely as well. I started down the rabbit hole to clean up some of the other rulers/dividers in the app, but I'll keep that as a separate PR so this one doesn't hang out for too long. Let me know if you think this utility belongs elsewhere!New

bg-divierutility in dark mode (implemented on rules):New

bg-dividernot implemented on categories (yet, I'll do this in a followup):Looks great! Thanks for tackling this one.
@ahatzz11 I haven't deeply thought through this, but just an FYI, I had started following this pattern for dividers in a few places:
050d5ebaad/app/components/menu_item_component.html.erb (L2)Not a big deal either way, just thought I'd call it out.
How can I apply the rule to categorize every transaction using AI without using an IF statement?