Skip to content

feat(dashboards): add ROAS metrics and drawer improvements#315

Closed
mrautela365 wants to merge 8 commits intomainfrom
feat/LFXV2-1220-ed-marketing-preview
Closed

feat(dashboards): add ROAS metrics and drawer improvements#315
mrautela365 wants to merge 8 commits intomainfrom
feat/LFXV2-1220-ed-marketing-preview

Conversation

@mrautela365
Copy link
Copy Markdown
Contributor

Summary

  • Replace complex web activities CTE with Platinum views for better performance
  • Add ROAS (Return on Ad Spend) as primary metric for the paid social card
  • Move recommended actions and key insights to the first fold in all marketing drawers
  • Add ROAS trend chart and spend/revenue stats to paid social drawer

Related

Test plan

  • Verify ED marketing dashboard loads with ROAS data on paid social card
  • Verify paid social drawer shows ROAS, spend, revenue stats
  • Verify ROAS trend chart renders correctly
  • Verify recommended actions appear in first fold of all drawers
  • Verify web activities card loads data from Platinum views

Generated with Claude Code

mrautela365 and others added 5 commits March 13, 2026 17:48
Add three drill-down drawer components for the ED marketing dashboard:
- Website Visits: daily trend chart + domain breakdown
- Email CTR: monthly trend, campaign CTR breakdown, reach vs opens
- Paid Social Reach: monthly impressions, channel breakdown

Backend: Snowflake queries with foundation-scoped recursive CTE for
all three endpoints. Frontend: signal-based drawers with Chart.js
integration following the established drawer pattern.

LFXV2-1220

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
- Fix Email CTR and Paid Social Reach card subtitles from "Last 30
  days" to "Last 6 months" to match actual query time range
- Fix docstring on getEmailCtr to accurately describe foundation
  filtering (uses CTE, not "All Projects" row)
- Add missing "Email CTR" section header in analytics-data interface
- Fix "Social Reach" section header that was mislabeled as "Email CTR"

LFXV2-1220

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
- Collapse nested guards on breakdown sections — use single `> 1`
  check to hide entire section instead of showing empty panel
- Close active drawer on foundation change to prevent stale data
- Use weighted CTR (clicks/sends) instead of AVG(CTR) for campaign
  aggregation accuracy

LFXV2-1220

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
The SQL already multiplies by 100 (SUM(clicks)/SUM(sends) * 100), so
the JS mapping should only round to 2 decimals (row.AVG_CTR * 100 / 100)
instead of applying another 100x factor (row.AVG_CTR * 10000 / 100).

LFXV2-1220

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
LFXV2-1220

- Replace complex web activities CTE with Platinum views
- Add ROAS as primary metric for paid social card
- Move recommended actions and insights to first fold in drawers
- Add ROAS trend chart to paid social drawer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
@mrautela365 mrautela365 requested a review from jordane as a code owner March 14, 2026 00:53
Copilot AI review requested due to automatic review settings March 14, 2026 00:53
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 14, 2026

Walkthrough

Adds four marketing analytics drawers (Website Visits, Email CTR, Paid Social Reach, Social Media), frontend service methods and template wiring, backend routes/controllers and ProjectService methods to supply analytics data, and shared interfaces/constants/enum updates to integrate the drawers into Marketing Overview.

Changes

Cohort / File(s) Summary
Frontend — Drawer Components
apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/..., .../email-ctr-drawer/..., .../paid-social-reach-drawer/..., .../social-media-drawer/...
Adds four drawer components (TS + HTML). Each exposes visible and data signals/inputs, computes chart datasets/options, includes static recommended actions/key insights, and implements close handlers.
Frontend — Marketing Overview
apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/...
Refactors MarketingOverview to use signals and forkJoin to fetch web/email/social endpoints, transforms responses into marketingCards, and wires drawer open/close interactions and the new drawer components in the template.
Frontend — Analytics Service
apps/lfx-one/src/app/shared/services/analytics.service.ts
Adds getWebActivitiesSummary, getEmailCtr, getSocialReach methods performing HTTP GETs and returning sanitized defaults on error. Note: these method implementations appear duplicated in-file (likely merge artifact).
Backend — Routes & Controller
apps/lfx-one/src/server/routes/analytics.route.ts, apps/lfx-one/src/server/controllers/analytics.controller.ts
Adds three GET routes and corresponding controller handlers (/web-activities-summary, /email-ctr, /social-reach) that validate foundationSlug, call ProjectService, log summary metrics, and return JSON responses.
Backend — Project Service
apps/lfx-one/src/server/services/project.service.ts
Adds getWebActivitiesSummary, getEmailCtr, getSocialReach which run Snowflake queries and assemble structured response objects for the API endpoints; exports related interfaces.
Shared Types & Constants
packages/shared/src/interfaces/analytics-data.interface.ts, packages/shared/src/interfaces/dashboard-metric.interface.ts, packages/shared/src/constants/dashboard-metrics.constants.ts
Introduces analytics interfaces (WebActivitiesSummaryResponse, EmailCtrResponse, SocialReachResponse, and related rows/groups), extends DashboardDrawerType with four marketing values, and updates MARKETING_OVERVIEW_METRICS to use EMPTY_CHART_DATA and the new drawer types.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant MktComp as Marketing Overview<br/>(Frontend)
    participant AnalyticsService as Analytics Service<br/>(Frontend)
    participant AnalyticsController as Analytics Controller<br/>(Backend)
    participant ProjectService as Project Service
    participant Snowflake as Snowflake DB
    participant Drawer as Drawer Component

    User->>MktComp: Load Marketing Overview
    MktComp->>AnalyticsService: forkJoin([getWebActivitiesSummary(), getEmailCtr(), getSocialReach()]) with foundationSlug

    par Web Activities
        AnalyticsService->>AnalyticsController: GET /web-activities-summary
        AnalyticsController->>ProjectService: getWebActivitiesSummary()
        ProjectService->>Snowflake: Query web activity metrics
        Snowflake-->>ProjectService: Aggregated results
        ProjectService-->>AnalyticsController: WebActivitiesSummaryResponse
        AnalyticsController-->>AnalyticsService: HTTP 200
    and Email CTR
        AnalyticsService->>AnalyticsController: GET /email-ctr
        AnalyticsController->>ProjectService: getEmailCtr()
        ProjectService->>Snowflake: Query email CTR metrics
        Snowflake-->>ProjectService: Aggregated results
        ProjectService-->>AnalyticsController: EmailCtrResponse
        AnalyticsController-->>AnalyticsService: HTTP 200
    and Social Reach
        AnalyticsService->>AnalyticsController: GET /social-reach
        AnalyticsController->>ProjectService: getSocialReach()
        ProjectService->>Snowflake: Query social reach metrics
        Snowflake-->>ProjectService: Aggregated results
        ProjectService-->>AnalyticsController: SocialReachResponse
        AnalyticsController-->>AnalyticsService: HTTP 200
    end

    AnalyticsService-->>MktComp: Responses collected
    MktComp->>MktComp: Transform data -> marketingCards
    User->>MktComp: Click metric card
    MktComp->>MktComp: set activeDrawer(drawerType)
    MktComp->>Drawer: Render drawer with bound data
    Drawer->>Drawer: Compute chart data
    User->>Drawer: Close drawer
    Drawer->>MktComp: visibleChange -> handleDrawerClose()
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat(dashboards): add ROAS metrics and drawer improvements' clearly and concisely summarizes the main changes: adding ROAS metrics and improving dashboard drawers, which aligns with the substantial feature additions across multiple drawer components and metric enhancements.
Description check ✅ Passed The pull request description is directly related to the changeset, providing a clear summary of the primary changes including ROAS metrics addition, Platinum views optimization, drawer improvements, and a comprehensive test plan that maps to the actual modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/LFXV2-1220-ed-marketing-preview
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds marketing dashboard plumbing (backend + shared types + Angular UI) to support web activities, email CTR, and paid social ROAS drill-down experiences.

Changes:

  • Introduces new shared interfaces and drawer types for marketing analytics responses and drawers.
  • Adds new backend analytics endpoints and Snowflake queries for web activities summary, email CTR, and paid social reach/ROAS.
  • Updates the Executive Director marketing overview to load live data, render metric cards, and open drill-down drawers.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/shared/src/interfaces/dashboard-metric.interface.ts Adds new marketing DashboardDrawerType enum values.
packages/shared/src/interfaces/analytics-data.interface.ts Adds shared response/type definitions for marketing analytics (web, email, paid social, social media).
packages/shared/src/constants/dashboard-metrics.constants.ts Replaces mock marketing metric card data with empty placeholders + drawer wiring.
apps/lfx-one/src/server/services/project.service.ts Implements Snowflake queries and response shaping for new marketing analytics endpoints.
apps/lfx-one/src/server/routes/analytics.route.ts Registers new /web-activities-summary, /email-ctr, /social-reach routes.
apps/lfx-one/src/server/controllers/analytics.controller.ts Adds controller handlers and validation for new marketing endpoints.
apps/lfx-one/src/app/shared/services/analytics.service.ts Adds Angular service methods to call the new marketing endpoints with fallback defaults.
apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.ts New website visits drawer component (charts + first-fold actions/insights).
apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html Drawer template for website visits.
apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.ts New paid social drawer with ROAS + spend/revenue + charts.
apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.html Drawer template for paid social reach/ROAS.
apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/marketing-overview.component.ts Loads marketing data, transforms cards, and manages drawer state.
apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/marketing-overview.component.html Adds key insights card and mounts drill-down drawer components.
apps/lfx-one/src/app/modules/dashboards/executive-director/components/email-ctr-drawer/email-ctr-drawer.component.ts New email CTR drawer component (charts + first-fold actions/insights).
apps/lfx-one/src/app/modules/dashboards/executive-director/components/email-ctr-drawer/email-ctr-drawer.component.html Drawer template for email CTR.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/lfx-one/src/server/services/project.service.ts
Comment thread apps/lfx-one/src/server/services/project.service.ts
Comment thread apps/lfx-one/src/server/services/project.service.ts
Comment thread apps/lfx-one/src/server/services/project.service.ts
Comment thread apps/lfx-one/src/server/services/project.service.ts
Comment thread apps/lfx-one/src/server/services/project.service.ts
LFXV2-1220

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
Copy link
Copy Markdown
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 (3)
apps/lfx-one/src/server/services/project.service.ts (1)

1825-1825: Add JSDoc for getSocialReach public method.

This method is public but undocumented, unlike neighboring public APIs.

As per coding guidelines: Add JSDoc comments for public functions and exported modules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/lfx-one/src/server/services/project.service.ts` at line 1825, Add a
JSDoc comment block for the public method getSocialReach to document its
purpose, parameters and return type; specifically add a brief description of
what getSocialReach does, a `@param` {string} foundationSlug description, a
`@returns` {Promise<SocialReachResponse>} description of the response shape (and
mention any relevant fields), and any `@throws` or error behavior if applicable,
placing the block immediately above the getSocialReach method declaration so it
matches neighboring public API docs.
apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/marketing-overview.component.ts (1)

108-129: Consider using an enum or constant keys instead of title string matching.

Matching cards by card.title === 'Website Visits' is fragile - if titles change, this logic breaks silently. Consider using the drawerType enum which is already defined on each card.

♻️ Suggested improvement using drawerType
 private initMarketingCards(): Signal<DashboardMetricCard[]> {
   return computed(() => {
     const { webActivities, emailCtr, socialReach } = this.marketingData();
     const loading = this.marketingDataLoading();

     return MARKETING_OVERVIEW_METRICS.map((card) => {
-      if (card.title === 'Website Visits') {
+      if (card.drawerType === DashboardDrawerType.MarketingWebsiteVisits) {
         return this.transformWebsiteVisits(card, webActivities, loading);
       }
-      if (card.title === 'Email CTR') {
+      if (card.drawerType === DashboardDrawerType.MarketingEmailCtr) {
         return this.transformEmailCtr(card, emailCtr, loading);
       }
-      if (card.title === 'Paid Social Reach') {
+      if (card.drawerType === DashboardDrawerType.MarketingPaidSocialReach) {
         return this.transformSocialReach(card, socialReach, loading);
       }
-      if (card.title === 'Social Media') {
+      if (card.drawerType === DashboardDrawerType.MarketingSocialMedia) {
         return this.transformSocialMedia(card);
       }
       return card;
     });
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/marketing-overview.component.ts`
around lines 108 - 129, The current initMarketingCards function matches cards by
fragile title strings; update it to use the card.drawerType enum/key instead of
card.title so changes to display text won't break logic. In initMarketingCards
(and where MARKETING_OVERVIEW_METRICS is mapped), replace each title comparison
with checks against the drawerType values (e.g., compare card.drawerType to the
enum/constant for WebsiteVisits, EmailCtr, PaidSocialReach, SocialMedia) and
call the same transformers (transformWebsiteVisits, transformEmailCtr,
transformSocialReach, transformSocialMedia) based on those drawerType cases.
apps/lfx-one/src/server/controllers/analytics.controller.ts (1)

1735-1760: Missing JSDoc comment for getSocialReach method.

For consistency with the other new methods (getWebActivitiesSummary, getEmailCtr) and the coding guidelines requiring JSDoc comments for public functions, add documentation.

📝 Add JSDoc comment
+  /**
+   * GET /api/analytics/social-reach
+   * Get social reach data including ROAS metrics
+   */
   public async getSocialReach(req: Request, res: Response, next: NextFunction): Promise<void> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/lfx-one/src/server/controllers/analytics.controller.ts` around lines
1735 - 1760, Add a JSDoc block above the public method getSocialReach describing
its purpose (fetch social reach for a foundation), document parameters (req:
Request, res: Response, next: NextFunction), the return type (Promise<void>),
and any thrown errors (e.g., ServiceValidationError when foundationSlug is
missing); follow the same tag/style used by getWebActivitiesSummary and
getEmailCtr (include `@public`, `@param`, `@returns`, and `@throws`) so the method
documentation is consistent with other public controller methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/marketing-overview.component.ts`:
- Around line 23-26: The import SocialMediaDrawerComponent is unresolved and
causes build failure; either add the missing component file and export
SocialMediaDrawerComponent from
../social-media-drawer/social-media-drawer.component.ts, or remove the broken
import line and remove SocialMediaDrawerComponent from the component's imports
array (the imports list that currently includes EmailCtrDrawerComponent,
PaidSocialReachDrawerComponent, SocialMediaDrawerComponent,
WebsiteVisitsDrawerComponent) so the code no longer references the missing
symbol.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.ts`:
- Around line 6-10: Update the standalone component imports to avoid barrel
exports: replace the lfxColors import from '@lfx-one/shared/constants' with a
direct import from '@lfx-one/shared/constants/colors.constants' and replace the
types SocialReachResponse, MarketingRecommendedAction, MarketingKeyInsight
(currently imported from '@lfx-one/shared/interfaces') with direct imports from
'@lfx-one/shared/interfaces/analytics-data.interface'; update the import
statements that reference these symbols (lfxColors, SocialReachResponse,
MarketingRecommendedAction, MarketingKeyInsight) accordingly so the component
imports the specific module files rather than the package barrel.

In `@apps/lfx-one/src/server/services/project.service.ts`:
- Around line 1754-1758: The current EXISTS subquery in project.service.ts uses
bidirectional ILIKE '%name%' (e.g., the block comparing fp.NAME and
em.PROJECT_NAME) which causes fuzzy matches and metric bleed; replace these
substring comparisons with deterministic exact-key joins—prefer using a stable
identifier such as fp.id or fp.slug matched to em.project_id or em.project_slug
(or a normalized exact key like lower(fp.slug) = lower(em.project_slug)) in the
EXISTS clause, and apply the same change to the other occurrences referenced
(the blocks used by getSocialReach and the ranges noted) so all project matching
uses exact/normalized equality instead of ILIKE.

---

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/marketing-overview.component.ts`:
- Around line 108-129: The current initMarketingCards function matches cards by
fragile title strings; update it to use the card.drawerType enum/key instead of
card.title so changes to display text won't break logic. In initMarketingCards
(and where MARKETING_OVERVIEW_METRICS is mapped), replace each title comparison
with checks against the drawerType values (e.g., compare card.drawerType to the
enum/constant for WebsiteVisits, EmailCtr, PaidSocialReach, SocialMedia) and
call the same transformers (transformWebsiteVisits, transformEmailCtr,
transformSocialReach, transformSocialMedia) based on those drawerType cases.

In `@apps/lfx-one/src/server/controllers/analytics.controller.ts`:
- Around line 1735-1760: Add a JSDoc block above the public method
getSocialReach describing its purpose (fetch social reach for a foundation),
document parameters (req: Request, res: Response, next: NextFunction), the
return type (Promise<void>), and any thrown errors (e.g., ServiceValidationError
when foundationSlug is missing); follow the same tag/style used by
getWebActivitiesSummary and getEmailCtr (include `@public`, `@param`, `@returns`, and
`@throws`) so the method documentation is consistent with other public controller
methods.

In `@apps/lfx-one/src/server/services/project.service.ts`:
- Line 1825: Add a JSDoc comment block for the public method getSocialReach to
document its purpose, parameters and return type; specifically add a brief
description of what getSocialReach does, a `@param` {string} foundationSlug
description, a `@returns` {Promise<SocialReachResponse>} description of the
response shape (and mention any relevant fields), and any `@throws` or error
behavior if applicable, placing the block immediately above the getSocialReach
method declaration so it matches neighboring public API docs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b15dd87-3d78-4de4-ba84-98ab76154135

📥 Commits

Reviewing files that changed from the base of the PR and between bef3965 and 020fc56.

📒 Files selected for processing (15)
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/email-ctr-drawer/email-ctr-drawer.component.html
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/email-ctr-drawer/email-ctr-drawer.component.ts
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/marketing-overview.component.html
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/marketing-overview/marketing-overview.component.ts
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.html
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.ts
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.html
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/website-visits-drawer/website-visits-drawer.component.ts
  • apps/lfx-one/src/app/shared/services/analytics.service.ts
  • apps/lfx-one/src/server/controllers/analytics.controller.ts
  • apps/lfx-one/src/server/routes/analytics.route.ts
  • apps/lfx-one/src/server/services/project.service.ts
  • packages/shared/src/constants/dashboard-metrics.constants.ts
  • packages/shared/src/interfaces/analytics-data.interface.ts
  • packages/shared/src/interfaces/dashboard-metric.interface.ts

Comment thread apps/lfx-one/src/server/services/project.service.ts
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 14, 2026

🚀 Deployment Status

Your branch has been deployed to: https://ui-pr-315.dev.v2.cluster.linuxfound.info

Deployment Details:

  • Environment: Development
  • Namespace: ui-pr-315
  • ArgoCD App: ui-pr-315

The deployment will be automatically removed when this PR is closed.

Copy link
Copy Markdown
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.

🧹 Nitpick comments (4)
apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.ts (3)

94-99: Inconsistent number formatting between chart ticks and formatNumber() method.

The chart tick callbacks use .toFixed(0) for thousands (e.g., 10K), while formatNumber() uses .toFixed(1) (e.g., 10.0K). This creates visual inconsistency between axis labels and tooltips/stat displays.

Consider extracting a shared formatting utility or aligning the precision:

♻️ Proposed fix to unify formatting
   protected formatNumber(num: number): string {
     if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
-    if (num >= 1_000) return `${(num / 1_000).toFixed(1)}K`;
+    if (num >= 1_000) return `${(num / 1_000).toFixed(0)}K`;
     return num.toLocaleString();
   }

Or adjust the chart callbacks to use .toFixed(1) for consistency with the method.

Also applies to: 135-140, 160-164

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.ts`
around lines 94 - 99, The chart tick callbacks (the callback functions
formatting axis ticks) are using .toFixed(0) while the formatNumber() method
uses .toFixed(1), causing inconsistent labels; update the code to use a single
shared formatting utility or change the tick callbacks to call formatNumber()
(or mirror its .toFixed(1) precision) so axis ticks and tooltips/stat displays
match—apply this fix to the callback blocks around the shown snippet and the
similar callback occurrences referenced (around the other callback ranges).

194-200: Limited color palette may cause visual issues with more than 5 platforms.

The backgroundColor array only contains 5 colors. If the data includes more than 5 platforms, Chart.js will cycle through the array, causing duplicate colors that may confuse users when interpreting the chart.

♻️ Proposed fix to generate sufficient colors dynamically
   private initPlatformChartData(): Signal<ChartData<'bar'>> {
     return computed(() => {
       const { platforms } = this.data();
       const sorted = [...platforms].sort((a, b) => b.followers - a.followers);
+      const blueShades = [
+        lfxColors.blue[700],
+        lfxColors.blue[600],
+        lfxColors.blue[500],
+        lfxColors.blue[400],
+        lfxColors.blue[300],
+        lfxColors.blue[200],
+        lfxColors.blue[100],
+      ];
       return {
         labels: sorted.map((p) => p.platform),
         datasets: [
           {
             data: sorted.map((p) => p.followers),
-            backgroundColor: [lfxColors.blue[700], lfxColors.blue[500], lfxColors.blue[400], lfxColors.blue[300], lfxColors.blue[200]],
+            backgroundColor: sorted.map((_, i) => blueShades[i % blueShades.length]),
             borderRadius: { topLeft: 0, bottomLeft: 0, topRight: 4, bottomRight: 4 },
             borderSkipped: 'start',
           },
         ],
       };
     });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.ts`
around lines 194 - 200, The backgroundColor array is fixed to 5 entries and will
repeat when there are more than 5 platforms; change the chart dataset to compute
backgroundColor dynamically (use the existing sorted array length) instead of
the hardcoded array. Add a helper (e.g., getColorsForCount(count: number)) in
the social-media-drawer.component that takes sorted.length and returns an array
of colors by first using colors from lfxColors (blue variants) and, if more are
needed, generating/interpolating additional shades or extending the palette so
each data item gets a unique color; then set datasets[0].backgroundColor =
getColorsForCount(sorted.length) where the chart is created. Ensure
getColorsForCount is used wherever the dataset config is built so the chart
always has a color per platform.

31-53: Hardcoded dummy data should be replaced with real data from the input.

The recommendedActions and keyInsights arrays are hardcoded placeholders. Per the PR objectives, recommended actions and key insights should be populated from actual analytics data. Consider adding these to the SocialMediaResponse interface and the data input, or clearly mark this as a TODO for follow-up.

Do you want me to open an issue to track replacing this dummy data with backend-driven content?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.ts`
around lines 31 - 53, The component currently uses hardcoded arrays
recommendedActions and keyInsights; replace these with real data by extending
the SocialMediaResponse interface to include recommendedActions and keyInsights
fields, update the component to read them from the existing data input (e.g.,
this.data.recommendedActions / this.data.keyInsights) with safe fallbacks (empty
arrays) and remove the hardcoded dummy arrays from the
SocialMediaDrawerComponent; if backend fields are not yet available, add clear
TODO comments on SocialMediaResponse and the data input to ensure these props
are populated later.
apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html (1)

44-48: Consider using LFX design token color classes for trend indicators.

The template uses standard Tailwind text-green-600 and text-red-600, while the TypeScript component uses lfxColors.emerald and lfxColors.red for chart styling. Per the coding guidelines, styling should use LFX design tokens for consistency.

If your Tailwind configuration maps LFX tokens to utility classes (e.g., text-emerald-600), consider updating to maintain visual consistency with the charts.

♻️ Suggested alignment with LFX tokens
-          <span class="text-2xl font-semibold" [class]="data().trend === 'up' ? 'text-green-600' : 'text-red-600'">
+          <span class="text-2xl font-semibold" [class]="data().trend === 'up' ? 'text-emerald-600' : 'text-red-600'">
             {{ data().changePercentage > 0 ? '+' : '' }}{{ data().changePercentage }}%
           </span>
-          <i class="text-sm" [class]="data().trend === 'up' ? 'fa-light fa-arrow-up text-green-600' : 'fa-light fa-arrow-down text-red-600'"></i>
+          <i class="text-sm" [class]="data().trend === 'up' ? 'fa-light fa-arrow-up text-emerald-600' : 'fa-light fa-arrow-down text-red-600'"></i>

As per coding guidelines: "Styling should use Tailwind CSS with PrimeUI plugin integration and LFX design tokens (colors, font-sizes)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html`
around lines 44 - 48, Replace the hardcoded Tailwind color classes in the
template's trend indicator with the LFX design token classes used by the
component (e.g., use the mapped utility classes corresponding to
lfxColors.emerald and lfxColors.red such as text-emerald-600/text-red-600 or
your project's LFX token names). Update the two [class] bindings on the span and
i elements that reference data().trend and data().changePercentage so they apply
the LFX token utility classes instead of text-green-600/text-red-600, ensuring
the visual tokens match the TypeScript chart styling in the SocialMediaDrawer
component.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html`:
- Around line 44-48: Replace the hardcoded Tailwind color classes in the
template's trend indicator with the LFX design token classes used by the
component (e.g., use the mapped utility classes corresponding to
lfxColors.emerald and lfxColors.red such as text-emerald-600/text-red-600 or
your project's LFX token names). Update the two [class] bindings on the span and
i elements that reference data().trend and data().changePercentage so they apply
the LFX token utility classes instead of text-green-600/text-red-600, ensuring
the visual tokens match the TypeScript chart styling in the SocialMediaDrawer
component.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.ts`:
- Around line 94-99: The chart tick callbacks (the callback functions formatting
axis ticks) are using .toFixed(0) while the formatNumber() method uses
.toFixed(1), causing inconsistent labels; update the code to use a single shared
formatting utility or change the tick callbacks to call formatNumber() (or
mirror its .toFixed(1) precision) so axis ticks and tooltips/stat displays
match—apply this fix to the callback blocks around the shown snippet and the
similar callback occurrences referenced (around the other callback ranges).
- Around line 194-200: The backgroundColor array is fixed to 5 entries and will
repeat when there are more than 5 platforms; change the chart dataset to compute
backgroundColor dynamically (use the existing sorted array length) instead of
the hardcoded array. Add a helper (e.g., getColorsForCount(count: number)) in
the social-media-drawer.component that takes sorted.length and returns an array
of colors by first using colors from lfxColors (blue variants) and, if more are
needed, generating/interpolating additional shades or extending the palette so
each data item gets a unique color; then set datasets[0].backgroundColor =
getColorsForCount(sorted.length) where the chart is created. Ensure
getColorsForCount is used wherever the dataset config is built so the chart
always has a color per platform.
- Around line 31-53: The component currently uses hardcoded arrays
recommendedActions and keyInsights; replace these with real data by extending
the SocialMediaResponse interface to include recommendedActions and keyInsights
fields, update the component to read them from the existing data input (e.g.,
this.data.recommendedActions / this.data.keyInsights) with safe fallbacks (empty
arrays) and remove the hardcoded dummy arrays from the
SocialMediaDrawerComponent; if backend fields are not yet available, add clear
TODO comments on SocialMediaResponse and the data input to ensure these props
are populated later.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 625d3908-5f6a-49a5-99bb-5082fc4c4f99

📥 Commits

Reviewing files that changed from the base of the PR and between 020fc56 and f1e8c5d.

📒 Files selected for processing (2)
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.ts

LFXV2-1220

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
Copy link
Copy Markdown
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 (1)
apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html (1)

15-24: Use LFX tokenized color classes instead of raw Tailwind palette utilities.

This template hard-codes text-gray-*, bg-gray-*, border-gray-*, text-green-*, text-red-*, text-blue-*, and text-amber-* throughout the drawer. Please swap those for the PrimeUI/LFX token utilities used elsewhere so the drawer stays themeable and consistent. As per coding guidelines, "Styling should use Tailwind CSS with PrimeUI plugin integration and LFX design tokens (colors, font-sizes)".

Also applies to: 33-47, 55-80, 89-103, 111-169

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html`
around lines 15 - 24, Replace all raw Tailwind color utilities in
social-media-drawer.component.html (e.g., text-gray-*, bg-gray-*, border-gray-*,
text-green-*, text-red-*, text-blue-*, text-amber-*) with the project’s
LFX/PrimeUI token classes used elsewhere so the drawer remains themeable; update
class attributes on elements such as the header h2
(data-testid="social-media-drawer-title"), the close button
(data-testid="social-media-drawer-close"), and all list/item/status elements
referenced in this template (lines noted in the review: ~15-24, 33-47, 55-80,
89-103, 111-169) to use the equivalent tokenized utilities (replace
text-gray-900 -> e.g., lfx-text-primary or the project token for primary text,
text-gray-500 -> lfx-text-muted, bg-gray-100 -> lfx-bg-surface, border-gray-* ->
lfx-border, text-green-* / text-red-* / text-blue-* / text-amber-* ->
lfx-text-success/error/info/warn respectively) so the class names match the
PrimeUI plugin tokens used across the app.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html`:
- Around line 44-47: The template currently treats any non-'up' trend as a
decline; update the bindings that use data().trend and data().changePercentage
in social-media-drawer.component (the span and i elements) to handle a neutral
state when changePercentage === 0: derive a neutral branch (e.g., treat trend
=== 'neutral' or compute data().changePercentage === 0) and adjust the class
binding and icon selection accordingly so zero shows a neutral color and neutral
icon (instead of red/down) — replace the simple ternaries with a three-way
conditional or ngClass/ngIf that distinguishes 'up', 'neutral' (0%), and 'down'.

---

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html`:
- Around line 15-24: Replace all raw Tailwind color utilities in
social-media-drawer.component.html (e.g., text-gray-*, bg-gray-*, border-gray-*,
text-green-*, text-red-*, text-blue-*, text-amber-*) with the project’s
LFX/PrimeUI token classes used elsewhere so the drawer remains themeable; update
class attributes on elements such as the header h2
(data-testid="social-media-drawer-title"), the close button
(data-testid="social-media-drawer-close"), and all list/item/status elements
referenced in this template (lines noted in the review: ~15-24, 33-47, 55-80,
89-103, 111-169) to use the equivalent tokenized utilities (replace
text-gray-900 -> e.g., lfx-text-primary or the project token for primary text,
text-gray-500 -> lfx-text-muted, bg-gray-100 -> lfx-bg-surface, border-gray-* ->
lfx-border, text-green-* / text-red-* / text-blue-* / text-amber-* ->
lfx-text-success/error/info/warn respectively) so the class names match the
PrimeUI plugin tokens used across the app.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 28b9f727-ca9d-4ee9-a012-59ae49e58ae0

📥 Commits

Reviewing files that changed from the base of the PR and between f1e8c5d and 5419497.

📒 Files selected for processing (1)
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/social-media-drawer/social-media-drawer.component.html

LFXV2-1220

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
Copy link
Copy Markdown
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 (1)
apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.html (1)

13-24: Use LFX token utilities instead of raw Tailwind palette/font-size classes.

This starts in the header and repeats throughout the template with utilities like text-gray-*, border-gray-*, bg-*, text-sm, text-lg, and text-2xl. Please switch these to the PrimeUI/LFX tokenized classes used elsewhere so theme overrides stay consistent.

As per coding guidelines, "Styling should use Tailwind CSS with PrimeUI plugin integration and LFX design tokens (colors, font-sizes)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.html`
around lines 13 - 24, The template uses raw Tailwind utilities (e.g.,
text-gray-*, bg-*, border-*, text-sm, text-lg, text-2xl) in the
PaidSocialReachDrawer component header (look for
data-testid="paid-social-reach-drawer-header", the <h2> with
data-testid="paid-social-reach-drawer-title", the subtitle <p>, the close
<button> with (click)="onClose()", and the <i> icon); update those class
attributes to use the project's PrimeUI/LFX tokenized utility classes instead of
raw palette/font-size classes so theme overrides apply consistently (replace
text-gray-500/text-gray-900 with the equivalent LFX text color tokens, swap
text-sm/text-lg/text-xl with the LFX font-size tokens, and replace
bg-*/hover:bg-* and any border colors with the LFX background/border token
classes), keeping existing structure and data-testid attributes intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.html`:
- Around line 46-64: The template in paid-social-reach-drawer.component.html
only renders Total Impressions and either ROAS or Month-over-Month but never
shows Revenue; update the drawer template to include a Revenue stat block
alongside Total Impressions (and ensure it appears in both branches of the
conditional that render ROAS/changePercentage), using the existing helpers like
formatNumber(data().revenue) and referencing data().revenue so the summary
surfaces revenue alongside ROAS and/or impressions; ensure the new block follows
the same markup pattern (flex-1 card with label and formatted value) and that
any conditional logic that previously returned two cards now returns three when
revenue is present.
- Around line 33-50: The template currently hides the ROAS block when
data().roas === 0 because the conditional uses data().roas > 0; update the
conditional in paid-social-reach-drawer.component.html to include zero (e.g.,
use data().roas >= 0 or simply remove the >0 check) so the ROAS metric
(displayed via data().roas.toFixed(2) + 'x') is always shown even when it equals
0.00x; keep the existing changePercentage and trend logic unchanged so the +/-%
indicator still only appears when data().changePercentage !== 0.
- Around line 55-62: The template currently treats any trend !== 'up' as
negative, causing 0% to render as down; update the bindings in
paid-social-reach-drawer.component.html to explicitly handle three states (up,
down, flat/neutral) using data().trend and data().changePercentage: change the
class binding on the percentage span and the icon binding to use a conditional
that checks for 'up' => green/up, 'down' => red/down, else => neutral (e.g.,
gray and a neutral icon or no icon), and adjust the displayed sign so you only
prefix '+' for values > 0 (no sign for 0). Target the two affected elements: the
percentage span and the <i> icon.

---

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.html`:
- Around line 13-24: The template uses raw Tailwind utilities (e.g.,
text-gray-*, bg-*, border-*, text-sm, text-lg, text-2xl) in the
PaidSocialReachDrawer component header (look for
data-testid="paid-social-reach-drawer-header", the <h2> with
data-testid="paid-social-reach-drawer-title", the subtitle <p>, the close
<button> with (click)="onClose()", and the <i> icon); update those class
attributes to use the project's PrimeUI/LFX tokenized utility classes instead of
raw palette/font-size classes so theme overrides apply consistently (replace
text-gray-500/text-gray-900 with the equivalent LFX text color tokens, swap
text-sm/text-lg/text-xl with the LFX font-size tokens, and replace
bg-*/hover:bg-* and any border colors with the LFX background/border token
classes), keeping existing structure and data-testid attributes intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 16112cea-57c3-4d50-b9da-a90c55a7b970

📥 Commits

Reviewing files that changed from the base of the PR and between 5419497 and 957a21e.

📒 Files selected for processing (1)
  • apps/lfx-one/src/app/modules/dashboards/executive-director/components/paid-social-reach-drawer/paid-social-reach-drawer.component.html

Copy link
Copy Markdown
Contributor

@MRashad26 MRashad26 left a comment

Choose a reason for hiding this comment

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

Code Review: Marketing Analytics Dashboard

This PR adds marketing analytics capabilities (web activities, email CTR, paid social reach, social media) with new backend endpoints and frontend drawer components. While the feature scope is well-defined, there are 5 critical and 16 major issues that must be addressed before merging.

Critical Issues (5)

  • S-1: New Snowflake queries use schemas (ANALYTICS.PLATINUM.*, ANALYTICS.SILVER_DIM.*) with zero precedent in this codebase
  • S-2: Bidirectional ILIKE fuzzy join will produce false positive matches
  • S-3: getWebActivitiesSummary passes foundation slug to PROJECT_SLUG filter (semantic mismatch)
  • S-4: Insufficient input validation on foundationSlug enables ILIKE pattern manipulation
  • Q-1: Hardcoded dummy data (fake Twitter 125K followers, etc.) will display as real data in production

Major Issues (16)

  • S-5: No visible auth middleware on new analytics routes exposing financial data
  • S-6: "Recursive" CTE only fetches 2 levels of project nesting
  • S-7: Financial data (spend/revenue) exposed without data classification review
  • A-1: Missing OnPush change detection on all new components
  • A-2: toSignal pipeline missing finalize safety net for loading state
  • A-3: Outer catchError is dead code (inner catches prevent it from firing)
  • A-4: Drawer [visible] one-way binding will desync on overlay close
  • C-1 to C-6: Multiple instances of raw HTML elements instead of shared components (lfx-button, lfx-table, lfx-tag, lfx-message, lfx-card)
  • Q-2 to Q-4: Significant code duplication (formatNumber x4, chart options 800+ lines, template markup x4)

Comment thread apps/lfx-one/src/server/services/project.service.ts
Comment thread apps/lfx-one/src/server/services/project.service.ts
Comment thread apps/lfx-one/src/server/services/project.service.ts
Comment thread apps/lfx-one/src/server/controllers/analytics.controller.ts
@mrautela365
Copy link
Copy Markdown
Contributor Author

Closing — superseded by the LFXV2-1468 per-card split. ROAS metrics now live in #441 (revenue impact card); drawer improvements are covered by #434, #435, #436, and the new drawers in #438-#441. See #443 for the dashboard shell.

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