Skip to content

Enhance Revenue report component with metric selection and filtering#4103

Open
Ingole712521 wants to merge 1 commit intoumami-software:masterfrom
Ingole712521:fix/choose-the-Y-axis-fo-rchart-on-revenue-page
Open

Enhance Revenue report component with metric selection and filtering#4103
Ingole712521 wants to merge 1 commit intoumami-software:masterfrom
Ingole712521:fix/choose-the-Y-axis-fo-rchart-on-revenue-page

Conversation

@Ingole712521
Copy link
Copy Markdown

Feature: Add Metric Selector to Revenue Chart


Summary

This PR enhances the Revenue page by introducing a metric selector for the chart, allowing users to switch between different key metrics instead of being limited to Total revenue.


Problem

Currently, the Revenue chart only displays Total revenue over time, while other important metrics such as Average, Transactions, and Unique Customers are shown only as aggregated values.

This makes it difficult to analyze how these metrics evolve over time and limits the usefulness of the chart for deeper insights. Additionally, it does not support visualization of custom server-side metrics.


Changes Implemented

  • Added a metric selector (tabs/dropdown) to the Revenue page

  • Enabled switching between:

    • Total (default)
    • Average
    • Transactions
    • Unique Customers
  • Updated chart to dynamically render based on selected metric

  • Reused existing data structures where possible

  • Preserved existing behavior and UI consistency


Improvements

  • Provides more flexible and meaningful data visualization
  • Enables trend analysis across multiple metrics
  • Lays groundwork for supporting custom metrics (e.g., server-side events like project counts)

Technical Details

  • Introduced state management for selected metric
  • Refactored chart logic to support multiple datasets
  • Ensured modular and reusable component structure
  • Maintained backward compatibility

Fix #4097

- Added FilterButtons component for metric selection in the Revenue report.
- Introduced new metric definitions and updated chart data handling to support dynamic metric selection.
- Updated SQL queries to return detailed revenue data including sum, average, count, and unique count.
- Improved type definitions for revenue data structures to ensure type safety.
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 23, 2026

@Ingole712521 is attempting to deploy a commit to the Umami Software Team on Vercel.

A member of the Team first needs to authorize it.

@Ingole712521 Ingole712521 changed the title Enhance Revenue report component with metric selection and filtering #4097 Enhance Revenue report component with metric selection and filtering Mar 23, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 23, 2026

Greptile Summary

This PR enhances the Revenue report by adding a FilterButtons metric selector that lets users switch the chart between Total, Average, Transactions, and Unique Customers — all without re-fetching data, since all four values are now returned in a single query. The useFilterParameters hook also gains UUID validation for segment and cohort query parameters.

Key points:

  • Breaking change in SQL query (getRevenue.ts): The previous query grouped by both event_name and time bucket, producing one colored dataset per revenue event type (e.g., "purchase", "subscription") for a stacked chart. The new query drops event_name and groups only by time, collapsing all event types into one aggregate series. Sites with multiple revenue event names will lose the ability to compare event types visually.
  • UUID regex is too narrow (useFilterParameters.ts): The new validation regex accepts only UUID versions 1–5; versions 6, 7, and 8 are silently discarded, which would cause segment/cohort filters to be silently dropped for users whose IDs use UUID v7 (now common in many ORMs and databases).
  • FilterButtons is an uncontrolled component: It initialises its display selection from the value prop via useState but never re-syncs with the prop. While not a visible bug today (since metric is only changed through the buttons), it creates fragility for any future external state change (e.g., URL-driven reset).
  • The chart Y-axis and tooltip formatting correctly adapt between currency and number display depending on the selected metric.

Confidence Score: 2/5

  • Not safe to merge as-is — the SQL change silently removes per-event-type breakdown, which is a regression for existing users, and the UUID regex will silently drop valid v6/v7/v8 segment/cohort filters.
  • Two P1 issues block a clean merge: (1) removing event_name grouping from the SQL query is a breaking behavioral change for any site with multiple revenue event types, and (2) the UUID regex incorrectly rejects v6/v7/v8 identifiers, silently suppressing filters. Neither is a data-loss risk, but both could surprise existing users in ways that are hard to debug.
  • src/queries/sql/reports/getRevenue.ts (breaking SQL change) and src/components/hooks/useFilterParameters.ts (UUID regex version range)

Important Files Changed

Filename Overview
src/app/(main)/websites/[websiteId]/(reports)/revenue/Revenue.tsx Adds metric selector (FilterButtons) and renderYLabel to the Revenue chart. Logic is largely sound but FilterButtons is uncontrolled so external metric state changes won't be reflected in the button UI.
src/queries/sql/reports/getRevenue.ts SQL query now aggregates all revenue event types into one time-series, dropping the event_name grouping. This is a breaking change that removes the per-event-type stacked chart view previously available. Average is correctly computed post-query in JS.
src/components/hooks/useFilterParameters.ts Adds UUID validation for segment and cohort query params — good security improvement, but the regex only covers versions 1–5 and will silently discard valid UUID v6/v7/v8 identifiers.

Sequence Diagram

sequenceDiagram
    participant User
    participant Revenue as Revenue.tsx
    participant FilterButtons
    participant BarChart
    participant API as getRevenue (SQL)

    User->>Revenue: selects currency / date range
    Revenue->>API: useResultQuery({ websiteId, startDate, endDate, currency })
    API-->>Revenue: { chart: [{t, sum, avg, count, unique_count}], total, country }

    Note over Revenue: Computes metricDefinitions, chartData, metrics

    Revenue->>FilterButtons: items=metricOptions, value=metric
    Revenue->>BarChart: chartData (single dataset for selected metric)

    User->>FilterButtons: clicks different metric (e.g. "Transactions")
    FilterButtons->>Revenue: onChange("count")
    Revenue->>Revenue: setMetric("count") → re-maps data.chart[].count as y
    Revenue->>BarChart: updated chartData (no new API call)
    BarChart-->>User: re-renders chart with count data
Loading

Reviews (1): Last reviewed commit: "Enhance Revenue report component with me..." | Re-trigger Greptile

Comment on lines 53 to 69
@@ -64,12 +65,16 @@ async function relationalQuery(
and revenue.created_at between {{startDate}} and {{endDate}}
and revenue.currency = upper({{currency}})
${filterQuery}
group by x, t
group by t
order by t
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Breaking change: event-type grouping removed

The original SQL query grouped by both event_name and t, producing one row per (event_name, time_bucket). This let the frontend render one dataset per revenue event type (e.g., "purchase", "subscription") as separate stacked bars. The new query drops the event_name column entirely and groups only by t, merging all revenue event types into a single aggregate line.

Users who relied on the chart to compare revenue contributions from multiple event types will lose that visibility. For example, a business with both "checkout" and "upsell" events will now only see a combined bar instead of the two separate series.

The PR description mentions "Preserved existing behavior and UI consistency", but this is a material behavioral regression for any site with more than one revenue event name.

Consider either:

  • preserving the per-event-type breakdowns as a selectable view, or
  • explicitly documenting this as a deliberate simplification

import { useMemo } from 'react';
import { useNavigation } from './useNavigation';

const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 UUID regex excludes v6/v7/v8

The version nibble [1-5] only matches UUID versions 1 through 5. UUID versions 6, 7, and 8 are standardized (RFC 9562) and increasingly used — UUID v7 in particular is now commonly chosen for sortable IDs in modern databases and ORMs.

A valid UUID v7 segment/cohort ID (version nibble = 7) would fail this regex and be silently replaced with undefined, causing the filter to be dropped without any error. This could confuse users whose segments aren't applied even though they appear selected.

Suggested change
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-9a-f][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

})}
</MetricsBar>
<Panel>
<FilterButtons items={metricOptions} value={metric} onChange={handleMetricChange} />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 FilterButtons holds uncontrolled internal state

FilterButtons initialises its own selected state from the value prop via useState(value), which means it only reads value once on mount. After that, the parent's metric state and the button component's display state are decoupled. If metric is ever changed externally — e.g., a future URL-driven filter, a "reset" action, or any navigation that remounts the parent — the highlighted button will stay on the old selection while the chart renders the new one.

The component should be converted to a controlled component:

Suggested change
<FilterButtons items={metricOptions} value={metric} onChange={handleMetricChange} />
export function FilterButtons({ items, value, onChange }: FilterButtonsProps) {
const handleChange = (value: string) => {
onChange?.(value);
};
return (
<Box>
<ToggleGroup
value={[value]}
onChange={e => handleChange(e[0])}
disallowEmptySelection={true}
>

(Change would be in FilterButtons.tsx; the Revenue usage already passes value={metric} correctly.)

stacked={true}
currency={currency}
stacked={false}
currency={selectedMetric.isCurrency ? currency : undefined}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Tooltip value doesn't update when switching non-currency metrics

BarChart formats its hover tooltip using:

value: currency
  ? formatLongCurrency(dataPoints[0].raw.y, currency)
  : `${formatLongNumber(dataPoints[0].raw.y)} ${dataPoints[0].dataset.label}`

The currency prop is correctly set to undefined for count/unique_count metrics, so the tooltip will show e.g. "5 Transactions". However, the dataset label shown in the tooltip comes from selectedMetric.label which is re-resolved in the same chartData memo, so this should be consistent.

One edge case: when switching from a currency metric back to a non-currency metric, the currency prop is undefined but the previous chart tooltip (if still visible) could flash stale data before the memo re-runs. This is a minor UX issue, but worth being aware of during testing.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Choose the Y-axis for chart on Revenue page

1 participant