Skip to content
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

feat(rules): templating actions #3305

Open
wants to merge 19 commits into
base: master
Choose a base branch
from

Conversation

UnderKoen
Copy link
Contributor

@UnderKoen UnderKoen commented Aug 22, 2024

Relates to #500 and #3378

chrome_jgqSYZmJON.mp4

@actual-github-bot actual-github-bot bot changed the title feat(rules): templating actions [WIP] feat(rules): templating actions Aug 22, 2024
Copy link

netlify bot commented Aug 22, 2024

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit 5f59aa5
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/66ffbd9591067500080b9e98
😎 Deploy Preview https://deploy-preview-3305.demo.actualbudget.org
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

github-actions bot commented Aug 22, 2024

Bundle Stats — desktop-client

Hey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle.

As this PR is updated, I'll keep you updated on how the bundle size is impacted.

Total

Files count Total bundle size % Changed
9 5.3 MB 0%

Changeset

No files were changed

View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

No assets were bigger

Smaller

No assets were smaller

Unchanged

Asset File Size % Changed
static/js/indexeddb-main-thread-worker-e59fee74.js 13.5 kB 0%
static/js/resize-observer.js 18.37 kB 0%
static/js/usePreviewTransactions.js 1.64 kB 0%
static/js/AppliedFilters.js 20.96 kB 0%
static/js/narrow.js 81.98 kB 0%
static/js/BackgroundImage.js 122.29 kB 0%
static/js/wide.js 225.26 kB 0%
static/js/ReportRouter.js 1.5 MB 0%
static/js/index.js 3.32 MB 0%

Copy link
Contributor

github-actions bot commented Aug 22, 2024

Bundle Stats — loot-core

Hey there, this message comes from a GitHub action that helps you and reviewers to understand how these changes affect the size of this project's bundle.

As this PR is updated, I'll keep you updated on how the bundle size is impacted.

Total

Files count Total bundle size % Changed
1 1.26 MB 0%

Changeset

No files were changed

View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

No assets were bigger

Smaller

No assets were smaller

Unchanged

Asset File Size % Changed
kcab.worker.js 1.26 MB 0%

@UnderKoen UnderKoen changed the title [WIP] feat(rules): templating actions feat(rules): templating actions Aug 22, 2024
@youngcw
Copy link
Contributor

youngcw commented Aug 23, 2024

Could you put in some more examples of what is possible with this? I see that you can pull in field values, can you pull in things like account balance?

@UnderKoen
Copy link
Contributor Author

You can currently only pull in information about the transaction. This is limited to anything what is currently possible with data available to rules. So not the name of the payee or name of the category. I saw an todo // TODO: Add matches op support for payees, accounts, categories. which woudl probaly also make this possible!

The main use case for this would currently be cleaning imported transactions. Or altering existing transactions.

Or changing the date of new income to the first of the month.
{{floor (add (year date) (div (month date) 12))}}-{{add (mod (month date) 12) 1}}-1

Is this now very verbose but we could add another helper for adding duration to dates.

The helpers I currently implemented are:

  • regex value regex replacement
  • add num1 num2 ...
  • sub num1 num2 ...
  • div num1 num2 ...
  • mul num1 num2 ...
  • mod num1 num2 ...
  • floor num
  • ceil num
  • round num
  • abs num
  • min num1 num2 ...
  • max num1 num2 ...
  • fixed num1 num2
  • day date
  • month date
  • year date
  • format date format

Values available:

  • today (date)
  • account (id)
  • amount (number)
  • cleared (boolean)
  • date (date)
  • error (?)
  • imported_id (id)
  • imported_payee (string)
  • is_child (boolean)
  • is_parent (boolean)
  • notes (string)
  • parent_id (id)
  • payee (id)
  • reconciled (boolean)
  • schedule (id)
  • sort_order (number)
  • starting_balance_flag (boolean)
  • tombstone (boolean)
  • transfer_id (id)

This list can be extended by adding more values at rules.ts#569 like how today is added.

@youngcw
Copy link
Contributor

youngcw commented Aug 24, 2024

There probably needs to be a syntax check in the rule edit window since its so easy to have bad syntax with something like this.

Are these supposed to run when manually creating a transaction? My testing doesn't seem to work in that case. I think its working for file imports though.

Im worried that there could be some issues with how the csv import checks for duplicates if someone has a templated rule. That will require more testing.

edit: So the csv importer can't see the matching transaction if there is a big change (change the amount sign for example), but the rule gets run before the real deduplication goes. Maybe we should look at moving the rules inside of the check that the csv import does for duplication matching.

@UnderKoen
Copy link
Contributor Author

UnderKoen commented Aug 26, 2024

@youngcw

There probably needs to be a syntax check in the rule edit window since its so easy to have bad syntax with something like this.

Will add

Are these supposed to run when manually creating a transaction? My testing doesn't seem to work in that case. I think its working for file imports though.

I just tested this and it has been working but not consistently, I think the rules are not always run?

Maybe we should look at moving the rules inside of the check that the csv import does for duplication matching.

Seems smart, I wouldn't have a clue how to achieve this. Could this be fixxed in an separatem PR and making this feature expiremental?

@youngcw
Copy link
Contributor

youngcw commented Aug 26, 2024

Making this experimental would probably be good for now.

@youngcw
Copy link
Contributor

youngcw commented Aug 26, 2024

  • tombstone (boolean)

Can this be set? or is it just available to use?

@UnderKoen
Copy link
Contributor Author

Can this be set? or is it just available to use?

I'm not sure, if it's possible to change it with current set action it is also possible with the templating.

Making this experimental would probably be good for now.

I will look how to do this!

@UnderKoen
Copy link
Contributor Author

@youngcw

Maybe we should look at moving the rules inside of the check that the csv import does for duplication matching.

Out of scope of this PR I think but wouldn't this be best solved with field like imported_payee? Just have imported_amount, imported_notes, etc.

@youngcw
Copy link
Contributor

youngcw commented Sep 4, 2024

Out of scope of this PR I think but wouldn't this be best solved with field like imported_payee? Just have imported_amount, imported_notes, etc.

I don't think that will fix matching if the amount gets changed

@UnderKoen
Copy link
Contributor Author

Im worried that there could be some issues with how the csv import checks for duplicates if someone has a templated rule. That will require more testing.

edit: So the csv importer can't see the matching transaction if there is a big change (change the amount sign for example), but the rule gets run before the real deduplication goes. Maybe we should look at moving the rules inside of the check that the csv import does for duplication matching.

I don't think I understand what the intented behaviour is and how to check the behaviour?

@youngcw
Copy link
Contributor

youngcw commented Sep 4, 2024

Im worried that there could be some issues with how the csv import checks for duplicates if someone has a templated rule. That will require more testing.

edit: So the csv importer can't see the matching transaction if there is a big change (change the amount sign for example), but the rule gets run before the real deduplication goes. Maybe we should look at moving the rules inside of the check that the csv import does for duplication matching.

I don't think I understand what the intented behaviour is and how to check the behaviour?

If you import a csv and then import the same csv again you should see that all the "new" transactions get matched with the existing transaction and shown to you in the import window to decide what to do. So compare that to if you have a rule that modifies the amount using a template rule and there wont be a match even though its the same transaction and will get deduplicated after the rules run.

@UnderKoen
Copy link
Contributor Author

Does this not work with the exported CSV's? I have no rules active and use the demo file
image

@Djurq
Copy link

Djurq commented Sep 9, 2024

This would be awesome!

@UnderKoen
Copy link
Contributor Author

@youngcw Any clue what i'm doing wrong, or missing?

@youngcw
Copy link
Contributor

youngcw commented Sep 10, 2024

with the CSV? Its not that the CSV import isn't working. The CSV importer shows which transactions are new and which ones match an existing transaction. If the rules change the amounts, for example, then the matching doesn't work since the transaction has changed too much.

@UnderKoen
Copy link
Contributor Author

@youngcw How does this look, when I import an CSV I don't see anywhere that it matches even with no rules active. After importing with and without rules there are no duplicates.

@youngcw
Copy link
Contributor

youngcw commented Sep 10, 2024

Import the same csv twice and you will see extra lines in the import window the second time. Those lines will show the matching

@UnderKoen
Copy link
Contributor Author

UnderKoen commented Sep 10, 2024

@youngcw I don't see any extra line? Or any diffent in the dialog.

2024-09-10 19 46 chrome_HV526F7C2X
2024-09-10 19 46 chrome_PVxZgABVCy

@youngcw
Copy link
Contributor

youngcw commented Sep 10, 2024

Hmm. Something must have broke. Im not seeing it anymore either.

@youngcw
Copy link
Contributor

youngcw commented Sep 10, 2024

https://deploy-preview-3179.demo.actualbudget.org/ This is the deploy for v24.8.0. It still works with the matching so you can see what it is supposed to be doing.

@youngcw
Copy link
Contributor

youngcw commented Sep 10, 2024

Its possible that this would work fine if the table wasn't messed up prior to this.

@UnderKoen
Copy link
Contributor Author

Ahha, thanks I really tough I was going crazy

Copy link
Contributor

coderabbitai bot commented Sep 17, 2024

Walkthrough

The changes introduce an action templating feature that allows users to define dynamic action options using a new feature toggle. The ActionEditor component is updated to support this templating, with conditional rendering based on the templating state. Additionally, the ExperimentalFeatures component includes a toggle for enabling or disabling action templating. The relevant type definitions are updated to accommodate the new feature flag, enhancing the overall functionality of the application.

Changes

Files Change Summary
packages/desktop-client/src/components/modals/EditRuleModal.jsx Enhanced ActionEditor with templating feature, added hooks and logic for toggling templates, and updated rendering based on the templating state.
packages/desktop-client/src/components/settings/Experimental.tsx Added a toggle for "Action templating" in the ExperimentalFeatures component to enable or disable the new functionality.
packages/loot-core/src/server/accounts/rules.ts Introduced Handlebars integration for action templating, added helpers registration, and modified the Action class to compile and execute templates.
packages/loot-core/src/shared/rules.ts Added error handling for 'invalid-template' in the getFieldError function.
packages/loot-core/src/types/models/rule.d.ts Updated SetRuleActionEntity interface to include an optional template property in the options object.
packages/loot-core/src/types/prefs.d.ts Updated FeatureFlag type definition to include actionTemplating and exclude spendingReport.

Possibly related PRs

Suggested labels

:sparkles: Merged

Suggested reviewers

  • MatissJanis

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Outside diff range and nitpick comments (1)
packages/loot-core/src/server/accounts/rules.ts (1)

585-591: Consider enhancing the template validation logic.

While validating the Handlebars template by executing it with an empty object {} is a good start, it may not cover all possible scenarios and catch all potential template errors.

Template errors that depend on specific object properties or complex logic within the template may not be detected by this validation approach. For example, if the template relies on certain properties being present in the object or performs conditional logic based on property values, executing it with an empty object may not trigger those specific error cases.

To improve the robustness of the template validation, consider the following suggestions:

  1. Use a more representative test object that includes common properties expected in the real usage scenario. This will help catch errors related to missing or invalid properties.
  2. Implement additional validation logic to check for common template issues, such as missing or invalid helpers, syntax errors, or invalid expressions.
  3. Consider using a template linting tool or library that specializes in validating Handlebars templates. These tools can provide more comprehensive checks and catch potential issues early in the development process.

By enhancing the template validation logic, you can improve the reliability and error handling of the Action class when using Handlebars templates.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 17fd068 and f08e5c3.

Files ignored due to path filters (2)
  • packages/loot-core/src/server/accounts/__snapshots__/transaction-rules.test.ts.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
Files selected for processing (10)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (4 hunks)
  • packages/desktop-client/src/components/rules/ActionExpression.tsx (1 hunks)
  • packages/desktop-client/src/components/settings/Experimental.tsx (1 hunks)
  • packages/desktop-client/src/hooks/useFeatureFlag.ts (1 hunks)
  • packages/loot-core/package.json (1 hunks)
  • packages/loot-core/src/server/accounts/rules.ts (6 hunks)
  • packages/loot-core/src/shared/rules.ts (1 hunks)
  • packages/loot-core/src/types/models/rule.d.ts (1 hunks)
  • packages/loot-core/src/types/prefs.d.ts (1 hunks)
  • upcoming-release-notes/3305.md (1 hunks)
Additional comments not posted (17)
upcoming-release-notes/3305.md (1)

1-6: LGTM!

The release notes provide a clear and concise description of the new rule action templating feature using Handlebars syntax. The categorization and authorship details are accurate.

The notes effectively communicate the core functionality of the feature, which allows users to manipulate transaction data based on predefined templates. The limitations and potential for future enhancements are also appropriately mentioned.

Overall, the release notes are well-written and informative.

packages/desktop-client/src/hooks/useFeatureFlag.ts (1)

11-11: LGTM!

The addition of the actionTemplating feature flag to the DEFAULT_FEATURE_FLAG_STATE object is a valid way to introduce a new toggleable feature. Setting the default value to false ensures that the feature is disabled by default, providing a safe and controlled rollout mechanism.

packages/loot-core/src/types/prefs.d.ts (1)

8-9: LGTM!

The addition of the 'actionTemplating' feature flag to the FeatureFlag type is a straightforward change that enables the use of this flag in the codebase. It does not introduce any apparent issues or affect the existing feature flags.

packages/loot-core/package.json (1)

31-31: Looks good!

The addition of the handlebars dependency at version ^4.7.8 is a reasonable choice for introducing templating functionality to the project. Handlebars is a well-established and widely-used templating engine that can improve code maintainability and reusability by separating the presentation logic from the application logic.

The version constraint follows semantic versioning and allows for minor and patch updates, ensuring compatibility with future releases.

packages/loot-core/src/types/models/rule.d.ts (1)

139-139: LGTM!

The addition of the optional template property to the options object within the SetRuleActionEntity interface is a good enhancement. It expands the capabilities of the interface by allowing the inclusion of a template string, which could be used for various purposes such as formatting or defining a structure for the value being set.

This change maintains backward compatibility as the property is marked as optional, ensuring existing functionality is not affected.

packages/desktop-client/src/components/rules/ActionExpression.tsx (1)

74-81: LGTM!

The conditional rendering logic for handling the options.template case is implemented correctly. It enhances the component's flexibility by allowing it to display a template value when available while maintaining backward compatibility with the existing Value component rendering.

The use of the optional chaining operator ?. ensures safe access to the template property, and the rendering of the Text elements follows the existing style conventions.

packages/desktop-client/src/components/settings/Experimental.tsx (1)

119-121: Please provide more information about the "actionTemplating" feature.

The PR objectives and AI-generated summary do not mention the "actionTemplating" feature flag. It would be helpful to understand:

  1. What is the purpose of the "actionTemplating" feature?
  2. How does it affect the application's behavior?
  3. What are the potential risks or benefits associated with this feature?

Providing this information will help reviewers better understand the context and impact of this change.

packages/loot-core/src/shared/rules.ts (1)

195-196: LGTM!

The addition of the 'invalid-template' case in the getFieldError function enhances the error handling capabilities by providing a specific error message for invalid handlebars templates. This change improves the user experience and the overall robustness of the error management system.

packages/loot-core/src/server/accounts/rules.ts (4)

29-78: LGTM!

The code segment registers various Handlebars helpers for mathematical operations, date formatting, and regex matching. The use of an IIFE is a good practice to avoid polluting the global scope.

The mathHelper higher-order function is a nice abstraction for defining mathematical operation helpers. It reduces code duplication and makes it easy to add new mathematical helpers in the future.

The helpers object is well-structured and contains a good set of helper functions for common use cases.

Overall, the code is clean, modular, and follows best practices.


Line range hint 571-592: LGTM!

The addition of the handlebarsTemplate property to the Action class and the corresponding changes in the constructor to initialize and validate the template are well-implemented.

The use of Handlebars.compile to compile the template and store it as a TemplateDelegate is a good approach. It allows for efficient execution of the template later on.

The validation of the template by attempting to execute it with an empty object is a nice touch. It ensures that the template is valid and will not throw errors during runtime.

The use of RuleError to throw a specific error message when the template is invalid is also a good practice. It provides clear feedback to the user about the issue.

Overall, the code changes are solid and enhance the functionality of the Action class.


617-637: LGTM!

The modifications to the exec method of the Action class to support executing Handlebars templates are well-implemented and provide a powerful feature.

The code correctly checks for the existence of the handlebarsTemplate property and executes the template with the object spread into the template context. This allows for dynamic value computation based on the object's properties.

The addition of the today property to the template context is a nice touch, providing access to the current day within the template.

The conversion of the computed value to the appropriate type based on the action's type is handled correctly. The code uses parseFloat for numbers, parseDate for dates, and a comparison with the string "true" for booleans.

Falling back to assigning the original value directly if no template is provided ensures backward compatibility and flexibility.

Overall, the code changes enhance the functionality of the Action class and provide a powerful templating mechanism for dynamic value computation.


623-634: LGTM!

The conversion of the computed value from the Handlebars template to the appropriate type based on the action's type is handled correctly in the exec method of the Action class.

The code uses appropriate methods and comparisons for each type:

  • For numbers, parseFloat is used to parse the string value into a floating-point number. This ensures that the resulting value is a valid number.
  • For dates, the parseDate function is used to parse the string value into a date object. This function likely handles the parsing logic specific to the date format used in the application.
  • For booleans, the code compares the string value with the string "true" to determine its boolean value. This comparison is sufficient to convert the string to a boolean value.

The type-specific conversion logic ensures that the computed value is properly converted to the expected type before being assigned to the specified field in the object.

Overall, the code changes are accurate and handle the type conversion correctly based on the action's type.

packages/desktop-client/src/components/modals/EditRuleModal.jsx (5)

39-39: LGTM!

The useFeatureFlag hook import looks good.


42-42: LGTM!

The new icon component imports look good.


368-371: LGTM!

The templated and isTemplatingEnabled variables are correctly initialized based on the options.template property and the actionTemplating feature flag.


393-423: LGTM!

The conditional rendering logic for the GenericInput component's type prop and the templating button is implemented correctly. The exclusion of certain fields from having templating options is also handled properly.


849-863: LGTM!

The logic for updating the options.template property based on the template field is implemented correctly. Clearing the value when disabling templating for non-string types and updating the value to match the options.template value are good practices to maintain consistency.

@UnderKoen
Copy link
Contributor Author

@youngcw it seems that the templating rules work with the CSV import. Could you confirm?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Outside diff range and nitpick comments (1)
packages/desktop-client/src/components/modals/EditRuleModal.jsx (1)

403-403: Fix typo in comment

There's a minor typo in the comment. "Due to that" should be "Due to the fact that" or simply "Because".

Consider updating the comment to:

-          {/*Due to that these fields have id's as value it is not helpful to have templating here*/}
+          {/* Because these fields have IDs as values, it is not helpful to have templating here */}
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between e41a05c and 7c7d84e.

Files selected for processing (3)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (4 hunks)
  • packages/desktop-client/src/components/settings/Experimental.tsx (1 hunks)
  • packages/loot-core/src/types/prefs.d.ts (1 hunks)
Files skipped from review as they are similar to previous changes (2)
  • packages/desktop-client/src/components/settings/Experimental.tsx
  • packages/loot-core/src/types/prefs.d.ts
Additional comments not posted (4)
packages/desktop-client/src/components/modals/EditRuleModal.jsx (4)

39-39: LGTM: New imports for templating feature

The new imports for useFeatureFlag and SVG icons are appropriate for the templating feature being implemented. These additions enhance the functionality and user interface of the component.

Also applies to: 42-42


Line range hint 371-426: LGTM: Templating feature implementation

The implementation of the templating feature in the ActionEditor component looks good. It correctly handles the toggling between templated and non-templated inputs, and provides a clear UI for users to switch between modes. The use of feature flags and conditional rendering ensures backwards compatibility.


852-876: LGTM: Templating handling in onChangeAction

The updates to the onChangeAction function properly handle the templating feature. The code correctly manages the template state when actions are modified, including resetting the template when the field or operation is changed. This implementation ensures consistency and prevents potential issues with mismatched templates.


Line range hint 1-1337: Summary: Solid implementation of templating feature

The changes in this file successfully implement a templating feature for action rules. Key points:

  1. New imports for feature flags and icons support the implementation.
  2. The ActionEditor component now handles templated inputs with a toggle UI.
  3. The EditRuleModal component's logic has been updated to manage templated actions correctly.

These changes enhance the flexibility of the rule system while maintaining backwards compatibility. The code is well-structured and follows React best practices.

Consider addressing the minor typo in the comment as noted earlier. Otherwise, this implementation is ready for production.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (5)
packages/loot-core/src/types/models/rule.d.ts (1)

141-141: LGTM! Consider adding documentation for the new template property.

The addition of the optional template property to the SetRuleActionEntity interface aligns well with the PR objectives of introducing templating actions within rules. This change enhances the flexibility of rule actions without breaking existing functionality.

Consider adding a brief comment to document the purpose and expected format of the template property. This will help other developers understand how to use this new feature correctly.

Example:

options?: {
  /** 
   * A string template for dynamic action options.
   * @example "Transaction amount: {{amount}}"
   */
  template?: string;
  splitIndex?: number;
};
packages/loot-core/src/shared/rules.ts (1)

222-223: LGTM! Consider a minor improvement for consistency.

The addition of the 'invalid-template' case is a good extension to the error handling capabilities, aligning well with the new templating feature. The error message is clear and descriptive.

For consistency with other error messages in this function, consider capitalizing the first letter of each word in the error message:

-      return 'Invalid handlebars template';
+      return 'Invalid Handlebars Template';
packages/desktop-client/src/components/modals/EditRuleModal.jsx (3)

374-376: Clarify the comment regarding templating feature flag behavior.

The comment on line 374 could be clearer:

// Even if the feature flag is disabled, we still want to be able to turn off templating

Consider rephrasing to:

// Even if the feature flag is disabled, we still want to allow disabling templating if previously enabled

This clarifies that templating can be toggled off even when the feature flag is disabled.


404-404: Improve comment clarity and grammar.

The comment on line 404 can be rephrased for better readability:

// Since these fields have IDs as values, it is not helpful to enable templating here

This improves clarity and ensures the intent is clearly communicated.


405-427: Use Array.includes() for better readability.

In the condition on line 406, consider using includes for improved readability:

{isTemplatingEnabled &&
  !['payee', 'category', 'account'].includes(field) && (
    <Button>
      {/* Button content */}
    </Button>
)}

Using includes makes the code more readable and aligns with modern JavaScript practices.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7c7d84e and 659961f.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • packages/desktop-client/src/components/modals/EditRuleModal.jsx (4 hunks)
  • packages/loot-core/src/server/accounts/rules.ts (6 hunks)
  • packages/loot-core/src/shared/rules.ts (1 hunks)
  • packages/loot-core/src/types/models/rule.d.ts (1 hunks)
  • packages/loot-core/src/types/prefs.d.ts (1 hunks)
🔇 Additional comments (8)
packages/loot-core/src/types/prefs.d.ts (1)

5-6: LGTM! Verify removal of 'spendingReport' across the codebase.

The changes to the FeatureFlag type align with the PR objectives by adding 'actionTemplating'. However, the removal of 'spendingReport' requires attention:

  1. Ensure that 'spendingReport' is no longer used anywhere in the codebase.
  2. Update any relevant documentation that might reference the removed feature flag.

To verify the removal of 'spendingReport', run the following script:

If any results are found, please review and update the corresponding code to remove references to the 'spendingReport' feature flag.

packages/desktop-client/src/components/modals/EditRuleModal.jsx (6)

40-40: Imports are appropriate and necessary.

The added imports for useFeatureFlag and the new icons (SvgAlignLeft, SvgCode, SvgInformationOutline) are correctly included and used in the code.

Also applies to: 43-43


372-373: Variable templated is correctly initialized.

The templated variable accurately reflects whether the options.template property is defined, which is essential for controlling the templating behavior.


397-397: Adjust input type and value based on templating.

The GenericInput component correctly adjusts the type and value properties when templated is true. This ensures that the input handles templated strings appropriately.

Also applies to: 399-399


854-863: Ensure a.value is properly handled when toggling templating.

When the 'template' field changes, setting a.value to null if a.type !== 'string' may have unintended effects if a.value is required elsewhere. Verify that a.value being null in these cases does not lead to errors or unexpected behavior.


866-868: Synchronize options.template with value when necessary.

When a.options.template is defined, updating it with the new value ensures consistency between the template and the action value.


878-878: Reset options.template when field or operation changes.

The code appropriately resets a.options.template to undefined when the field or operation changes. This prevents stale templates from persisting and ensures that the action reflects the current field and operation.

packages/loot-core/src/server/accounts/rules.ts (1)

3-3: Handlebars module imported correctly.

The import statement for the Handlebars library is appropriate and necessary for adding templating functionality.

packages/loot-core/src/server/accounts/rules.ts Outdated Show resolved Hide resolved
packages/loot-core/src/server/accounts/rules.ts Outdated Show resolved Hide resolved
packages/loot-core/src/server/accounts/rules.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (2)
packages/loot-core/src/server/accounts/rules.ts (1)

564-572: Improve error handling in Handlebars template compilation.

While the addition of Handlebars template compilation is a good feature, the error handling could be enhanced. Instead of using console.debug and a generic assertion, consider throwing a more informative error that includes details from the caught exception.

Consider applying this change:

 if (options?.template) {
   this.handlebarsTemplate = Handlebars.compile(options.template);
   try {
     this.handlebarsTemplate({});
   } catch (e) {
-    console.debug(e);
-    assert(false, 'invalid-template', `Invalid Handlebars template`);
+    throw new Error(`Invalid Handlebars template: ${e.message}`);
   }
 }
packages/loot-core/src/server/accounts/rules.test.ts (1)

323-323: Fix the typo in the template string

In line 323, 'payed' should be 'paid' in the template string.

Apply this diff to correct the typo:

-            template: 'Hey {{notes}}! You just payed {{amount}}',
+            template: 'Hey {{notes}}! You just paid {{amount}}',
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 659961f and 5f59aa5.

📒 Files selected for processing (2)
  • packages/loot-core/src/server/accounts/rules.test.ts (1 hunks)
  • packages/loot-core/src/server/accounts/rules.ts (6 hunks)
🔇 Additional comments (4)
packages/loot-core/src/server/accounts/rules.ts (4)

3-3: LGTM: Handlebars import added correctly.

The Handlebars library is imported properly, allowing for template compilation and usage throughout the file.


34-85: LGTM: Comprehensive Handlebars helpers implemented.

The registerHandlebarsHelpers function provides a wide range of useful helpers for mathematical operations, string manipulation, and date formatting. The implementation is well-structured and should enhance the templating capabilities significantly.


46-61: ⚠️ Potential issue

Security concern: Potential ReDoS vulnerability in regex helper.

The regex helper constructs regular expressions from user-provided input, which could lead to Regular Expression Denial of Service (ReDoS) attacks if not properly validated. Consider implementing safeguards to limit the complexity of user-provided regex patterns.

To verify the potential impact, let's search for usages of this helper:

#!/bin/bash
# Search for usage of the 'regex' helper in Handlebars templates
rg --type typescript 'regex\s*=' -A 3 -B 3

Line range hint 1-1441: Overall: Significant enhancement to rules system with Handlebars templating.

The introduction of Handlebars templating to the rules system greatly increases its flexibility and power. Users can now create more dynamic rules based on transaction data. However, with this increased power comes increased complexity and potential for errors.

Recommendations:

  1. Implement comprehensive unit tests for the new templating functionality, especially edge cases and error scenarios.
  2. Consider adding a "dry run" feature for rules, allowing users to test their templates safely before applying them to real data.
  3. Update user documentation to explain the new templating features and provide examples of safe and effective usage.

To ensure proper test coverage, let's check for existing tests related to the Action class:

✅ Verification successful

Action Required: Verify Coverage for the Action Class Tests

The previous attempt to locate test files for the Action class encountered issues. To ensure comprehensive test coverage, please execute the following shell scripts:

[/run_scripts]


Test Coverage for the Action Class

The search results indicate that the Action class is adequately covered by tests in accounts/rules.test.ts. This suggests that the new Handlebars templating functionality has corresponding test cases ensuring its correct behavior.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for test files that might cover the Action class
rg --type typescript 'describe.*Action' -g '*test*'

Length of output: 173


Script:

#!/bin/bash
# Search for test files that cover the Action class by looking for describe blocks containing 'Action'
rg --glob '*.test.ts' --glob '*.spec.ts' 'describe\s*\(\s*[\'"]Action[\'"]\s*,'

Length of output: 93


Script:

#!/bin/bash
# Alternatively, search for any references to the Action class within test files
rg --glob '*.test.ts' --glob '*.spec.ts' 'Action' -A 5 -B 5

Length of output: 9199

Comment on lines +597 to +617
if (this.handlebarsTemplate) {
object[this.field] = this.handlebarsTemplate({
...object,
today: currentDay(),
});

// Handlebars always returns a string, so we need to convert
switch (this.type) {
case 'number':
object[this.field] = parseFloat(object[this.field]);
break;
case 'date':
object[this.field] = parseDate(object[this.field]);
break;
case 'boolean':
object[this.field] = object[this.field] === 'true';
break;
}
} else {
object[this.field] = this.value;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for type conversions after template execution.

The current implementation doesn't handle potential errors in type conversion, such as NaN for numbers or invalid dates. Consider adding checks to ensure the converted values are valid.

Apply this diff to include conversion checks:

 if (this.handlebarsTemplate) {
   object[this.field] = this.handlebarsTemplate({
     ...object,
     today: currentDay(),
   });

   // Handlebars always returns a string, so we need to convert
   switch (this.type) {
     case 'number':
-      object[this.field] = parseFloat(object[this.field]);
+      const num = parseFloat(object[this.field]);
+      if (isNaN(num)) {
+        throw new Error(`Invalid number result from template for field ${this.field}`);
+      }
+      object[this.field] = num;
       break;
     case 'date':
-      object[this.field] = parseDate(object[this.field]);
+      const date = parseDate(object[this.field]);
+      if (isNaN(date.getTime())) {
+        throw new Error(`Invalid date result from template for field ${this.field}`);
+      }
+      object[this.field] = date;
       break;
     case 'boolean':
       object[this.field] = object[this.field] === 'true';
       break;
   }
 } else {
   object[this.field] = this.value;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (this.handlebarsTemplate) {
object[this.field] = this.handlebarsTemplate({
...object,
today: currentDay(),
});
// Handlebars always returns a string, so we need to convert
switch (this.type) {
case 'number':
object[this.field] = parseFloat(object[this.field]);
break;
case 'date':
object[this.field] = parseDate(object[this.field]);
break;
case 'boolean':
object[this.field] = object[this.field] === 'true';
break;
}
} else {
object[this.field] = this.value;
}
if (this.handlebarsTemplate) {
object[this.field] = this.handlebarsTemplate({
...object,
today: currentDay(),
});
// Handlebars always returns a string, so we need to convert
switch (this.type) {
case 'number':
const num = parseFloat(object[this.field]);
if (isNaN(num)) {
throw new Error(`Invalid number result from template for field ${this.field}`);
}
object[this.field] = num;
break;
case 'date':
const date = parseDate(object[this.field]);
if (isNaN(date.getTime())) {
throw new Error(`Invalid date result from template for field ${this.field}`);
}
object[this.field] = date;
break;
case 'boolean':
object[this.field] = object[this.field] === 'true';
break;
}
} else {
object[this.field] = this.value;
}

Comment on lines +350 to +383
describe('math helpers', () => {
function testHelper(
template: string,
expected: unknown,
field = 'amount',
) {
test(template, () => {
const action = new Action('set', field, '', { template });
const item = { [field]: 10 };
action.exec(item);
expect(item[field]).toBe(expected);
});
}

testHelper('{{add amount 5}}', 15);
testHelper('{{add amount 5 10}}', 25);
testHelper('{{sub amount 5}}', 5);
testHelper('{{sub amount 5 10}}', -5);
testHelper('{{mul amount 5}}', 50);
testHelper('{{mul amount 5 10}}', 500);
testHelper('{{div amount 5}}', 2);
testHelper('{{div amount 5 10}}', 0.2);
testHelper('{{mod amount 3}}', 1);
testHelper('{{mod amount 6 5}}', 4);
testHelper('{{floor (div amount 3)}}', 3);
testHelper('{{ceil (div amount 3)}}', 4);
testHelper('{{round (div amount 3)}}', 3);
testHelper('{{round (div amount 4)}}', 3);
testHelper('{{abs -5}}', 5);
testHelper('{{abs 5}}', 5);
testHelper('{{min amount 5 500}}', 5);
testHelper('{{max amount 5 500}}', 500);
testHelper('{{fixed (div 10 4) 2}}', '2.50', 'notes');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding tests for edge cases in math helpers

The current tests for math helpers cover standard arithmetic operations. To ensure robustness, consider adding tests for edge cases such as:

  • Division by zero.
  • Operations with negative numbers.
  • Non-numeric input handling.
  • Large number computations.

Comment on lines +385 to +399
describe('date helpers', () => {
function testHelper(template: string, expected: unknown) {
test(template, () => {
const action = new Action('set', 'notes', '', { template });
const item = { notes: '' };
action.exec(item);
expect(item.notes).toBe(expected);
});
}

testHelper('{{day "2002-07-25"}}', '25');
testHelper('{{month "2002-07-25"}}', '7');
testHelper('{{year "2002-07-25"}}', '2002');
testHelper('{{format "2002-07-25" "MM yyyy d"}}', '07 2002 25');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Expand tests for date helpers to cover invalid inputs

The date helper tests handle valid date strings effectively. To enhance reliability, consider adding tests for:

  • Invalid date formats (e.g., '2020-13-01', 'invalid-date').
  • Null or undefined inputs.
  • Edge cases like leap years and end-of-month dates.

Comment on lines +331 to +338
function testHelper(template: string, expected: unknown) {
test(template, () => {
const action = new Action('set', 'notes', '', { template });
const item = { notes: 'Sarah Condition' };
action.exec(item);
expect(item.notes).toBe(expected);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor test helper functions to reduce code duplication

The testHelper functions in the 'regex helper' (lines 331-338), 'math helpers' (lines 351-362), and 'date helpers' (lines 386-393) describe blocks share similar structures. Refactoring these into a single reusable function or abstracting common logic can reduce code duplication and improve maintainability.

Also applies to: 351-362, 386-393

}

testHelper('{{regex notes "/[aeuio]/g" "a"}}', 'Sarah Candataan');
testHelper('{{regex notes "/[aeuio]/" ""}}', 'Srah Condition');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct the expected result in the regex helper test

In line 341, the test aims to remove all vowels from 'Sarah Condition' by replacing [aeuio] with an empty string. The expected result is currently 'Srah Condition', but removing vowels should result in 'Srh Cndtn'.

Apply this diff to correct the expected result:

-          testHelper('{{regex notes "/[aeuio]/" ""}}', 'Srah Condition');
+          testHelper('{{regex notes "/[aeuio]/" ""}}', 'Srh Cndtn');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
testHelper('{{regex notes "/[aeuio]/" ""}}', 'Srah Condition');
testHelper('{{regex notes "/[aeuio]/" ""}}', 'Srh Cndtn');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants