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

Merged
merged 23 commits into from
Oct 8, 2024
Merged

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 b5448c1
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/67055d56dcb30800085450a0
😎 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.31 MB → 5.32 MB (+3.26 kB) +0.06%
Changeset
File Δ Size
src/icons/v1/Code.tsx 🆕 +448 B 0 B → 448 B
src/icons/v1/AlignLeft.tsx 🆕 +387 B 0 B → 387 B
src/components/rules/ActionExpression.tsx 📈 +375 B (+10.04%) 3.65 kB → 4.01 kB
src/hooks/useFeatureFlag.ts 📈 +27 B (+8.44%) 320 B → 347 B
src/components/settings/Experimental.tsx 📈 +316 B (+7.32%) 4.22 kB → 4.52 kB
src/components/modals/EditRuleModal.jsx 📈 +1.67 kB (+4.47%) 37.37 kB → 39.04 kB
home/runner/work/actual/actual/packages/loot-core/src/shared/rules.ts 📈 +73 B (+1.02%) 7.01 kB → 7.08 kB
View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

Asset File Size % Changed
static/js/index.js 3.33 MB → 3.34 MB (+3.26 kB) +0.10%

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/BackgroundImage.js 122.29 kB 0%
static/js/AppliedFilters.js 20.96 kB 0%
static/js/narrow.js 82.55 kB 0%
static/js/wide.js 224.88 kB 0%
static/js/ReportRouter.js 1.51 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.19 MB → 1.26 MB (+74.62 kB) +6.11%
Changeset
File Δ Size
node_modules/handlebars/dist/cjs/handlebars/compiler/parser.js 🆕 +200.84 kB 0 B → 200.84 kB
node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js 🆕 +118.53 kB 0 B → 118.53 kB
node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js 🆕 +56.75 kB 0 B → 56.75 kB
node_modules/handlebars/dist/cjs/handlebars/runtime.js 🆕 +43.72 kB 0 B → 43.72 kB
node_modules/handlebars/dist/cjs/handlebars/compiler/whitespace-control.js 🆕 +22.62 kB 0 B → 22.62 kB
node_modules/handlebars/dist/cjs/handlebars/compiler/helpers.js 🆕 +18.76 kB 0 B → 18.76 kB
node_modules/handlebars/dist/cjs/handlebars/compiler/code-gen.js 🆕 +15.51 kB 0 B → 15.51 kB
node_modules/handlebars/dist/cjs/handlebars/compiler/visitor.js 🆕 +12.33 kB 0 B → 12.33 kB
node_modules/handlebars/dist/cjs/handlebars/utils.js 🆕 +10.07 kB 0 B → 10.07 kB
node_modules/handlebars/dist/cjs/handlebars/helpers/each.js 🆕 +9.64 kB 0 B → 9.64 kB
node_modules/handlebars/dist/cjs/handlebars/base.js 🆕 +9.41 kB 0 B → 9.41 kB
node_modules/handlebars/dist/cjs/handlebars/internal/proto-access.js 🆕 +8.29 kB 0 B → 8.29 kB
node_modules/handlebars/dist/cjs/handlebars/exception.js 🆕 +5.81 kB 0 B → 5.81 kB
node_modules/handlebars/dist/cjs/handlebars.js 🆕 +4.79 kB 0 B → 4.79 kB
node_modules/handlebars/dist/cjs/handlebars.runtime.js 🆕 +4.5 kB 0 B → 4.5 kB
node_modules/handlebars/dist/cjs/handlebars/helpers/if.js 🆕 +4.35 kB 0 B → 4.35 kB
node_modules/handlebars/dist/cjs/handlebars/compiler/base.js 🆕 +3.83 kB 0 B → 3.83 kB
node_modules/handlebars/dist/cjs/handlebars/helpers.js 🆕 +3.82 kB 0 B → 3.82 kB
node_modules/handlebars/dist/cjs/handlebars/logger.js 🆕 +3.79 kB 0 B → 3.79 kB
node_modules/handlebars/dist/cjs/handlebars/helpers/block-helper-missing.js 🆕 +3.74 kB 0 B → 3.74 kB
node_modules/handlebars/dist/cjs/handlebars/helpers/with.js 🆕 +3.67 kB 0 B → 3.67 kB
node_modules/handlebars/dist/cjs/handlebars/compiler/ast.js 🆕 +3.34 kB 0 B → 3.34 kB
node_modules/handlebars/dist/cjs/handlebars/decorators/inline.js 🆕 +2.63 kB 0 B → 2.63 kB
node_modules/handlebars/dist/cjs/handlebars/helpers/log.js 🆕 +2.45 kB 0 B → 2.45 kB
node_modules/handlebars/dist/cjs/handlebars/no-conflict.js 🆕 +2.45 kB 0 B → 2.45 kB
node_modules/handlebars/dist/cjs/handlebars/internal/wrapHelper.js 🆕 +2.14 kB 0 B → 2.14 kB
node_modules/handlebars/dist/cjs/handlebars/helpers/helper-missing.js 🆕 +1.97 kB 0 B → 1.97 kB
node_modules/handlebars/dist/cjs/handlebars/internal/create-new-lookup-object.js 🆕 +1.74 kB 0 B → 1.74 kB
node_modules/handlebars/dist/cjs/handlebars/helpers/lookup.js 🆕 +1.21 kB 0 B → 1.21 kB
node_modules/handlebars/dist/cjs/handlebars/safe-string.js 🆕 +1.14 kB 0 B → 1.14 kB
node_modules/handlebars/dist/cjs/handlebars/decorators.js 🆕 +1000 B 0 B → 1000 B
packages/loot-core/src/server/accounts/rules.ts 📈 +3 kB (+9.99%) 30.08 kB → 33.08 kB
packages/loot-core/src/shared/rules.ts 📈 +83 B (+0.86%) 9.38 kB → 9.46 kB
View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

Asset File Size % Changed
kcab.worker.js 1.19 MB → 1.26 MB (+74.62 kB) +6.11%

Smaller

No assets were smaller

Unchanged

No assets were unchanged

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

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

UnderKoen commented Aug 23, 2024

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
  • debug any

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
Member

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
Member

youngcw commented Aug 26, 2024

Making this experimental would probably be good for now.

@youngcw
Copy link
Member

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
Member

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
Member

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

@matt-fidd
Copy link
Contributor

Opened tracking issue #3606 for experimental feedback

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: 2

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

Line range hint 553-575: Enhance error handling and type safety in the Action class.

The addition of Handlebars templating to the Action class is a good improvement. However, there are a couple of areas that could be enhanced:

  1. The error handling for invalid templates could be more informative.
  2. The options parameter lacks type specificity.

Consider the following improvements:

  1. Enhance error handling:
if (options?.template) {
  try {
    this.handlebarsTemplate = Handlebars.compile(options.template);
    this.handlebarsTemplate({});
  } catch (e) {
    throw new Error(`Invalid Handlebars template: ${e.message}`);
  }
}
  1. Improve type safety by defining an interface for the options:
interface ActionOptions {
  template?: string;
  // Add other option properties as needed
}

constructor(op: ActionOperator, field: string, value: any, options?: ActionOptions) {
  // ... existing code ...
}

These changes will provide more detailed error messages and improve type safety in the Action class.


1-1: Summary: Handlebars templating enhances rule flexibility, but consider improving error handling and type safety.

The introduction of Handlebars templating to the rules system is a significant enhancement that provides greater flexibility in defining actions. However, there are several areas where the implementation could be improved:

  1. Error handling in Handlebars helpers, particularly for math operations and regex usage.
  2. Type safety in the Action class constructor and options.
  3. Robustness in template execution and type conversion in the exec method.

Addressing these points will lead to a more robust and maintainable codebase. Consider implementing the suggested improvements in error handling, type safety, and data validation throughout the file.

As this change introduces more complexity to the rule system, consider the following architectural improvements:

  1. Create a separate module for Handlebars helpers to improve modularity.
  2. Implement a robust error handling and logging strategy throughout the rule execution process.
  3. Consider adding unit tests for the new templating functionality to ensure reliability.

These changes will help maintain the integrity of the rule system as it grows in complexity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

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

📒 Files selected for processing (3)
  • packages/desktop-client/src/components/settings/Experimental.tsx (1 hunks)
  • packages/loot-core/src/server/accounts/rules.test.ts (1 hunks)
  • packages/loot-core/src/server/accounts/rules.ts (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/loot-core/src/server/accounts/rules.test.ts
🧰 Additional context used
🔇 Additional comments (2)
packages/desktop-client/src/components/settings/Experimental.tsx (2)

Line range hint 1-138: Summary: New feature toggle successfully integrated

The changes in this file are focused and well-implemented. The new feature toggle for "Rule action templating" has been seamlessly integrated into the ExperimentalFeatures component, following the established patterns in the codebase. This addition aligns perfectly with the PR objectives and enhances the application's functionality by allowing users to enable or disable the templating feature for rule actions.

The implementation includes:

  1. Proper use of the FeatureToggle component
  2. Correct flag naming ("actionTemplating")
  3. An appropriate feedback link for user input

These changes contribute to the overall goal of introducing templating actions within rules, as described in the PR objectives. The code is clean, consistent, and ready for integration.


113-118: LGTM! The new feature toggle is correctly implemented.

The addition of the FeatureToggle for "Rule action templating" aligns well with the PR objectives. The implementation follows the established pattern in the file, and the feedback link correctly points to the relevant GitHub issue.

To ensure consistency across the codebase, let's verify the usage of this new feature flag:

This script will help us verify that the feature flag is properly defined, used consistently, and not hardcoded elsewhere in the codebase.

✅ Verification successful

Verification Successful!

The actionTemplating feature flag is correctly defined and utilized across the codebase. No unintended or duplicated references were found, ensuring consistent usage.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of the 'actionTemplating' feature flag across the codebase.

# Test 1: Check for the feature flag definition
echo "Checking for feature flag definition:"
rg --type typescript --type javascript "actionTemplating.*FeatureFlag"

# Test 2: Check for usage of the feature flag
echo "Checking for usage of the feature flag:"
rg --type typescript --type javascript "useFeatureFlag.*actionTemplating"

# Test 3: Check for any hardcoded references to the feature
echo "Checking for hardcoded references:"
rg --type typescript --type javascript "actionTemplating" -g '!**/Experimental.tsx'

Length of output: 590


Script:

#!/bin/bash
# Description: Verify the usage of the 'actionTemplating' feature flag across the codebase.

# Test 1: Check for the feature flag definition
echo "Checking for feature flag definition:"
rg "actionTemplating.*FeatureFlag" -g "*.ts" -g "*.tsx"

# Test 2: Check for usage of the feature flag
echo "Checking for usage of the feature flag:"
rg "useFeatureFlag.*actionTemplating" -g "*.ts" -g "*.tsx"

# Test 3: Check for any hardcoded references to the feature
echo "Checking for hardcoded references:"
rg "actionTemplating" -g "*.ts" -g "*.tsx" -g "!Experimental.tsx"

Length of output: 580

@@ -1,5 +1,6 @@
// @ts-strict-ignore
import * as dateFns from 'date-fns';
import * as Handlebars from 'handlebars';
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 enhancing error handling and security in Handlebars helpers.

The addition of Handlebars templating and helper functions is a good improvement for flexibility. However, there are a few points to consider:

  1. The mathHelper function doesn't handle potential NaN values, which could lead to unexpected results.
  2. The regex helper might be vulnerable to ReDoS (Regular Expression Denial of Service) attacks if user input is directly used.
  3. The debug helper logs to the console, which might not be ideal for production environments.

Consider the following improvements:

  1. Add NaN checking in the mathHelper:
function mathHelper(fn: (a: number, b: number) => number) {
  return (a: unknown, ...b: unknown[]) => {
    b.splice(-1, 1);
    const numbers = [a, ...b].map(Number);
    if (numbers.some(isNaN)) {
      throw new Error('Invalid number input');
    }
    return numbers.reduce(fn);
  };
}
  1. Implement a safeguard against complex regex patterns in the regex helper:
regex: (value: unknown, regex: unknown, replace: unknown) => {
  // ... existing checks ...
  if (regex.length > 100) { // Adjust the limit as needed
    throw new Error('Regex pattern too complex');
  }
  // ... rest of the function ...
}
  1. Consider replacing the debug helper with a more production-friendly logging mechanism or remove it entirely if not needed in production.

Also applies to: 34-88

Comment on lines +600 to +620
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.

🛠️ Refactor suggestion

Improve robustness in template execution and type conversion.

The modifications to the exec method to handle Handlebars templates are a good addition. However, there are a few areas that could be improved for better error handling and robustness:

  1. There's no error handling for failed type conversions.
  2. The type conversion switch statement could be more comprehensive.

Consider the following improvements:

  1. Add error handling for type conversions:
case 'number':
  const num = parseFloat(object[this.field]);
  if (isNaN(num)) {
    throw new Error(`Invalid number conversion 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 conversion for field ${this.field}`);
  }
  object[this.field] = date;
  break;
  1. Make the type conversion more robust:
switch (this.type) {
  case 'number':
    // ... (as above)
  case 'date':
    // ... (as above)
  case 'boolean':
    object[this.field] = object[this.field].toLowerCase() === 'true';
    break;
  case 'string':
    // Ensure it's a string
    object[this.field] = String(object[this.field]);
    break;
  default:
    throw new Error(`Unsupported type ${this.type} for field ${this.field}`);
}

These changes will make the exec method more robust and less prone to runtime errors.

Copy link
Contributor

@matt-fidd matt-fidd left a comment

Choose a reason for hiding this comment

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

Amazing, thank you for this! I'd like to get it merged in as is for now to release it into the hands of edge users and gather some feedback before the next release cycle.

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.

4 participants