Skip to content

feat(analytics): add DecisionsScoreDistribution - #1207

Merged
siiick merged 23 commits into
mainfrom
feat/in-app-analytics-rule-vs-decision-outcome
Nov 24, 2025
Merged

feat(analytics): add DecisionsScoreDistribution#1207
siiick merged 23 commits into
mainfrom
feat/in-app-analytics-rule-vs-decision-outcome

Conversation

@siiick

@siiick siiick commented Nov 12, 2025

Copy link
Copy Markdown
Contributor

✨ Improvements

  • Adjusted and centralized decision colors.
  • Added tooltip icons (descriptions still to be defined — see locale files, @Pascal-Delange ).
  • Refactored the filters bar by separating Clear and Apply actions to improve feature discoverability.
  • Added a link to Metabase analytics at the top of the page.
  • eintroduced the secondary date-range filter.

🧹 UX & Behavior

  • Cleared custom filter values when switching scenarios.
  • Introduced a minimum 3px height for decisions in the Decisions graph to ensure visibility when outcomes are extremely small (e.g., < 1px).

⚠️ Notes

  • The 3px minimum-height feature is complex to implement, and it’s still unclear whether the benefit justifies the effort.
    Logic is available in commit 67b0276.
  • The custom bar component is currently commented out. cc @ChibiBlasphem

Note

Adds Decisions Score Distribution, centralized outcome colors, contextual tooltips, restored comparison date range, and refactors queries/filters UI with multiple UX improvements.

  • Analytics UI:
    • Centralize decision colors via constants/analytics.ts and use in Decisions, OutcomeFilter, RuleVsDecisionOutcomes.
    • New AnalyticsTooltip component; add tooltip icons/descriptions to charts and table headers.
    • Decisions chart: improved tooltip with per-outcome values and total, adjustable bar padding by period length, optional symlog scale scaffolding, grouping/stacking tweaks, CSV export, and hover-driven highlighting of OutcomeFilter.
    • Add DecisionsScoreDistribution panel to layout; resize main/side columns.
    • Restore secondary compareRange filter in FiltersBar; keep instant updates for scenarioId/date ranges.
    • FiltersBar UX: separate Apply/Reapply and Clear dynamic actions inline per priority row.
    • Date range popover: localized quick-range presets with labeled summary.
    • Add legacy analytics link to header.
  • Data/Queries:
    • Consolidate analytics queries into queries/analytics/get-data.ts; add endpoints for decisions-score-distribution and wire server route handler.
    • Minor logic tweak to only include trigger when scenario unchanged; remove stray logging.
  • Tables:
    • RulesHit and ScreeningHits: header tooltips; percentage formatting adjustments.
    • Table header markup refinement for header content container.
  • Styles/i18n:
    • New Tailwind utility animate-ping-once for highlight effect.
    • Add/expand i18n strings (en/fr/ar) for tooltips, presets, and labels.

Written by Cursor Bugbot for commit 8e49872. This will update automatically on new commits. Configure here.

@siiick
siiick force-pushed the feat/in-app-analytics-rule-vs-decision-outcome branch from a86c90b to e1a2835 Compare November 19, 2025 16:13
@siiick
siiick marked this pull request as ready for review November 20, 2025 10:52
@siiick
siiick force-pushed the feat/in-app-analytics-rule-vs-decision-outcome branch from 050132c to 69ffa33 Compare November 20, 2025 10:52
@siiick
siiick requested review from ChibiBlasphem and Pascal-Delange and removed request for ChibiBlasphem November 20, 2025 10:56

@ChibiBlasphem ChibiBlasphem left a comment

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.

I didn't highlight them but can you also move out all the functions from Decisions which do not use local variables from it?

Outcome,
RangeId,
} from '@app-builder/models/analytics';
import { OUTCOME_COLORS } from '@app-builder/routes/_builder+/_analytics+/analytics.$scenarioId';

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.

This constant should IMO be part of the constants folder, taking it from the route file while being in a component will surely have a bad effect later.

Comment on lines +93 to +103
<TooltipV2.Provider>
<TooltipV2.Tooltip>
<TooltipV2.TooltipTrigger asChild>
<Icon icon="tip" className="size-5 text-grey-60 hover:text-purple-65 cursor-pointer" />
</TooltipV2.TooltipTrigger>
<TooltipV2.TooltipContent>
<span className="font-normal">{t('analytics:rule_vs_decision_outcomes.tooltip')}</span>
</TooltipV2.TooltipContent>
</TooltipV2.Tooltip>
</TooltipV2.Provider>
</span>

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.

This tooltip is repeated a lot of time while the only thing that changes is the content. Can it be extracted in a GraphTooltip component with only the content as a prop so it can be more maintainable?

Comment on lines +38 to +47
<TooltipV2.Provider>
<TooltipV2.Tooltip>
<TooltipV2.TooltipTrigger asChild>
<Icon icon="tip" className="size-4 text-grey-60 hover:text-purple-65 cursor-pointer ml-v2-sm" />
</TooltipV2.TooltipTrigger>
<TooltipV2.TooltipContent>
<span className="font-normal">{t('analytics:screening_hits.columns.execs.tooltip')}</span>
</TooltipV2.TooltipContent>
</TooltipV2.Tooltip>
</TooltipV2.Provider>

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.

Tooltip here

Comment on lines +56 to +65
<TooltipV2.Provider>
<TooltipV2.Tooltip>
<TooltipV2.TooltipTrigger asChild>
<Icon icon="tip" className="size-4 text-grey-60 hover:text-purple-65 cursor-pointer ml-v2-sm" />
</TooltipV2.TooltipTrigger>
<TooltipV2.TooltipContent>
<span className="font-normal">{t('analytics:screening_hits.columns.hits.tooltip')}</span>
</TooltipV2.TooltipContent>
</TooltipV2.Tooltip>
</TooltipV2.Provider>

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.

Tooltip here

Comment on lines +74 to +83
<TooltipV2.Provider>
<TooltipV2.Tooltip>
<TooltipV2.TooltipTrigger asChild>
<Icon icon="tip" className="size-4 text-grey-60 hover:text-purple-65 cursor-pointer ml-v2-sm" />
</TooltipV2.TooltipTrigger>
<TooltipV2.TooltipContent>
<span className="font-normal">{t('analytics:screening_hits.columns.hit_ratio.tooltip')}</span>
</TooltipV2.TooltipContent>
</TooltipV2.Tooltip>
</TooltipV2.Provider>

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.

Tooltip here

Comment on lines +93 to +104
<TooltipV2.Provider>
<TooltipV2.Tooltip>
<TooltipV2.TooltipTrigger asChild>
<Icon icon="tip" className="size-4 text-grey-60 hover:text-purple-65 cursor-pointer ml-v2-sm" />
</TooltipV2.TooltipTrigger>
<TooltipV2.TooltipContent>
<span className="font-normal">
{t('analytics:screening_hits.columns.avg_hits_per_screening.tooltip')}
</span>
</TooltipV2.TooltipContent>
</TooltipV2.Tooltip>
</TooltipV2.Provider>

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.

Tooltip here

Comment on lines +49 to +54
export const OUTCOME_COLORS: Record<Outcome, string> = {
approve: '#46BB7F',
review: '#FDBD35',
blockAndReview: '#FF8533',
decline: '#DB5F4A',
};

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.

As previously said, it would be better to either move this to the constants folder or create a constant file located with the Analytics components and import it here. It's generally a bad idea to have file A importing from file B and file B importing from file A.

Comment on lines +28 to +31
// wait for a random time between 1 and 5 seconds
// const randomTime = Math.floor(Math.random() * 5000) + 1000;
// console.log('--------> randomTime', randomTime);
// await new Promise((resolve) => setTimeout(resolve, randomTime));

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.

Is it still needed?

@siiick
siiick requested a review from ChibiBlasphem November 21, 2025 11:12
…component across multiple analytics components
@siiick
siiick force-pushed the feat/in-app-analytics-rule-vs-decision-outcome branch from 4c3ea17 to 13a68f7 Compare November 21, 2025 11:12
…ant function and directly using formatNumber
@siiick
siiick force-pushed the feat/in-app-analytics-rule-vs-decision-outcome branch from 4d52763 to 675ed06 Compare November 21, 2025 12:39
Comment on lines +33 to +35
<AnalyticsTooltip content={t('analytics:rule_hits.columns.hit_count.tooltip')}>
<Icon icon="tip" className="size-4 text-grey-60 hover:text-purple-65 cursor-pointer ml-v2-sm" />
</AnalyticsTooltip>

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.

All the tooltips on the page use <Icon icon="tip" className="size-4 text-grey-60 hover:text-purple-65 cursor-pointer ml-v2-sm" /> can it not be directly set inside the component?
There's only one place where the class is size-5 instead of size-4, make the className of the icon customizable instead. Right now there's still too much code duplication in my opinion

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done thanks :)

…to streamline tooltip rendering and remove redundant Icon components
@siiick
siiick requested a review from ChibiBlasphem November 24, 2025 07:27

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Comment thread packages/app-builder/src/components/Analytics/OutcomeFilter.tsx Outdated
onMouseEnter={() => {
setIsHovered(true);
}}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Missing onMouseLeave handler causes stuck hover state

The isHovered state is set to true on onMouseEnter but never reset to false on onMouseLeave. This causes the hover state to persist indefinitely after the user moves their mouse over the component once, preventing the highlight animation from working correctly on subsequent hovers and potentially affecting UI behavior that depends on the hover state.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is intentional. We want the ping effect only once, the first time the user hover the component.

@ChibiBlasphem ChibiBlasphem left a comment

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.

lgtm

<TooltipV2.TooltipTrigger asChild>
<Icon
icon="tip"
className={cn('text-grey-60 text-purple-65 hover:text-grey-50 cursor-pointer ml-v2-sm', className)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Conflicting Tailwind CSS text color classes

The className contains two conflicting text color classes text-grey-60 and text-purple-65. In Tailwind CSS, when multiple classes of the same property type are present, only the last one applies, so text-purple-65 overrides text-grey-60, making the first class ineffective.

Fix in Cursor Fix in Web

@siiick
siiick merged commit e64a7b0 into main Nov 24, 2025
6 checks passed
@siiick
siiick deleted the feat/in-app-analytics-rule-vs-decision-outcome branch November 24, 2025 08:26
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.

2 participants