Add rule option to change transaction name #2175
Reference in New Issue
Block a user
Delete Branch "add-transaction-name-rule"
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 allows someone to rename a transaction as part of a transaction rule. This was kind of inspired by #2174. I personally have been wanting a transaction rename, because the transaction list is often a mess of names.
I think this is probably a good candidate for some AI work in the future (e.g. autodetect transaction name), which would probably be similar to autodetecting merchant, but this would allow for a manual rule as well.
I'd love some feedback here on the changes I made to
actions_controller.js- I had started making some of the showing/hiding more agnostic and cursor helped fill in some of the nuances of swapping select<>input types.Here's a video showing off:
https://github.com/user-attachments/assets/a87492ca-5f7b-4c4b-a7d3-c79e8fdd4296
@zachgoll I'm curious - what is the future functionality for thefunctiontype?update:
This PR has been refactored a bunch to include HTML templates that are cloned and injected by the stimulus controller.
I think this rule idea makes sense. I'm thinking of cases like:
If
amount > 1000ANDamount is positiveSet transaction name to:
Paycheck: Employer Name@@ -51,3 +49,1 @@optionEl.value = option[1];optionEl.textContent = option[0];this.valueSelectEl.appendChild(optionEl);#buildSelectFor(actionExecutor) {This is definitely the direction we'll need to go to introduce a text input but I'd like to try and keep it consistent with the pattern I've built over in
conditions_controller.jswith the#build[FieldType]For(model)convention:0946a1497a/app/javascript/controllers/rule/conditions_controller.js (L37-L41)My ultimate goal with this pattern is to move all style + Rails form attribute building out of the Stimulus controller so the Stimulus controller can work with 100% generic HTML and have minimal coupling with the view template. I'm not totally sold on my existing pattern yet and I haven't found great documentation around using Stimulus for this type of thing.
One thing I started doing that seems to work pretty well is using HTML templates where we build a template in our ERB view (which has access to our app's styles, form structure, etc.), and then cloning the template and show/hiding it in the Stimulus controller. That might be helpful in this case too. Here's an example of doing that:
ERB defines HTML template (notice how we're heavily leveraging the Rails form builder, which is not available in a Stimulus controller):
0946a1497a/app/views/rule/conditions/_condition_group.html.erb (L30-L35)Stimulus controller stays "dumb" and only has to know which target element to append the template to:
0946a1497a/app/javascript/controllers/rule/conditions_controller.js (L14-L21)@@ -2,7 +2,6 @@To answer your question about the
functiontype, it's actually being used already. A "function" executor would represent a type of action where the user doesn't assign any specific value, but rather, selects a function that will run automatically.The two function types we currently have would be:
These get their designation through the inheritance chain here:
0946a1497a/app/models/rule/action_executor.rb (L16-L18)Continuing my comment from above, this case statement will still need
functionadded and we need to preserve the"hidden" => !needs_valueso when a function is selected, we properly hide any input that is no longer needed.@@ -19,38 +24,67 @@ export default class extends Controller {(executor) => executor.key === e.target.value,);Does this seem like the right way to ensure the "to" text (a child of the
actionValuediv) stays in place? Originally I just targeted the htmlspanbut I liked the idea of targeting it a bit more specifically.@@ -51,3 +49,1 @@optionEl.value = option[1];optionEl.textContent = option[0];this.valueSelectEl.appendChild(optionEl);#buildSelectFor(actionExecutor) {Alright so I think I have this done. Again - I'm a ruby/rails novice here so if my second attempt is off the mark let me know, but I believe it's much closer to the pattern you've described. I do like this a lot better - all of the styling stays a lot more consistent. I have left a couple of questions/comments about a few things too.
@@ -2,7 +2,6 @@Ah I understand the
functionnow - I didn't see that locally before sinceai_enabled = false. I turned those on manually just to validate, and have the following:needs_valueback to ensure theto ___is not shown when the type is not select/text (we can keep this hardcoded here, or doaction.executor.type != "function", I kind of like the positive personally so I did that.functionin thecasesince it ends up being hidden. If I'm misunderstanding this let me knowOh got it! This makes sense, I missed it (per other comment below)
@@ -14,3 +13,3 @@<%= tag.div class: class_names("min-w-1/2 flex items-center gap-2", "hidden" => !needs_value),<%= tag.div class: class_names("min-w-1/2 flex items-center gap-2"),data: { rule__actions_target: "actionValue" } do %>Now that we have the templates and the injection of those templates from the stimulus controller, this
casebecomes a bit weird since it is only ever used for the initial render. The action type for that initial render is determined by the form, which currently has a hardcoded action (and I don't see this changing since we want a first item in the list):441f436187/app/views/rules/_form.html.erb (L24)I left a comment here explaining this since there is technically unused code here, and if more types come into play they could be put here, but thought I'd get your opinion on what you'd like to see for the initial load vs subsequent injections.
A side effect of this PR is that it fixes some weird sizing issues that seem to exist in production:
@@ -14,3 +13,3 @@<%= tag.div class: class_names("min-w-1/2 flex items-center gap-2", "hidden" => !needs_value),<%= tag.div class: class_names("min-w-1/2 flex items-center gap-2"),data: { rule__actions_target: "actionValue" } do %>For sake of cleaner code without dead paths, I opted to remove the
casehere since that's only required for the initial render. This commit is the diff between thecaseand nocase. Thiscaseisn't actually needed any more because of the template injection.I'm happy to revert this if it makes more sense to have it, but I personally like the cleaner code with comment.
@@ -14,3 +13,3 @@<%= tag.div class: class_names("min-w-1/2 flex items-center gap-2", "hidden" => !needs_value),<%= tag.div class: class_names("min-w-1/2 flex items-center gap-2"),data: { rule__actions_target: "actionValue" } do %>Another piece of cleanup we can do now that we have template injection on the entire action target (which also means we don't need the
case), is we no longer have a need foraction_typeorneeds_value:action_typewas used to determine which one to show for each action. Now that we are injecting rules, this wasn't used. Since the initial load is now hardcoded, we don't really need thisneeds_valuewas used to show/hide the entire action target for cases likefunction. This is also not needed anymore since the controller hides the entire action value for cases that don't need any input: https://github.com/maybe-finance/maybe/pull/2175/files#diff-cd0bc09d066aff1b98283823f34902bd4d57c03b6fd45a3bb2f08ea8f6fa0dadR36-R37@@ -2,7 +2,6 @@Yep, looks good! You're correct I misspoke on the case statement. We do not need the
functiontype there.Works great! Left one small suggestion around the templating but otherwise I think this is good to go.
@@ -19,38 +24,67 @@ export default class extends Controller {(executor) => executor.key === e.target.value,);I don't think it's a big deal either way, but I'd probably throw the "to" span directly in each of the templates. It's obviously slightly duplicative, but I think it increases clarity and simplifies things enough to warrant that. This way, the replacements all happen as a single string of markup:
@@ -19,38 +24,67 @@ export default class extends Controller {(executor) => executor.key === e.target.value,);Great idea - I like this a lot more than having a secondary piece of this be updated by the controller instead of inside the template. Definitely cleaner code this way.
@zachgoll I agree about the
toinside each template - that is a lot cleaner. When I was playing (since I had that PR that deleted all my tags) I found an edge case where no options for a specific thing (tags, merchants) was not a great UX, so I added a(none)disabled select box for that case, which matches the current drawer text when there is no tag to choose from.I also found a few weird cases where when converting from a select to a text where a selection has been chosen already and now you're editing that same field, the text box would get pre-filled with some kind of identifier, so
buildTextInputFornow clears the text.Let me know what you think!
Yeah I think these improvements and changes all make sense! Looks great!