From 646d0d90a437e5854d29cccfb8d3567bfd7361d5 Mon Sep 17 00:00:00 2001 From: Jed Fox <git@jedfox.com> Date: Tue, 2 May 2023 14:08:05 -0400 Subject: [PATCH] Remove unused payee rules feature (#985) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #615. I would appreciate double-checking that I didn’t accidentally delete anything that is important. Since I’m removing the related API methods, this is technically a breaking change (even if people would have no reason to remove this stuff), so we should probably do a major release of the API package. --- packages/api/methods.js | 16 --- .../components/payees/ManagePayeesWithData.js | 6 +- .../1682974838138_remove_payee_rules.sql | 5 + .../server/accounts/transaction-rules.test.ts | 59 -------- .../src/server/accounts/transaction-rules.ts | 128 +----------------- packages/loot-core/src/server/api-models.ts | 13 -- packages/loot-core/src/server/api.ts | 31 ----- packages/loot-core/src/server/db/index.ts | 28 ---- packages/loot-core/src/server/main.ts | 32 ----- packages/loot-core/src/server/models.ts | 18 --- .../src/server/sync/sync.property.test.ts | 6 - .../loot-core/src/types/api.handlers.d.ts | 8 -- .../loot-core/src/types/main.handlers.d.ts | 8 -- upcoming-release-notes/985.md | 6 + 14 files changed, 13 insertions(+), 351 deletions(-) create mode 100644 packages/loot-core/migrations/1682974838138_remove_payee_rules.sql create mode 100644 upcoming-release-notes/985.md diff --git a/packages/api/methods.js b/packages/api/methods.js index ccad98af6..90cda1ae2 100644 --- a/packages/api/methods.js +++ b/packages/api/methods.js @@ -149,19 +149,3 @@ export function updatePayee(id, fields) { export function deletePayee(id) { return send('api/payee-delete', { id }); } - -export function getPayeeRules(payeeId) { - return send('api/payee-rules-get', { payeeId }); -} - -export function createPayeeRule(payeeId, rule) { - return send('api/payee-rule-create', { payee_id: payeeId, rule }); -} - -export function updatePayeeRule(id, fields) { - return send('api/payee-rule-update', { id, fields }); -} - -export function deletePayeeRule(id) { - return send('api/payee-rule-delete', { id }); -} diff --git a/packages/desktop-client/src/components/payees/ManagePayeesWithData.js b/packages/desktop-client/src/components/payees/ManagePayeesWithData.js index d02ffd359..6c86e2cc3 100644 --- a/packages/desktop-client/src/components/payees/ManagePayeesWithData.js +++ b/packages/desktop-client/src/components/payees/ManagePayeesWithData.js @@ -58,11 +58,7 @@ function ManagePayeesWithData({ }, []); async function onUndo({ tables, messages, meta, url }, scroll = false) { - if ( - !tables.includes('payees') && - !tables.includes('payee_mapping') && - !tables.includes('payee_rules') - ) { + if (!tables.includes('payees') && !tables.includes('payee_mapping')) { return; } diff --git a/packages/loot-core/migrations/1682974838138_remove_payee_rules.sql b/packages/loot-core/migrations/1682974838138_remove_payee_rules.sql new file mode 100644 index 000000000..070aecd53 --- /dev/null +++ b/packages/loot-core/migrations/1682974838138_remove_payee_rules.sql @@ -0,0 +1,5 @@ +BEGIN TRANSACTION; + +DROP TABLE payee_rules; + +COMMIT; diff --git a/packages/loot-core/src/server/accounts/transaction-rules.test.ts b/packages/loot-core/src/server/accounts/transaction-rules.test.ts index c3fc3a40c..50211264f 100644 --- a/packages/loot-core/src/server/accounts/transaction-rules.test.ts +++ b/packages/loot-core/src/server/accounts/transaction-rules.test.ts @@ -15,7 +15,6 @@ import { resetState, getProbableCategory, updateCategoryRules, - migrateOldRules, } from './transaction-rules'; // TODO: write tests to make sure payee renaming is "pre" and category @@ -368,64 +367,6 @@ describe('Transaction rules', () => { }); }); - test('migrating from the old payee rules works', async () => { - await loadRules(); - let categoryGroupId = await db.insertCategoryGroup({ name: 'general' }); - let categoryId = await db.insertCategory({ - name: 'food', - cat_group: categoryGroupId, - }); - let krogerId = await db.insertPayee({ name: 'kroger' }); - let lowesId = await db.insertPayee({ name: 'lowes', category: categoryId }); - - await db.insertPayeeRule({ - payee_id: krogerId, - type: 'contains', - value: 'kroger', - }); - await db.insertPayeeRule({ - payee_id: lowesId, - type: 'equals', - value: '123 lowes', - }); - await db.insertPayeeRule({ - payee_id: lowesId, - type: 'equals', - value: 'lowes 456', - }); - - // Migrate! - await migrateOldRules(); - - expect(getRules().length).toBe(3); - - expect(runRules({ payee: null, imported_payee: '123 lowes' })).toEqual({ - category: categoryId, - imported_payee: '123 lowes', - payee: lowesId, - }); - expect(runRules({ payee: null, imported_payee: 'lowes 456' })).toEqual({ - category: categoryId, - imported_payee: 'lowes 456', - payee: lowesId, - }); - expect(runRules({ payee: null, imported_payee: '1 lowes 2' })).toEqual({ - imported_payee: '1 lowes 2', - payee: null, - }); - expect( - runRules({ - payee: null, - imported_payee: 'blah blah kroger bla', - category: null, - }), - ).toEqual({ - imported_payee: 'blah blah kroger bla', - payee: krogerId, - category: null, - }); - }); - test('transactions can be queried by rule', async () => { await loadRules(); let account = await db.insertAccount({ name: 'bank' }); diff --git a/packages/loot-core/src/server/accounts/transaction-rules.ts b/packages/loot-core/src/server/accounts/transaction-rules.ts index d1f3602cc..46c7c5593 100644 --- a/packages/loot-core/src/server/accounts/transaction-rules.ts +++ b/packages/loot-core/src/server/accounts/transaction-rules.ts @@ -16,7 +16,7 @@ import * as db from '../db'; import { getMappings } from '../db/mappings'; import { RuleError } from '../errors'; import { requiredFields, toDateRepr } from '../models'; -import { setSyncingMode, batchMessages } from '../sync'; +import { batchMessages } from '../sync'; import { addSyncListener } from '../sync/index'; import { @@ -712,129 +712,3 @@ export async function updateCategoryRules(transactions) { } }); } - -// This can be removed in the future -export async function migrateOldRules() { - let allPayees = await db.all( - `SELECT p.*, c.id as category FROM payees p - LEFT JOIN category_mapping cm ON cm.id = p.category - LEFT JOIN categories c ON (c.id = cm.transferId AND c.tombstone = 0) - WHERE p.tombstone = 0 AND transfer_acct IS NULL`, - ); - let allRules = await db.all( - `SELECT pr.*, pm.targetId as payee_id FROM payee_rules pr - LEFT JOIN payee_mapping pm ON pm.id = pr.payee_id - WHERE pr.tombstone = 0`, - ); - - let payeesById = new Map(); - for (let i = 0; i < allPayees.length; i++) { - payeesById.set(allPayees[i].id, allPayees[i]); - } - - let rulesByPayeeId = new Map(); - for (let i = 0; i < allRules.length; i++) { - let item = allRules[i]; - let rules = rulesByPayeeId.get(item.payee_id) || []; - rules.push(item); - rulesByPayeeId.set(item.payee_id, rules); - } - - let rules = []; - - // Convert payee name rules - for (let [payeeId, payeeRules] of rulesByPayeeId.entries()) { - let equals = payeeRules.filter(r => { - let payee = payeesById.get(r.payee_id); - - return ( - (r.type === 'equals' || r.type == null) && - (!payee || r.value.toLowerCase() !== payee.name.toLowerCase()) - ); - }); - let contains = payeeRules.filter(r => r.type === 'contains'); - let actions = [{ op: 'set', field: 'payee', value: payeeId }]; - - if (equals.length > 0) { - rules.push({ - stage: null, - conditionsOp: 'and', - conditions: [ - { - op: 'oneOf', - field: 'imported_payee', - value: equals.map(payeeRule => payeeRule.value), - }, - ], - actions, - }); - } - - if (contains.length > 0) { - rules = rules.concat( - contains.map(payeeRule => ({ - stage: null, - conditionsOp: 'and', - conditions: [ - { - op: 'contains', - field: 'imported_payee', - value: payeeRule.value, - }, - ], - actions, - })), - ); - } - } - - // Convert category rules - let catRules = allPayees - .filter(p => p.category) - .reduce((map, payee) => { - let ids = map.get(payee.category) || new Set(); - ids.add(payee.id); - map.set(payee.category, ids); - return map; - }, new Map()); - - for (let [catId, payeeIds] of catRules) { - rules.push({ - stage: null, - conditionsOp: 'and', - conditions: [ - { - op: 'oneOf', - field: 'payee', - value: [...payeeIds], - }, - ], - actions: [ - { - op: 'set', - field: 'category', - value: catId, - }, - ], - }); - } - - // Very important: we never want to sync migration changes, but it - // still has to run through the syncing layer to make sure - // projections are correct. This is only OK because we require a - // sync reset after this. - let prevMode = setSyncingMode('disabled'); - await batchMessages(async () => { - for (let rule of rules) { - await insertRule({ - stage: rule.stage, - conditionsOp: rule.conditionsOp, - conditions: rule.conditions, - actions: rule.actions, - }); - } - - await db.runQuery('DELETE FROM payee_rules', []); - }); - setSyncingMode(prevMode); -} diff --git a/packages/loot-core/src/server/api-models.ts b/packages/loot-core/src/server/api-models.ts index ea1da3d13..482a527a7 100644 --- a/packages/loot-core/src/server/api-models.ts +++ b/packages/loot-core/src/server/api-models.ts @@ -167,16 +167,3 @@ export const payeeModel = { return payee; }, }; - -export const payeeRuleModel = { - ...models.payeeRuleModel, - - toExternal(rule) { - let { tombstone, ...result } = rule; - return result; - }, - - fromExternal(rule) { - return rule; - }, -}; diff --git a/packages/loot-core/src/server/api.ts b/packages/loot-core/src/server/api.ts index 4c73f5419..fc2c3c4ab 100644 --- a/packages/loot-core/src/server/api.ts +++ b/packages/loot-core/src/server/api.ts @@ -19,7 +19,6 @@ import { categoryModel, categoryGroupModel, payeeModel, - payeeRuleModel, } from './api-models'; import { runQuery as aqlQuery } from './aql'; import * as cloudStorage from './cloud-storage'; @@ -545,36 +544,6 @@ handlers['api/payee-delete'] = withMutation(async function ({ id }) { return handlers['payees-batch-change']({ deleted: [{ id }] }); }); -handlers['api/payee-rules-get'] = async function ({ payeeId }) { - let rules = await handlers['payees-get-rules']({ id: payeeId }); - return rules.map(payeeRuleModel.toExternal); -}; - -handlers['api/payee-rule-create'] = withMutation(async function ({ - payee_id, - rule, -}) { - return handlers['payees-add-rule']({ - payee_id, - type: rule.type, - value: rule.value || null, - }); -}); - -handlers['api/payee-rule-update'] = withMutation(async function ({ - id, - fields, -}) { - return handlers['payees-update-rule']({ - id, - ...payeeRuleModel.fromExternal(fields), - }); -}); - -handlers['api/payee-rule-delete'] = withMutation(async function ({ id }) { - return handlers['payees-delete-rule']({ id }); -}); - export default function installAPI(serverHandlers) { handlers = Object.assign({}, serverHandlers, handlers); return handlers; diff --git a/packages/loot-core/src/server/db/index.ts b/packages/loot-core/src/server/db/index.ts index 1d08c3d16..a68a64e21 100644 --- a/packages/loot-core/src/server/db/index.ts +++ b/packages/loot-core/src/server/db/index.ts @@ -24,7 +24,6 @@ import { categoryModel, categoryGroupModel, payeeModel, - payeeRuleModel, } from '../models'; import { sendMessages, batchMessages } from '../sync'; @@ -454,10 +453,6 @@ export async function deletePayee(payee) { // mappings.map(m => update('payee_mapping', { id: m.id, targetId: null })) // ); - let rules = await all('SELECT * FROM payee_rules WHERE payee_id = ?', [ - payee.id, - ]); - await Promise.all(rules.map(rule => deletePayeeRule({ id: rule.id }))); return delete_('payees', payee.id); } @@ -533,29 +528,6 @@ export async function getPayeeByName(name) { ); } -export function insertPayeeRule(rule) { - rule = payeeRuleModel.validate(rule); - return insertWithUUID('payee_rules', rule); -} - -export function deletePayeeRule(rule) { - return delete_('payee_rules', rule.id); -} - -export function updatePayeeRule(rule) { - rule = payeeModel.validate(rule, { update: true }); - return update('payee_rules', rule); -} - -export function getPayeeRules(id) { - return all( - `SELECT pr.* FROM payee_rules pr - LEFT JOIN payee_mapping pm ON pm.id = pr.payee_id - WHERE pm.targetId = ? AND pr.tombstone = 0`, - [id], - ); -} - export function getAccounts() { return all( `SELECT a.*, b.name as bankName, b.id as bankId FROM accounts a diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index a07047f9e..ac508c218 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -505,34 +505,6 @@ handlers['payees-get-rules'] = async function ({ id }) { return rules.getRulesForPayee(id).map(rule => rule.serialize()); }; -handlers['payees-delete-rule'] = mutator(async function ({ id, payee_id }) { - return withUndo( - async () => { - return await db.deletePayeeRule({ id }); - }, - { payeeId: payee_id }, - ); -}); - -handlers['payees-update-rule'] = mutator(async function (rule) { - return withUndo( - async () => { - return await db.updatePayeeRule(rule); - }, - { payeeId: rule.payee_id }, - ); -}); - -handlers['payees-add-rule'] = mutator(async function (rule) { - return withUndo( - async () => { - let id = await db.insertPayeeRule(rule); - return { ...rule, id }; - }, - { payeeId: rule.payee_id }, - ); -}); - function validateRule(rule) { // Returns an array of errors, the array is the same link as the // passed-in `array`, or null if there are no errors @@ -655,10 +627,6 @@ handlers['rules-run'] = async function ({ transaction }) { return rules.runRules(transaction); }; -handlers['rules-migrate'] = async function () { - await rules.migrateOldRules(); -}; - handlers['make-filters-from-conditions'] = async function ({ conditions }) { return rules.conditionsToAQL(conditions); }; diff --git a/packages/loot-core/src/server/models.ts b/packages/loot-core/src/server/models.ts index 86bcd3ed5..40cb1002a 100644 --- a/packages/loot-core/src/server/models.ts +++ b/packages/loot-core/src/server/models.ts @@ -102,24 +102,6 @@ export const payeeModel = { }, }; -export const payeeRuleModel = { - validateType(rule) { - const { type } = rule; - if (type !== 'equals' && type !== 'contains') { - throw new Error('Invalid rule type: ' + type); - } - }, - - validate(rule, { update }: { update?: boolean } = {}) { - if (!update || 'type' in rule) { - payeeRuleModel.validateType(rule); - } - - requiredFields('payee_rules', rule, ['payee_id', 'type'], update); - return rule; - }, -}; - export const transactionModel = { validate(trans, { update }: { update?: boolean } = {}) { requiredFields('transaction', trans, ['date', 'acct'], update); diff --git a/packages/loot-core/src/server/sync/sync.property.test.ts b/packages/loot-core/src/server/sync/sync.property.test.ts index 0b636cbde..4242e3c61 100644 --- a/packages/loot-core/src/server/sync/sync.property.test.ts +++ b/packages/loot-core/src/server/sync/sync.property.test.ts @@ -84,12 +84,6 @@ let schema = { category: 'text', tombstone: 'integer', }, - payee_rules: { - payee_id: 'text', - type: 'text', - value: 'text', - tombstone: 'integer', - }, payee_mapping: { targetId: 'text' }, }; diff --git a/packages/loot-core/src/types/api.handlers.d.ts b/packages/loot-core/src/types/api.handlers.d.ts index 962f081d7..30e7b5b66 100644 --- a/packages/loot-core/src/types/api.handlers.d.ts +++ b/packages/loot-core/src/types/api.handlers.d.ts @@ -112,12 +112,4 @@ export interface ApiHandlers { 'api/payee-update': (arg: { id; fields }) => Promise<unknown>; 'api/payee-delete': (arg: { id }) => Promise<unknown>; - - 'api/payee-rules-get': (arg: { payeeId }) => Promise<unknown>; - - 'api/payee-rule-create': (arg: { payee_id; rule }) => Promise<unknown>; - - 'api/payee-rule-update': (arg: { id; fields }) => Promise<unknown>; - - 'api/payee-rule-delete': (arg: { id }) => Promise<unknown>; } diff --git a/packages/loot-core/src/types/main.handlers.d.ts b/packages/loot-core/src/types/main.handlers.d.ts index 49d3c0005..90cb6cc8b 100644 --- a/packages/loot-core/src/types/main.handlers.d.ts +++ b/packages/loot-core/src/types/main.handlers.d.ts @@ -81,12 +81,6 @@ export interface MainHandlers { 'payees-get-rules': (arg: { id }) => Promise<unknown>; - 'payees-delete-rule': (arg: { id; payee_id }) => Promise<unknown>; - - 'payees-update-rule': (rule) => Promise<unknown>; - - 'payees-add-rule': (rule) => Promise<unknown>; - 'rule-validate': (rule) => Promise<{ error: unknown }>; 'rule-add': (rule) => Promise<{ error: unknown } | { id: string }>; @@ -107,8 +101,6 @@ export interface MainHandlers { 'rules-run': (arg: { transaction }) => Promise<unknown>; - 'rules-migrate': () => Promise<unknown>; - 'make-filters-from-conditions': (arg: { conditions }) => Promise<unknown>; getCell: (arg: { sheetName; name }) => Promise<unknown>; diff --git a/upcoming-release-notes/985.md b/upcoming-release-notes/985.md new file mode 100644 index 000000000..890b46234 --- /dev/null +++ b/upcoming-release-notes/985.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [j-f1] +--- + +Remove unused payee rules feature -- GitLab