-
Notifications
You must be signed in to change notification settings - Fork 0
feat(dashboards): add Snowflake APIs for marketing #346
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
base: feat/LFXV2-1220-pr1-persona-types
Are you sure you want to change the base?
Changes from all commits
3982c0b
989c266
008e138
88f019b
45588ee
a4c73f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1668,4 +1668,125 @@ export class AnalyticsController { | |
| next(error); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/analytics/web-activities-summary | ||
| * Get web activities summary grouped by domain category | ||
| */ | ||
| public async getWebActivitiesSummary(req: Request, res: Response, next: NextFunction): Promise<void> { | ||
| const startTime = logger.startOperation(req, 'get_web_activities_summary'); | ||
|
|
||
| try { | ||
| const foundationSlug = req.query['foundationSlug'] as string | undefined; | ||
|
|
||
| if (!foundationSlug) { | ||
| throw ServiceValidationError.forField('foundationSlug', 'foundationSlug query parameter is required', { | ||
| operation: 'get_web_activities_summary', | ||
| }); | ||
| } | ||
|
|
||
| const response = await this.projectService.getWebActivitiesSummary(foundationSlug); | ||
|
|
||
| logger.success(req, 'get_web_activities_summary', startTime, { | ||
| foundation_slug: foundationSlug, | ||
| total_sessions: response.totalSessions, | ||
| domain_groups_count: response.domainGroups.length, | ||
| daily_data_points: response.dailyData.length, | ||
| }); | ||
|
|
||
| res.json(response); | ||
| } catch (error) { | ||
| logger.error(req, 'get_web_activities_summary', startTime, error); | ||
| next(error); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/analytics/email-ctr | ||
| * Get email click-through rate data | ||
| */ | ||
| public async getEmailCtr(req: Request, res: Response, next: NextFunction): Promise<void> { | ||
| const startTime = logger.startOperation(req, 'get_email_ctr'); | ||
|
|
||
| try { | ||
| const foundationName = req.query['foundationName'] as string | undefined; | ||
|
|
||
| if (!foundationName) { | ||
| throw ServiceValidationError.forField('foundationName', 'foundationName query parameter is required', { | ||
| operation: 'get_email_ctr', | ||
| }); | ||
| } | ||
mrautela365 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const response = await this.projectService.getEmailCtr(foundationName); | ||
|
|
||
| logger.success(req, 'get_email_ctr', startTime, { | ||
| foundation_name: foundationName, | ||
| current_ctr: response.currentCtr, | ||
| monthly_data_points: response.monthlyData.length, | ||
| }); | ||
|
|
||
| res.json(response); | ||
| } catch (error) { | ||
| logger.error(req, 'get_email_ctr', startTime, error); | ||
| next(error); | ||
| } | ||
| } | ||
|
|
||
| public async getSocialReach(req: Request, res: Response, next: NextFunction): Promise<void> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 Blocker:
|
||
| const startTime = logger.startOperation(req, 'get_social_reach'); | ||
|
|
||
| try { | ||
| const foundationName = req.query['foundationName'] as string | undefined; | ||
|
|
||
| if (!foundationName) { | ||
| throw ServiceValidationError.forField('foundationName', 'foundationName query parameter is required', { | ||
| operation: 'get_social_reach', | ||
| }); | ||
| } | ||
|
|
||
| const response = await this.projectService.getSocialReach(foundationName); | ||
|
|
||
| logger.success(req, 'get_social_reach', startTime, { | ||
| foundation_name: foundationName, | ||
| total_reach: response.totalReach, | ||
| monthly_data_points: response.monthlyData.length, | ||
| }); | ||
|
|
||
| res.json(response); | ||
| } catch (error) { | ||
| logger.error(req, 'get_social_reach', startTime, error); | ||
| next(error); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * GET /api/analytics/social-media | ||
| * Get social media metrics from Snowflake Platinum tables | ||
| */ | ||
| public async getSocialMedia(req: Request, res: Response, next: NextFunction): Promise<void> { | ||
| const startTime = logger.startOperation(req, 'get_social_media'); | ||
|
|
||
| try { | ||
| const foundationName = req.query['foundationName'] as string | undefined; | ||
|
|
||
| if (!foundationName) { | ||
| throw ServiceValidationError.forField('foundationName', 'foundationName query parameter is required', { | ||
| operation: 'get_social_media', | ||
| }); | ||
| } | ||
mrautela365 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const response = await this.projectService.getSocialMedia(foundationName); | ||
|
|
||
| logger.success(req, 'get_social_media', startTime, { | ||
| foundation_name: foundationName, | ||
| total_followers: response.totalFollowers, | ||
| platforms_count: response.platforms.length, | ||
| }); | ||
|
|
||
| res.json(response); | ||
| } catch (error) { | ||
| logger.error(req, 'get_social_media', startTime, error); | ||
| next(error); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚫 Blocker: Inconsistent parameter naming —
foundationSlugvsfoundationNameacross the 4 endpointsProblem:
getWebActivitiesSummaryacceptsfoundationSlug(filtering byPROJECT_SLUG), while the other 3 endpoints acceptfoundationName(filtering byFOUNDATION_NAME/PROJECT_NAME). The frontend (PR #347) calls them as:Why it's a problem:
foundation.slugandfoundation.nameare different values. If a foundation's display name changes but slug doesn't (or vice versa), one endpoint breaks while the other works.PROJECT_NAME(line 1739, 1751, 1762) but the parameter is calledfoundationName— this is semantically confusing since projects ≠ foundations.Fix: Align all 4 endpoints on a single identifier. If the Snowflake tables use different filter columns, document why — but the API surface should ideally use one parameter (preferably
foundationSlugsince slugs are stable identifiers).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By design — documented in a comment at line 1672. Web Activities uses
foundationSlugbecauseWEB_ACTIVITIES_*tables key onPROJECT_SLUG. Email CTR, Social Reach, and Social Media usefoundationNamebecause those Platinum tables key onFOUNDATION_NAME/PROJECT_NAME. The frontend passes the correct identifier for each endpoint (foundation.slugvsfoundation.name). Unifying would require either a backend JOIN (fragile) or Snowflake schema changes.