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

Add sorting option to custom reports #4141

Merged
merged 20 commits into from
Jan 13, 2025

Conversation

matt-fidd
Copy link
Contributor

@matt-fidd matt-fidd commented Jan 12, 2025

$${\color{red}\text{This PR contains a migration, don't connect it to a server you'd like to continue using}}$$

Adds support for sorting data in barcharts, pie charts and data tables
Disabled on time mode graphs and total mode graphs split by interval

Sort modes:

  • Ascending
  • Descending
  • Name (alphabetical)
  • Budget order

Sort is reversed to make ascending/descending more natural on all negative values

@actual-github-bot actual-github-bot bot changed the title Add sorting option to custom reports [WIP] Add sorting option to custom reports Jan 12, 2025
Copy link

netlify bot commented Jan 12, 2025

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit 5d9a1f5
🔍 Latest deploy log https://app.netlify.com/sites/actualbudget/deploys/67858dcf3021530008c6d6fc
😎 Deploy Preview https://deploy-preview-4141.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 Jan 12, 2025

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
18 → 21 5.92 MB → 5.92 MB (+4.44 kB) +0.07%
Changeset
File Δ Size
src/components/reports/spreadsheets/sortData.ts 🆕 +821 B 0 B → 821 B
locale/pt-BR.json 🆕 +19 B 0 B → 19 B
locale/nb-NO.json 🆕 +19 B 0 B → 19 B
locale/cy.json 🆕 +17 B 0 B → 17 B
src/i18n.ts 📈 +378 B (+20.31%) 1.82 kB → 2.19 kB
src/components/reports/spreadsheets/grouped-spreadsheet.ts 📈 +369 B (+14.37%) 2.51 kB → 2.87 kB
src/components/reports/disabledList.ts 📈 +305 B (+7.72%) 3.86 kB → 4.16 kB
src/components/reports/ReportSidebar.tsx 📈 +1.32 kB (+7.50%) 17.63 kB → 18.95 kB
src/components/reports/ReportOptions.ts 📈 +417 B (+6.16%) 6.61 kB → 7.01 kB
locale/fr.json 📈 +516 B (+4.00%) 12.6 kB → 13.11 kB
src/components/reports/spreadsheets/custom-spreadsheet.ts 📈 +155 B (+2.77%) 5.47 kB → 5.63 kB
home/runner/work/actual/actual/packages/loot-core/src/client/data-hooks/reports.ts 📈 +27 B (+1.88%) 1.4 kB → 1.43 kB
src/components/reports/reports/CustomReport.tsx 📈 +443 B (+1.87%) 23.08 kB → 23.51 kB
locale/en.json 📈 +27 B (+0.03%) 96.69 kB → 96.72 kB
src/components/reports/graphs/DonutGraph.tsx 📉 -111 B (-1.38%) 7.84 kB → 7.73 kB
src/components/reports/graphs/BarGraph.tsx 📉 -211 B (-2.49%) 8.28 kB → 8.07 kB
View detailed bundle breakdown

Added

Asset File Size % Changed
static/js/pt-BR.js 0 B → 19 B (+19 B) -
static/js/nb-NO.js 0 B → 19 B (+19 B) -
static/js/cy.js 0 B → 17 B (+17 B) -

Removed

No assets were removed

Bigger

Asset File Size % Changed
static/js/ReportRouter.js 1.58 MB → 1.59 MB (+3.46 kB) +0.21%
static/js/fr.js 12.6 kB → 13.11 kB (+516 B) +4.00%
static/js/index.js 3.74 MB → 3.74 MB (+405 B) +0.01%
static/js/en.js 96.69 kB → 96.72 kB (+27 B) +0.03%

Smaller

No assets were smaller

Unchanged

Asset File Size % Changed
static/js/pl.js 17 B 0%
static/js/ta.js 17 B 0%
static/js/zh-Hant.js 10.13 kB 0%
static/js/es.js 3.45 kB 0%
static/js/indexeddb-main-thread-worker-e59fee74.js 13.5 kB 0%
static/js/workbox-window.prod.es5.js 5.69 kB 0%
static/js/de.js 4.59 kB 0%
static/js/resize-observer.js 18.37 kB 0%
static/js/uk.js 120.47 kB 0%
static/js/BackgroundImage.js 122.29 kB 0%
static/js/AppliedFilters.js 10.24 kB 0%
static/js/useAccountPreviewTransactions.js 1.63 kB 0%
static/js/narrow.js 84.3 kB 0%
static/js/wide.js 105.92 kB 0%

Copy link
Contributor

github-actions bot commented Jan 12, 2025

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.33 MB → 1.33 MB (+72 B) +0.01%
Changeset
File Δ Size
packages/loot-core/src/server/reports/app.ts 📈 +68 B (+1.55%) 4.28 kB → 4.35 kB
packages/loot-core/src/server/aql/schema/index.ts 📈 +71 B (+0.48%) 14.6 kB → 14.67 kB
View detailed bundle breakdown

Added

No assets were added

Removed

No assets were removed

Bigger

Asset File Size % Changed
kcab.worker.js 1.33 MB → 1.33 MB (+72 B) +0.01%

Smaller

No assets were smaller

Unchanged

No assets were unchanged

@matt-fidd
Copy link
Contributor Author

/update-vrt

@matt-fidd
Copy link
Contributor Author

/update-vrt

@matt-fidd
Copy link
Contributor Author

/update-vrt

@matt-fidd matt-fidd force-pushed the custom-report-sorting branch from 2e7ab25 to 10938cf Compare January 12, 2025 20:07
@matt-fidd matt-fidd changed the title [WIP] Add sorting option to custom reports Add sorting option to custom reports Jan 12, 2025
Copy link
Contributor

coderabbitai bot commented Jan 12, 2025

Walkthrough

The pull request introduces a comprehensive sorting functionality across various components of the desktop client and core reporting system. The changes span multiple files in the packages/desktop-client and packages/loot-core directories, adding a new sortBy property to custom reports and implementing sorting mechanisms for different graph and spreadsheet types. A new SQL migration script is included to add a sort_by column to the custom_reports table, and type definitions are updated to support new sorting options with values 'asc', 'desc', 'name', and 'budget'. The modifications enable users to specify sorting preferences for reports, with the implementation touching components like ReportOptions, ReportSidebar, graph components, and spreadsheet utilities.

Possibly related PRs

Suggested labels

sparkles: Merged

Suggested reviewers

  • youngcw

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 987eed5 and 5dd53db.

📒 Files selected for processing (3)
  • packages/desktop-client/src/components/reports/disabledList.ts (6 hunks)
  • packages/desktop-client/src/components/reports/reports/CustomReport.tsx (11 hunks)
  • packages/loot-core/migrations/1736640000000__custom_report_sorting.sql (1 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/CustomReport.tsx (1)
Learnt from: matt-fidd
PR: actualbudget/actual#4141
File: packages/loot-core/src/types/models/reports.d.ts:14-14
Timestamp: 2025-01-12T20:27:52.636Z
Learning: In the Actual Budget codebase's reports feature, `sortBy` in `CustomReportEntity` is typed as string to store user-friendly values ('Ascending'/'Descending'), while `sortByOpType` ('asc'/'desc') is used internally for operations.
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (macos-latest)
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: Analyze
  • GitHub Check: Wait for Netlify build to finish
🔇 Additional comments (9)
packages/loot-core/migrations/1736640000000__custom_report_sorting.sql (1)

1-7: LGTM! Migration looks good.

The migration correctly:

  1. Wraps changes in a transaction for atomicity
  2. Adds the sort_by column with appropriate default value
  3. Updates existing records with sensible defaults based on graph type
packages/desktop-client/src/components/reports/disabledList.ts (4)

51-54: LGTM! Type definition looks good.

The graphOptions type correctly defines:

  • Required defaultSort property for specifying default sort order
  • Optional disableSort property for disabling sorting when needed

65-65: LGTM! Default sort configuration aligns with requirements.

The sort configuration correctly implements:

  • 'Budget' sort for TableGraph (as requested in PR feedback)
  • 'Descending' sort for other graph types
  • Disabled sorting for AreaGraph

Also applies to: 73-73, 82-83, 91-91


104-105: LGTM! Time mode graphs correctly disable sorting.

Sorting is correctly disabled for all time mode graphs (TableGraph, StackedBarGraph, LineGraph) as mentioned in PR objectives.

Also applies to: 113-114, 124-125


185-185: LGTM! Default sort handling looks good.

The changes correctly:

  1. Update defaultsGraphList to handle 'defaultSort'
  2. Implement sort defaults in the defaultItems function

Also applies to: 546-550

packages/desktop-client/src/components/reports/reports/CustomReport.tsx (4)

15-15: LGTM! Type and state setup looks good.

The changes correctly:

  1. Import the sortByOpType for type safety
  2. Add sortBy state initialized from the report

Also applies to: 234-235


365-365: LGTM! Sort operation mapping looks good.

The code correctly maps user-friendly sort values to internal operations using ReportOptions.sortByMap.


388-388: LGTM! Sort integration in data processing looks good.

The changes correctly integrate sorting into both grouped and custom spreadsheet creation.

Also applies to: 403-403, 423-423, 445-445


465-465: LGTM! Report state management looks good.

The changes correctly:

  1. Add sortBy to the report state
  2. Update setReportData to handle sort changes
  3. Pass setSortBy to ReportSidebar

Also applies to: 610-610, 741-741

Finishing Touches

  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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: 3

🧹 Nitpick comments (6)
packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts (1)

145-154: Optimize sorting implementation for better performance.

The current implementation has unnecessary spread operations and multiple iterations:

  1. Spreading arrays is unnecessary when using sort() as it already returns the sorted array
  2. The map operation can be combined with the initial sort

Consider this more efficient implementation:

-    const sortedGroupedDataFiltered = [...groupedDataFiltered]
-      .sort(sortData({ balanceTypeOp, sortByOp }))
-      .map(g => {
-        g.categories = [...(g.categories ?? [])].sort(
-          sortData({ balanceTypeOp, sortByOp }),
-        );
-        return g;
-      });
+    const sortedGroupedDataFiltered = groupedDataFiltered
+      .sort(sortData({ balanceTypeOp, sortByOp }))
+      .map(g => ({
+        ...g,
+        categories: (g.categories ?? []).sort(
+          sortData({ balanceTypeOp, sortByOp }),
+        ),
+      }));

     setData(sortedGroupedDataFiltered);
packages/loot-core/src/server/reports/app.ts (1)

Line range hint 20-36: Add validation for sort_by field.

Consider adding validation in the validate method to ensure sort_by only accepts valid values ('asc' or 'desc').

 validate(
   report: Omit<CustomReportEntity, 'tombstone'>,
   { update }: { update?: boolean } = {},
 ) {
   requiredFields('Report', report, ['conditionsOp'], update);

   if (!update || 'conditionsOp' in report) {
     if (!['and', 'or'].includes(report.conditionsOp)) {
       throw new ValidationError(
         'Invalid filter conditionsOp: ' + report.conditionsOp,
       );
     }
   }

+  if (!update || 'sortBy' in report) {
+    if (report.sortBy && !['asc', 'desc'].includes(report.sortBy)) {
+      throw new ValidationError(
+        'Invalid sortBy value: ' + report.sortBy,
+      );
+    }
+  }

   return report;
 }
packages/desktop-client/src/components/reports/ReportOptions.ts (1)

26-26: Consider using const values for sort direction.

Instead of using string literals, consider defining const values for sort directions to prevent typos and improve maintainability.

+const SORT_DIRECTIONS = {
+  ASCENDING: 'Ascending',
+  DESCENDING: 'Descending',
+} as const;

 export const defaultReport: CustomReportEntity = {
   // ...other properties
-  sortBy: 'Ascending',
+  sortBy: SORT_DIRECTIONS.ASCENDING,
   // ...other properties
 };
packages/desktop-client/src/components/reports/graphs/BarGraph.tsx (1)

2-2: Remove unused CSSProperties import.

The CSSProperties type is imported but not used in the changed code.

-import React, { useState, type CSSProperties } from 'react';
+import React, { useState } from 'react';
packages/desktop-client/src/components/reports/ReportSidebar.tsx (1)

164-172: Extract complex disable logic to a separate function.

The disableSort logic is complex and would be more maintainable as a separate function.

+const shouldDisableSort = (
+  graphType: string,
+  groupBy: string,
+  mode: string,
+  disabledList: any
+) => {
+  return (
+    graphType !== 'TableGraph' &&
+    (groupBy === 'Interval' ||
+      (disabledList?.mode
+        ?.find(m => m.description === mode)
+        ?.graphs.find(g => g.description === graphType)
+        ?.disableSort ??
+        false))
+  );
+};

-  const disableSort =
-    customReportItems.graphType !== 'TableGraph' &&
-    (customReportItems.groupBy === 'Interval' ||
-      (disabledList?.mode
-        ?.find(m => m.description === customReportItems.mode)
-        ?.graphs.find(g => g.description === customReportItems.graphType)
-        ?.disableSort ??
-        false));
+  const disableSort = shouldDisableSort(
+    customReportItems.graphType,
+    customReportItems.groupBy,
+    customReportItems.mode,
+    disabledList
+  );
packages/loot-core/migrations/1736640000000__custom_report_sorting.sql (1)

1-5: LGTM! Consider adding a CHECK constraint.

The migration follows best practices by using a transaction. For additional data integrity, consider adding a CHECK constraint to ensure only valid sort directions ('asc' or 'desc') can be stored:

 BEGIN TRANSACTION;

-ALTER TABLE custom_reports ADD COLUMN sort_by TEXT DEFAULT 'asc';
+ALTER TABLE custom_reports ADD COLUMN sort_by TEXT DEFAULT 'asc'
+  CHECK (sort_by IN ('asc', 'desc'));

 COMMIT;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1be1d4 and 10938cf.

⛔ Files ignored due to path filters (16)
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-1-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-2-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Data-Table-and-checks-the-visuals-3-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-1-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-2-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Switches-to-Donut-Graph-and-checks-the-visuals-3-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-1-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-2-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-labels-button-shows-the-labels-3-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-1-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-2-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-legend-button-shows-the-legend-side-bar-3-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-1-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-2-chromium-linux.png is excluded by !**/*.png
  • packages/desktop-client/e2e/reports.test.js-snapshots/Reports-custom-reports-Validates-that-show-summary-button-shows-the-summary-3-chromium-linux.png is excluded by !**/*.png
  • upcoming-release-notes/4141.md is excluded by !**/*.md
📒 Files selected for processing (14)
  • packages/desktop-client/src/components/reports/ReportOptions.ts (3 hunks)
  • packages/desktop-client/src/components/reports/ReportSidebar.tsx (5 hunks)
  • packages/desktop-client/src/components/reports/disabledList.ts (4 hunks)
  • packages/desktop-client/src/components/reports/graphs/BarGraph.tsx (2 hunks)
  • packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx (2 hunks)
  • packages/desktop-client/src/components/reports/reports/CustomReport.tsx (10 hunks)
  • packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts (5 hunks)
  • packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts (3 hunks)
  • packages/desktop-client/src/components/reports/spreadsheets/sortData.ts (1 hunks)
  • packages/loot-core/migrations/1736640000000__custom_report_sorting.sql (1 hunks)
  • packages/loot-core/src/client/data-hooks/reports.ts (1 hunks)
  • packages/loot-core/src/server/aql/schema/index.ts (1 hunks)
  • packages/loot-core/src/server/reports/app.ts (3 hunks)
  • packages/loot-core/src/types/models/reports.d.ts (3 hunks)
🔇 Additional comments (17)
packages/loot-core/src/client/data-hooks/reports.ts (1)

21-21: LGTM!

The addition of the sortBy property follows the established pattern for mapping database fields to entity properties.

packages/desktop-client/src/components/reports/disabledList.ts (3)

53-53: LGTM! Type definition properly updated.

The disableSort optional boolean property is correctly added to the graphOptions type.


79-79: LGTM! Sorting correctly disabled for AreaGraph.

Sorting is appropriately disabled for the AreaGraph in total mode, which aligns with the PR objectives.


99-99: LGTM! Sorting consistently disabled for time mode graphs.

Sorting is appropriately disabled for all time mode graphs (TableGraph, StackedBarGraph, LineGraph), maintaining consistency with the PR objectives.

Also applies to: 107-107, 117-117

packages/loot-core/src/server/reports/app.ts (1)

44-44: LGTM! Report model properly updated.

The sort_by field is correctly mapped in both directions between the database and JS representations.

Also applies to: 68-68

packages/desktop-client/src/components/reports/graphs/DonutGraph.tsx (1)

Line range hint 250-271: LGTM! Sorting logic properly moved to spreadsheet layer.

The removal of local sorting in the DonutGraph component is appropriate as sorting is now handled at the spreadsheet level, making the code cleaner and more maintainable.

packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts (2)

17-17: LGTM! Type definitions properly updated.

The sortByOpType import and sortByOp property are correctly added with proper typing.

Also applies to: 52-52


73-73: LGTM! Sorting implementation is clean and efficient.

The sorting implementation:

  • Sets a sensible default sort order ('asc')
  • Uses spread operator to avoid mutating the original array
  • Properly integrates with the existing data flow

Also applies to: 288-291

packages/desktop-client/src/components/reports/ReportOptions.ts (2)

53-56: LGTM! Well-structured sort options with translations.

The sort options are well-defined with proper translations and type-safe format values.


207-210: LGTM! Clean mapping implementation.

The mapping between display values and internal format is implemented correctly using Map.

packages/desktop-client/src/components/reports/graphs/BarGraph.tsx (1)

222-222: LGTM! Clean data flow.

The direct use of data[splitData] simplifies the data flow by removing local sorting, which is now handled centrally.

packages/desktop-client/src/components/reports/ReportSidebar.tsx (2)

146-150: LGTM! Clean handler implementation.

The onChangeSortBy handler follows the established pattern and correctly updates both session state and parent component.


287-308: LGTM! Well-structured conditional rendering.

The sort options UI is correctly implemented with proper conditional rendering based on the disableSort flag.

packages/desktop-client/src/components/reports/reports/CustomReport.tsx (4)

15-15: LGTM! Type and state management look good.

The sortByOpType import and sortBy state are properly integrated with the existing state management pattern.

Also applies to: 234-235


365-365: LGTM! Data processing logic is properly integrated.

The sorting functionality is consistently implemented across both group and graph data processing, with proper memoization dependencies.

Also applies to: 388-388, 403-403, 423-423, 445-445


465-465: LGTM! Component integration is consistent.

The sortBy functionality is well-integrated into the component hierarchy, following existing patterns for state management and prop passing.

Also applies to: 604-604, 735-735


Line range hint 1-924: Verify test coverage for the new sorting functionality.

While the implementation is solid, ensure that test cases are added to verify:

  1. Default sorting behavior
  2. Ascending and descending sort operations
  3. Persistence of sort preferences
  4. Integration with different report types (bar charts, pie charts, data tables)

Would you like me to help generate test cases for the new sorting functionality?

@matt-fidd matt-fidd mentioned this pull request Jan 12, 2025
@youngcw
Copy link
Member

youngcw commented Jan 12, 2025

This is looking pretty good. I have a few thoughts.

  1. I know the ascending/descending is technically correct with what happens, but it feels backwards. Maybe we need to think about this and maybe have it automagically flip if the report is all negative amounts.
  2. I think there should be a sort option for budget order. For example, the data table makes more sense to me to be in budget order vs amount based sorting.
  3. I think the sort should be available in the stacked bars. Both the ascending/descending options as well as the original(?) budget order option.

@Teprifer
Copy link

Agree with youngcw's first point, instantly felt weird. It's the size of the bar vs the raw amount, I'd go with the former as it's a visual setting for bar charts. Except possibly for Net Deposit which seems to make sense as-is (ascending shows small to large bar with my budget.
Point 2 is solid too.

For migration I'd suggest populating the sort box in the UI, my reports loaded with nothing in it so it was tiny.

image

@matt-fidd
Copy link
Contributor Author

/update-vrt

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

🧹 Nitpick comments (3)
packages/desktop-client/src/components/reports/spreadsheets/sortData.ts (2)

7-10: Consider using a complete mapping for better type safety.

The Partial<Record> allows for undefined mappings. Since we know all possible values, we can make this more type-safe.

-const reverseSort: Partial<Record<sortByOpType, sortByOpType>> = {
+const reverseSort: Record<'asc' | 'desc', sortByOpType> = {
   asc: 'desc',
   desc: 'asc',
 };

12-15: Optimize balance type lookup performance.

For better performance with larger datasets, consider using a Set for O(1) lookup instead of array includes O(n).

-const balanceTypesToReverse = ['totalDebts', 'netDebts'];
+const balanceTypesToReverse = new Set(['totalDebts', 'netDebts']);

 const shouldReverse = (balanceTypeOp: balanceTypeOpType) =>
-  balanceTypesToReverse.includes(balanceTypeOp);
+  balanceTypesToReverse.has(balanceTypeOp);
packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts (1)

288-291: Avoid unnecessary array spread for sorting.

Creating a new array with spread operator before sorting is unnecessary as sort() already mutates and returns the same array.

-    const sortedCalcDataFiltered = [...calcDataFiltered].sort(
+    const sortedCalcDataFiltered = calcDataFiltered.sort(
       sortData({ balanceTypeOp, sortByOp }),
     );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bda9de1 and dc8cd63.

📒 Files selected for processing (7)
  • packages/desktop-client/src/components/reports/ReportOptions.ts (3 hunks)
  • packages/desktop-client/src/components/reports/reports/CustomReport.tsx (10 hunks)
  • packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts (5 hunks)
  • packages/desktop-client/src/components/reports/spreadsheets/sortData.ts (1 hunks)
  • packages/loot-core/migrations/1736640000000__custom_report_sorting.sql (1 hunks)
  • packages/loot-core/src/server/aql/schema/index.ts (1 hunks)
  • packages/loot-core/src/types/models/reports.d.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/loot-core/src/server/aql/schema/index.ts
  • packages/loot-core/migrations/1736640000000__custom_report_sorting.sql
🧰 Additional context used
📓 Learnings (4)
packages/loot-core/src/types/models/reports.d.ts (1)
Learnt from: matt-fidd
PR: actualbudget/actual#4141
File: packages/loot-core/src/types/models/reports.d.ts:14-14
Timestamp: 2025-01-12T20:27:52.636Z
Learning: In the Actual Budget codebase's reports feature, `sortBy` in `CustomReportEntity` is typed as string to store user-friendly values ('Ascending'/'Descending'), while `sortByOpType` ('asc'/'desc') is used internally for operations.
packages/desktop-client/src/components/reports/spreadsheets/sortData.ts (2)
Learnt from: matt-fidd
PR: actualbudget/actual#4141
File: packages/loot-core/src/types/models/reports.d.ts:14-14
Timestamp: 2025-01-12T20:27:52.636Z
Learning: In the Actual Budget codebase's reports feature, `sortBy` in `CustomReportEntity` is typed as string to store user-friendly values ('Ascending'/'Descending'), while `sortByOpType` ('asc'/'desc') is used internally for operations.
Learnt from: matt-fidd
PR: actualbudget/actual#4141
File: packages/desktop-client/src/components/reports/spreadsheets/sortData.ts:15-26
Timestamp: 2025-01-12T20:22:02.704Z
Learning: In the custom reports feature of Actual, the `balanceTypeOp` and `sortByOp` parameters in the `sortData` function are always set and should not be marked as optional.
packages/desktop-client/src/components/reports/reports/CustomReport.tsx (1)
Learnt from: matt-fidd
PR: actualbudget/actual#4141
File: packages/loot-core/src/types/models/reports.d.ts:14-14
Timestamp: 2025-01-12T20:27:52.636Z
Learning: In the Actual Budget codebase's reports feature, `sortBy` in `CustomReportEntity` is typed as string to store user-friendly values ('Ascending'/'Descending'), while `sortByOpType` ('asc'/'desc') is used internally for operations.
packages/desktop-client/src/components/reports/spreadsheets/custom-spreadsheet.ts (1)
Learnt from: matt-fidd
PR: actualbudget/actual#4141
File: packages/desktop-client/src/components/reports/spreadsheets/sortData.ts:15-26
Timestamp: 2025-01-12T20:22:02.704Z
Learning: In the custom reports feature of Actual, the `balanceTypeOp` and `sortByOp` parameters in the `sortData` function are always set and should not be marked as optional.
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Functional
  • GitHub Check: Visual regression
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: Analyze
🔇 Additional comments (12)
packages/desktop-client/src/components/reports/spreadsheets/sortData.ts (2)

1-5: Clean imports using type imports!

Good use of type imports for better tree-shaking and runtime performance.


31-44: 🛠️ Refactor suggestion

Add numeric type safety and stable sort implementation.

The current implementation has two issues:

  1. No handling of potential NaN values in numeric comparisons
  2. Sort is not stable, which can lead to inconsistent ordering when values are equal
   return (a, b) => {
     let comparison = 0;
     if (sortByOp === 'asc') {
-      comparison = a[balanceTypeOp] - b[balanceTypeOp];
+      const aVal = a[balanceTypeOp] || 0;
+      const bVal = b[balanceTypeOp] || 0;
+      comparison = aVal - bVal;
     } else if (sortByOp === 'desc') {
-      comparison = b[balanceTypeOp] - a[balanceTypeOp];
+      const aVal = a[balanceTypeOp] || 0;
+      const bVal = b[balanceTypeOp] || 0;
+      comparison = bVal - aVal;
     } else if (sortByOp === 'name') {
       comparison = (a.name ?? '').localeCompare(b.name ?? '');
     } else if (sortByOp === 'budget') {
       comparison = 0;
     }
 
-    return comparison;
+    // Implement stable sort by using name as secondary key
+    return comparison || (a.name ?? '').localeCompare(b.name ?? '');
   };

Likely invalid or redundant comment.

packages/loot-core/src/types/models/reports.d.ts (3)

14-14: Correct use of string type for user-friendly values!

The sortBy property is correctly typed as string to store user-friendly values ('Ascending'/'Descending') as mentioned in the learnings.


34-34: Well-defined sortByOpType with all possible values!

The sortByOpType correctly includes all possible sort operations: 'asc', 'desc', 'name', and 'budget'.


128-128: Consistent use of sortByOpType for internal operations!

The sort_by property in CustomReportData correctly uses sortByOpType for internal operations, maintaining the separation between user-friendly and internal values.

packages/desktop-client/src/components/reports/ReportOptions.ts (3)

53-58: LGTM! Sort options align with requirements.

The sort options include both basic (Ascending/Descending) and specialized (Name/Budget) sorting as requested in the PR feedback. The options are properly internationalized.


209-212: LGTM! Clean implementation of sort options mapping.

The implementation correctly maps user-friendly descriptions to internal format values, following the established pattern in the codebase.


26-26: 🛠️ Refactor suggestion

Consider context-aware default sorting.

Based on user feedback, the default descending sort might feel counterintuitive for certain reports. Consider making the default sort order context-aware:

  • For reports with negative amounts, automatically flip the sort order
  • For reports showing Net Deposit, use ascending order by default
-  sortBy: 'Descending',
+  sortBy: context => {
+    if (context.hasNegativeAmounts) return 'Ascending';
+    if (context.mode === 'Net Deposit') return 'Ascending';
+    return 'Descending';
+  },

Likely invalid or redundant comment.

packages/desktop-client/src/components/reports/reports/CustomReport.tsx (4)

15-15: LGTM! Proper type import.

The import of sortByOpType aligns with the retrieved learning about separating user-friendly values from internal operation types.


234-235: LGTM! Clean state management for sorting.

The implementation properly:

  • Manages sort state using React's useState hook
  • Maps user-friendly values to internal operation types
  • Provides appropriate fallback values

Also applies to: 365-365


465-465: LGTM! Consistent state updates.

The sort state is properly:

  • Included in the report configuration
  • Updated when loading report data
  • Passed to the sidebar component

Also applies to: 604-604, 735-735


388-388: Verify sort behavior for time-based and interval-split graphs.

The PR objectives mention that sorting should be disabled for time mode graphs and total mode graphs split by interval. However, sortByOp is being passed unconditionally to both getGroupData and getGraphData.

Also applies to: 403-403, 423-423, 445-445

@matt-fidd matt-fidd force-pushed the custom-report-sorting branch from 1f66f33 to 0d0ae3c Compare January 13, 2025 15:49
@youngcw
Copy link
Member

youngcw commented Jan 13, 2025

Amazing

I would vote for budget order to be the default but what do others think? Descending would be my second choice though.

@matt-fidd
Copy link
Contributor Author

Good ideas both, I have to admit that I got the sorting all muddled up even when writing...

Amazing

I would vote for budget order to be the default but what do others think? Descending would be my second choice though.

I prefer descending on the pies and bars, but budget makes more sense in the table. I can make new defaults based on the type?

@youngcw
Copy link
Member

youngcw commented Jan 13, 2025

I prefer descending on the pies and bars, but budget makes more sense in the table. I can make new defaults based on the type?

I think thats fine as long as its not overly difficult to code up.

@matt-fidd
Copy link
Contributor Author

/update-vrt

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

🧹 Nitpick comments (2)
packages/desktop-client/src/components/reports/reports/CustomReport.tsx (1)

545-550: Consider adding error handling for invalid sort values.

The default sort handling assumes the presence of a valid sort value. Consider adding a fallback for cases where defaultsGraphList returns an unexpected value.

-    const defaultSort = defaultsGraphList(mode, chooseGraph, 'defaultSort');
-    if (defaultSort) {
-      setSessionReport('sortBy', defaultSort);
-      setSortBy(defaultSort);
-    }
+    const defaultSort = defaultsGraphList(mode, chooseGraph, 'defaultSort');
+    const validSort = defaultSort || 'Descending'; // Fallback to a safe default
+    setSessionReport('sortBy', validSort);
+    setSortBy(validSort);
packages/desktop-client/src/components/reports/disabledList.ts (1)

104-105: Remove redundant defaultSort from time graphs.

Since sorting is disabled for time-based graphs (disableSort: true), having a defaultSort value is redundant and might cause confusion in maintenance. Consider removing the defaultSort property from these configurations.

 {
   description: 'TableGraph',
   // ... other properties ...
   disableSort: true,
-  defaultSort: 'Descending',
 },

Also applies to: 113-114, 124-125

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e54a9fa and 987eed5.

📒 Files selected for processing (2)
  • packages/desktop-client/src/components/reports/disabledList.ts (6 hunks)
  • packages/desktop-client/src/components/reports/reports/CustomReport.tsx (11 hunks)
🧰 Additional context used
📓 Learnings (1)
packages/desktop-client/src/components/reports/reports/CustomReport.tsx (1)
Learnt from: matt-fidd
PR: actualbudget/actual#4141
File: packages/loot-core/src/types/models/reports.d.ts:14-14
Timestamp: 2025-01-12T20:27:52.636Z
Learning: In the Actual Budget codebase's reports feature, `sortBy` in `CustomReportEntity` is typed as string to store user-friendly values ('Ascending'/'Descending'), while `sortByOpType` ('asc'/'desc') is used internally for operations.
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (macos-latest)
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: Analyze
  • GitHub Check: Wait for Netlify build to finish
🔇 Additional comments (8)
packages/desktop-client/src/components/reports/reports/CustomReport.tsx (5)

15-15: LGTM! Type import looks good.

The sortByOpType import aligns with the retrieved learning about sort operations.


234-235: LGTM! State initialization is correct.

The sort state is properly initialized from the loaded report configuration.


365-365: LGTM! Sort operation mapping is well implemented.

The mapping from user-friendly values to operation types is handled correctly using the ReportOptions.sortByMap.


741-741: LGTM! Sort setter prop is correctly passed.

The setSortBy function is properly passed to the ReportSidebar component.


388-388: Verify sort behavior with time mode and total mode graphs.

The sort operation is being passed to both grouped and custom spreadsheet creation. According to the PR objectives, sorting should be disabled for time mode graphs and total mode graphs that are split by interval.

Run this script to verify the implementation:

Also applies to: 403-403, 423-423, 445-445

packages/desktop-client/src/components/reports/disabledList.ts (3)

51-54: LGTM! Type definition changes align with requirements.

The addition of defaultSort and disableSort properties provides a clean way to configure sorting behavior per graph type.


185-185: LGTM! Function signature updated correctly.

The addition of 'defaultSort' to the type union allows retrieving default sort values through the existing API.


51-54: Verify sort options initialization.

As per Teprifer's feedback, reports initially loaded with no content. Ensure that:

  1. The migration properly populates the sort_by column
  2. The UI correctly initializes with these default sort values
✅ Verification successful

Sort initialization is properly implemented

The sort functionality is correctly implemented with:

  • Default sort values defined for all graph types
  • Proper persistence in both session storage and database
  • Consistent initialization in the UI components
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for migration files and sort initialization logic
echo "Checking migrations:"
fd -e sql . packages/loot-core/src/server/db/migrations/

echo "Checking sort initialization:"
rg -A 5 "sort.*initialize|initialize.*sort" packages/desktop-client/src/components/reports/

Length of output: 404


Script:

#!/bin/bash
# Find migrations directory
echo "Locating migrations directory:"
fd migrations

echo "Checking sort-related code in reports:"
rg -A 5 "defaultSort|sort_by" packages/desktop-client/src/components/reports/

echo "Checking sort initialization in the entire codebase:"
ast-grep --pattern 'defaultSort = $_'

Length of output: 9363


Script:

#!/bin/bash
# Check setSessionReport implementation
echo "Checking setSessionReport implementation:"
rg -A 5 "setSessionReport|saveReport" packages/desktop-client/src/components/reports/

# Check if there are any related tests
echo "Checking related tests:"
rg -A 5 "test.*CustomReport|describe.*CustomReport" packages/desktop-client/src/**/*test*

Length of output: 21069

@matt-fidd matt-fidd force-pushed the custom-report-sorting branch from 987eed5 to 5dd53db Compare January 13, 2025 16:35
@matt-fidd
Copy link
Contributor Author

I prefer descending on the pies and bars, but budget makes more sense in the table. I can make new defaults based on the type?

I think thats fine as long as its not overly difficult to code up.

Done!

@youngcw
Copy link
Member

youngcw commented Jan 13, 2025

do you think you could revert-rerun the vrt on those data table snapshots? There didn't pick up the default order change and I don't want that causing issues down the line.

@matt-fidd
Copy link
Contributor Author

/update-vrt

@youngcw
Copy link
Member

youngcw commented Jan 13, 2025

weird. I wonder if the table sorting is defaulting to descending with a direct navigation like the VRT is doing.

@matt-fidd
Copy link
Contributor Author

weird. I wonder if the table sorting is defaulting to descending with a direct navigation like the VRT is doing.

yeah, strange. I don't think it's a massive deal because it's rare that anyone would browse directly but I'll have a look tomorrow to see if I can sort it.

@youngcw
Copy link
Member

youngcw commented Jan 13, 2025

weird. I wonder if the table sorting is defaulting to descending with a direct navigation like the VRT is doing.

yeah, strange. I don't think it's a massive deal because it's rare that anyone would browse directly but I'll have a look tomorrow to see if I can sort it.

If its consistent then its not a big deal. The interface seems to work right for a real user.

@matt-fidd matt-fidd force-pushed the custom-report-sorting branch from 5ca41ea to 5d9a1f5 Compare January 13, 2025 22:03
@matt-fidd
Copy link
Contributor Author

weird. I wonder if the table sorting is defaulting to descending with a direct navigation like the VRT is doing.

yeah, strange. I don't think it's a massive deal because it's rare that anyone would browse directly but I'll have a look tomorrow to see if I can sort it.

If its consistent then its not a big deal. The interface seems to work right for a real user.

That's fine, I'll leave as-is ready for review then. I've retriggered the actions.

@matt-fidd matt-fidd merged commit bec8419 into actualbudget:master Jan 13, 2025
20 checks passed
@matt-fidd matt-fidd deleted the custom-report-sorting branch January 13, 2025 22:11
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