Skip to content

feat(org-lens): add foundations and projects section to org overview#706

Merged
ahmedomosanya merged 1 commit into
feat/CD-3507-org-lens-shellfrom
feat/LFXV2-1680-foundations-and-projects
May 14, 2026
Merged

feat(org-lens): add foundations and projects section to org overview#706
ahmedomosanya merged 1 commit into
feat/CD-3507-org-lens-shellfrom
feat/LFXV2-1680-foundations-and-projects

Conversation

@ahmedomosanya
Copy link
Copy Markdown

@ahmedomosanya ahmedomosanya commented May 14, 2026

WIP / Draft. Adds the Foundations and Projects section to /org/overview. Stacks on top of PR #646 (feat/CD-3507-org-lens-shell); merge target is the feature branch, not main.

Ticket: LFXV2-1680

Stacked on: #646

Summary

  • New section under /org/overview (mounted via @defer (on viewport) with a 4-tile skeleton placeholder):
    • 4-tile stat strip: Foundations (per-tier breakdown) · Projects (Leading / Contributing / Participating / Silent) · Governance Roles (board + committee) · Meetings This Week (board / technical / marketing / working group / other).
    • Foundations table with one row per foundation the org touches, sorted member tier-ranked → non-member by LF project count → an Outside-LF umbrella row last. Each row carries a tier-ribbon subtitle, Org Role, Voting Status, Governance Participation, plus a chevron to expand inline-detail per-project rows (sorted by commits desc).
  • New API: GET /api/orgs/:accountId/lens/foundations-and-projects (single LEFT JOIN against the ORG_LENS_FOUNDATIONS_AND_PROJECTS rollup × ORG_LENS_FOUNDATION_PROJECTS_DETAIL per-project detail). Empty-org case returns 200 with an empty rows envelope, never 404.
  • Wire-boundary normalisation: dbt's __outside_lf__ sentinel becomes the kebab-case outside-lf slug before it reaches testids / telemetry.
  • Shared interfaces added for the row, project, stat-strip, and response shapes.
  • MembershipTierClass expanded from the prior 10-class union to the canonical 13-class ladder (Founding, Strategic, End User, Contributor added; Sponsor removed; Steering demoted to rank 6) with the rank order documented in JSDoc.

Important — temporary dev-schema bridge

organization.service.ts (the existing ORG_LENS_ACCOUNT_CONTEXT query) and the new OrgLensFoundationsService both read from ANALYTICS_DEV.LF_AOPEYEMI_PLATINUM_LFX_ONE rather than ANALYTICS.PLATINUM_LFX_ONE, because the upstream dbt PR that promotes the ORG_LENS_FOUNDATIONS_AND_PROJECTS / ORG_LENS_FOUNDATION_PROJECTS_DETAIL rollups (and the 13-class tier ladder) into prod is still in flight. Both qualifiers MUST flip back to ANALYTICS.PLATINUM_LFX_ONE before the parent PR (#646) merges to main. Tracked in this PR so it can't get lost.

Out of scope (intentionally)

  • Slug-aware Memberships drilldown — row body click currently routes to /org/memberships (placeholder) without a slug; restore the slug arg + register org/memberships/:foundationSlug once the detail page lands.
  • Slug-aware per-project drilldown — LF project rows route to /org/projects (no slug, no ProjectContext mutation, no lens switch) until the slug-aware destination ships.
  • Reverting the dev-schema qualifier (above) — done in the follow-up commit once the dbt PR merges.

Test plan

  • /org/overview mounts the new section after the involvement carousel and renders the 4 stat tiles with non-zero counts for a Platinum / Premier org (Toyota, IBM family).
  • Switching the active org via the org selector re-fetches once, clears expansion state, and re-emits the overview_view Plausible event for the new org id.
  • Foundations table sort order: member rows first (by tier rank then name), then non-member LF rows (by project_count_lf desc then name), then the Outside-LF umbrella row.
  • Chevron expands the inline-detail project rows (sorted by commits desc); collapsing leaves no orphan UI; switching org closes all expanded rows.
  • LF project row click navigates to /org/projects and emits mfp_project_row_click. Non-LF rows under Outside-LF are no-ops (no nav, no event).
  • Outside-LF row has no Org Role / Voting Status / Governance Participation badges (em-dash), is not keyboard-activatable, and renders the outside-lf testid slug (never the __outside_lf__ sentinel).
  • Empty-org case (no foundation involvement) renders the empty caption — no error toast, no 404.
  • Error path (force a 5xx) renders the retry affordance; clicking retry re-issues the request.
  • yarn build, yarn check-types, yarn lint:check, yarn format:check all pass (pre-push build already green).
  • Manual a11y pass on the chevron (Enter / Space toggle, focus return, aria-label flips between Expand / Collapse).

…FXV2-1680)

Add the Foundations and Projects section to /org/overview. Renders a
4-tile stat strip (Foundations / Projects / Governance Roles /
Meetings This Week) and a per-foundation table with inline-detail
project rows, sourced from two pre-aggregated dbt rollups via a
single Snowflake query per render.

Server side
- new GET /api/orgs/:accountId/lens/foundations-and-projects mounted
  under a fresh /api/orgs router (kept distinct from the existing
  /api/organizations router to avoid widening that surface)
- OrgLensFoundationsController validates accountId against the
  Salesforce id pattern and emits start / success lifecycle logs
- OrgLensFoundationsService runs one LEFT JOIN against the
  ORG_LENS_FOUNDATIONS_AND_PROJECTS rollup and the
  ORG_LENS_FOUNDATION_PROJECTS_DETAIL per-project detail and shapes
  the wire response (rows + stat strip), normalising the
  '__outside_lf__' sentinel to the 'outside-lf' kebab slug at the
  wire boundary
- empty-org case returns a 200 with an empty rows envelope (never a
  404), matching the rest of Org Lens

Client side
- OrgOverviewFoundationsAndProjectsComponent owns fetch + loading /
  error / ready / empty state, retry, per-row expansion (reset on
  org switch), and first-render telemetry
- FoundationRowComponent renders the 4-cell main row (logo +
  tier-ribbon subtitle + chevron, Org Role, Voting Status,
  Governance Participation) using :host { display: contents } so
  the inner <tr> becomes a direct tbody child
- FoundationsStatStripComponent renders the 4 stat tiles with
  per-tile breakdown subtext
- foundation-logo / tier-ribbon helpers centralise the
  deterministic-by-foundation-id colour and class derivations
- OrgLensFoundationsService HTTP proxy fronts the new endpoint
- mounted into org-overview.component via @defer (on viewport) with
  a 4-tile skeleton placeholder; non-LF project-row clicks remain
  no-ops, LF rows route to /org/projects (slug-aware drilldown is a
  follow-on)

Shared
- OrgLensRowKind, OrgRoleBadge, VotingStatusBadge,
  GovernanceParticipationBucket, ProjectInfluenceBucket unions plus
  OrgLensFoundationProject / OrgLensFoundationRow /
  OrgLensFoundationsStatStrip / OrgLensFoundationsAndProjectsResponse
  interfaces
- MembershipTierClass expanded from the prior 10-class union to the
  canonical 13-class ladder (Founding / Strategic / End User /
  Contributor added; Sponsor removed; Steering demoted to rank 6)
  with the rank order captured in JSDoc

Temporary dev-schema bridge
- organization.service.ts ORG_LENS_ACCOUNT_CONTEXT query and the
  new OrgLensFoundationsService both read from
  ANALYTICS_DEV.LF_AOPEYEMI_PLATINUM_LFX_ONE rather than
  ANALYTICS.PLATINUM_LFX_ONE, because the dbt PR that promotes the
  ORG_LENS_FOUNDATIONS_AND_PROJECTS / ORG_LENS_FOUNDATION_PROJECTS_DETAIL
  rollups (and the 13-class tier ladder) into prod is still in
  flight. Both qualifiers MUST flip back to ANALYTICS.PLATINUM_LFX_ONE
  before this PR's parent (CD-3507 org lens shell) merges to main.

This PR stacks on feat/CD-3507-org-lens-shell (PR #646).

Generated with [Cursor Composer](https://cursor.com/composer)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
Copilot AI review requested due to automatic review settings May 14, 2026 13:30
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 656a1e31-3161-42a3-8bb5-b03c144042a4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/LFXV2-1680-foundations-and-projects

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

@ahmedomosanya ahmedomosanya added ai-assisted A task or activity that was supported by AI, such as CoPilot, ChatGPT, or other AI technology. enhancement New feature or request WIP labels May 14, 2026
@ahmedomosanya ahmedomosanya marked this pull request as ready for review May 14, 2026 13:32
@ahmedomosanya ahmedomosanya requested a review from a team as a code owner May 14, 2026 13:32
@ahmedomosanya ahmedomosanya merged commit 977ac42 into feat/CD-3507-org-lens-shell May 14, 2026
14 checks passed
@ahmedomosanya ahmedomosanya deleted the feat/LFXV2-1680-foundations-and-projects branch May 14, 2026 13:32
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 the Foundations and Projects section to the Org Lens overview page (/org/overview), backed by a new BFF endpoint that joins two pre-aggregated dbt rollups in Snowflake. The shared MembershipTierClass union is widened from 10 to 13 canonical tier classes (Sponsor removed; Founding/Strategic/End User/Contributor added; Steering demoted). Both Snowflake reads are temporarily wired to a developer-scoped dev schema (ANALYTICS_DEV.LF_AOPEYEMI_PLATINUM_LFX_ONE) and must be flipped back to ANALYTICS.PLATINUM_LFX_ONE before the parent feature branch merges to main.

Changes:

  • New API GET /api/orgs/:accountId/lens/foundations-and-projects with controller + service + dedicated /api/orgs router.
  • New Angular section component (stat strip, foundations table with chevron expansion to per-project inline detail) deferred on viewport, plus tier/logo helpers and a client OrgLensFoundationsService.
  • Shared interface additions for the new response shape and a breaking widening of MembershipTierClass to the 13-class ladder.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
packages/shared/src/interfaces/org-lens.interface.ts Widens MembershipTierClass; adds row/project/stat-strip/response interfaces
apps/lfx-one/src/server/services/organization.service.ts Switches ORG_LENS_ACCOUNT_CONTEXT to dev schema
apps/lfx-one/src/server/services/org-lens-foundations.service.ts New service: joined Snowflake query, sentinel slug normalization, stat-strip aggregation
apps/lfx-one/src/server/controllers/org-lens-foundations.controller.ts New controller with accountId regex validation
apps/lfx-one/src/server/routes/orgs.route.ts New router for /api/orgs/:accountId/lens/...
apps/lfx-one/src/server/server.ts Mounts the new /api/orgs router
apps/lfx-one/src/app/shared/services/org-lens-foundations.service.ts Client HTTP proxy for the new endpoint
apps/lfx-one/src/app/modules/dashboards/org/org-overview/org-overview.component.{ts,html} Adds deferred-on-viewport section with skeleton placeholder
.../org-overview-foundations-and-projects.component.{ts,html,scss} Parent section: state stream, retry, expansion, telemetry
.../components/foundations-stat-strip.component.{ts,html} 4-tile stat strip with detail-line aggregation
.../components/foundation-row.component.{ts,html,scss} Per-foundation row with badges, chevron, navigation
.../helpers/{tier-ribbon,foundation-logo}.helper.ts Tier-pill palette and deterministic logo-square fallback
Comments suppressed due to low confidence (4)

apps/lfx-one/src/server/services/org-lens-foundations.service.ts:210

  • The Outside-LF sentinel is normalized to outside-lf only on foundationSlug; foundationId (__outside_lf__) is still used as the React-style key in @for ... track row.foundationId in the parent template, as the expansion-state map key, and as the Map key in rowsByFoundation. The PR description states the __outside_lf__ sentinel "MUST NOT leak into testids, telemetry payloads, or future routing keys" — but it currently leaks into the mfp_caret_toggle / mfp_row_click Plausible payloads (which receive foundationName, but the parent's toggleExpansion(row.foundationId) and the testid fallback row.foundationSlug || row.foundationId would still emit it for any non-outside-lf row whose dbt slug is null). Recommend normalizing both foundationId and foundationSlug to outside-lf for the umbrella row, or computing the testid/expansion key from the already-normalized foundationSlug.
        // Normalize the Outside-LF umbrella row's slug at the wire boundary.
        // dbt emits FOUNDATION_ID='__outside_lf__' and FOUNDATION_SLUG also
        // equal to '__outside_lf__' (literal sentinel string, NOT null) for
        // the umbrella row. The wire value MUST be the kebab-case slug
        // 'outside-lf'. We force this for any row_kind='outside_lf'
        // regardless of what dbt sends — the sentinel is an internal
        // implementation detail and MUST NOT leak into testids, telemetry
        // payloads, or future routing keys.
        const foundationSlug =
          raw.ROW_KIND === 'outside_lf' ? 'outside-lf' : (raw.FOUNDATION_SLUG ?? raw.FOUNDATION_ID);
        row = {
          foundationId: raw.FOUNDATION_ID,
          foundationSlug,
          foundationName: raw.FOUNDATION_NAME,
          foundationLogoUrl: raw.FOUNDATION_LOGO_URL,
          rowKind: raw.ROW_KIND,
          membershipTierClass: raw.MEMBERSHIP_TIER_CLASS,
          membershipTierDisplayName: raw.MEMBERSHIP_TIER_DISPLAY_NAME,
          projectCount: raw.PROJECT_COUNT_LF ?? 0,
          badges: {
            orgRole: raw.ORG_ROLE,
            votingStatus: raw.VOTING_STATUS,
            // Outside LF emits NULL bucket → render em-dash.
            governanceParticipation: raw.GOVERNANCE_PARTICIPATION_BUCKET ?? '—',
            governanceAttendancePct: raw.GOVERNANCE_ATTENDANCE_PCT,
          },
          projects: [],
        };
        rowsByFoundation.set(raw.FOUNDATION_ID, row);

apps/lfx-one/src/server/services/org-lens-foundations.service.ts:231

  • projectCount is set to raw.PROJECT_COUNT_LF ?? 0 from the upstream rollup, while the projects array is populated from the LEFT JOIN and may include projects that fall outside the PROJECT_COUNT_LF definition (e.g. non-LF projects under the Outside-LF umbrella, or projects gated out by PROJECT_INFLUENCE_MAX_BUCKET being null on line 231). As a result, projectCount may not match projects.length, and projectsInvolvedText ("{count} project(s) involved") on the row can display a different number than the inline-detail caption ("Projects Involved ({{ row.projects.length }})"). Consider either deriving projectCount from projects.length server-side or documenting the intentional discrepancy.
          projectCount: raw.PROJECT_COUNT_LF ?? 0,
          badges: {
            orgRole: raw.ORG_ROLE,
            votingStatus: raw.VOTING_STATUS,
            // Outside LF emits NULL bucket → render em-dash.
            governanceParticipation: raw.GOVERNANCE_PARTICIPATION_BUCKET ?? '—',
            governanceAttendancePct: raw.GOVERNANCE_ATTENDANCE_PCT,
          },
          projects: [],
        };
        rowsByFoundation.set(raw.FOUNDATION_ID, row);

        // Foundations tile: count member foundations only.
        if (row.rowKind === 'member' && row.membershipTierClass) {
          foundationsTotal += 1;
          tierBreakdown[row.membershipTierClass] = (tierBreakdown[row.membershipTierClass] ?? 0) + 1;
        }

        // Governance + meetings tiles: LF foundations only.
        // Outside-LF emits zero from dbt, but skip to be explicit.
        if (row.rowKind !== 'outside_lf') {
          boardMembers += raw.BOARD_MEMBER_SEAT_COUNT ?? 0;
          committeeMembers += raw.COMMITTEE_MEMBER_SEAT_COUNT ?? 0;
          mtwBoard += raw.MEETINGS_THIS_WEEK_BOARD ?? 0;
          mtwTechnical += raw.MEETINGS_THIS_WEEK_TECHNICAL ?? 0;
          mtwMarketing += raw.MEETINGS_THIS_WEEK_MARKETING ?? 0;
          mtwWorkingGroup += raw.MEETINGS_THIS_WEEK_WORKING_GROUP ?? 0;
          mtwOther += raw.MEETINGS_THIS_WEEK_OTHER ?? 0;
        }
      }

      if (raw.PROJECT_ID && raw.PROJECT_INFLUENCE_MAX_BUCKET) {

apps/lfx-one/src/server/services/org-lens-foundations.service.ts:261

  • Project rows are skipped silently if PROJECT_INFLUENCE_MAX_BUCKET is null even when PROJECT_ID is non-null — this drops them from both the inline-detail table and the projects-tile counts with no log. If dbt ever emits a project row before the influence bucket is computed (or with an unmapped value), the user sees a foundation row claiming N projects involved with fewer rendered. Either default the bucket to 'Silent' in the SQL/CASE or log a warning when this branch is hit.
      if (raw.PROJECT_ID && raw.PROJECT_INFLUENCE_MAX_BUCKET) {
        const project: OrgLensFoundationProject = {
          projectId: raw.PROJECT_ID,
          projectSlug: raw.PROJECT_SLUG ?? raw.PROJECT_ID,
          projectName: raw.PROJECT_NAME ?? raw.PROJECT_ID,
          isLfProject: raw.PROJECT_IS_LF_PROJECT === true,
          influence: raw.PROJECT_INFLUENCE_MAX_BUCKET,
          maintainers: raw.PROJECT_MAINTAINERS_COUNT ?? 0,
          contributors: raw.PROJECT_CONTRIBUTORS_COUNT ?? 0,
          collaborators: raw.PROJECT_COLLABORATORS_COUNT ?? 0,
          commits: raw.PROJECT_COMMITS_COUNT ?? 0,
        };
        row.projects.push(project);

        // Projects tile: count every project on every row (including
        // Outside-LF) per the influence bucket.
        switch (project.influence) {
          case 'Leading':
            projectsLeading += 1;
            break;
          case 'Contributing':
            projectsContributing += 1;
            break;
          case 'Participating':
            projectsParticipating += 1;
            break;
          case 'Silent':
            projectsSilent += 1;
            break;
        }
      }

apps/lfx-one/src/server/services/org-lens-foundations.service.ts:301

  • When result.rows.length === 0 the service returns an empty response with accountName: ''. The FE component then uses companyName() which falls back to the AccountContext, so the UI is fine — but consumers of the API directly would see a blank account name where the org actually exists (just has no foundation involvement). Consider either fetching/echoing the account name from the existing ORG_LENS_ACCOUNT_CONTEXT table for the empty path, or documenting on the response interface that accountName is empty string for the zero-engagement case.
  private emptyResponse(accountId: string): OrgLensFoundationsAndProjectsResponse {
    return {
      accountId,
      accountName: '',
      statStrip: {
        foundations: { total: 0, breakdown: {} },
        projects: { total: 0, leading: 0, contributing: 0, participating: 0, silent: 0 },
        governanceRoles: { total: 0, boardMembers: 0, committeeMembers: 0 },
        meetingsThisWeek: { total: 0, board: 0, technical: 0, marketing: 0, workingGroup: 0, other: 0 },
      },
      rows: [],
    };
  }

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

Comment on lines +134 to +135
FROM ANALYTICS_DEV.LF_AOPEYEMI_PLATINUM_LFX_ONE.ORG_LENS_FOUNDATIONS_AND_PROJECTS f
LEFT JOIN ANALYTICS_DEV.LF_AOPEYEMI_PLATINUM_LFX_ONE.ORG_LENS_FOUNDATION_PROJECTS_DETAIL p
MEMBERSHIP_TIER_DISPLAY_NAME,
MEMBERSHIP_TIER_CLASS
FROM ANALYTICS.PLATINUM_LFX_ONE.ORG_LENS_ACCOUNT_CONTEXT
FROM ANALYTICS_DEV.LF_AOPEYEMI_PLATINUM_LFX_ONE.ORG_LENS_ACCOUNT_CONTEXT
Comment on lines +4 to +21
<tr
[class]="trClasses()"
[attr.role]="trRole()"
[attr.tabindex]="trTabIndex()"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug()"
(click)="onRowClick($event)"
(keydown)="onRowKeydown($event)">
<td class="px-4 py-3 align-middle">
<div class="flex items-center gap-3">
<button
type="button"
class="h-7 w-7 p-0 flex items-center justify-center rounded text-gray-500 hover:bg-gray-200 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:outline-none"
[attr.aria-expanded]="expanded()"
[attr.aria-label]="chevronAriaLabel()"
[attr.data-testid]="'org-overview-foundations-and-projects-row-' + testIdSlug() + '-caret'"
(click)="onChevronClick($event)">
<i class="fa-light" [class.fa-chevron-down]="!expanded()" [class.fa-chevron-up]="expanded()" aria-hidden="true"></i>
</button>
Comment on lines +79 to +101
private readonly state = toSignal(
combineLatest([this.accountId$, this.retryTrigger$.pipe(startWith(0))]).pipe(
switchMap(([accountId]) => {
if (!accountId) {
return of<SectionState>({ status: 'empty', data: EMPTY_RESPONSE });
}
return this.foundationsService.getFoundationsAndProjects(accountId).pipe(
map<OrgLensFoundationsAndProjectsResponse, SectionState>((data) => ({
status: data.rows.length === 0 ? 'empty' : 'ready',
data,
})),
startWith<SectionState>({ status: 'loading', data: null }),
catchError(() => of<SectionState>({ status: 'error', data: null })),
tap((s) => {
if (s.status === 'ready' || s.status === 'empty') {
this.emitOverviewViewOnce(accountId);
}
})
);
})
),
{ initialValue: INITIAL_STATE }
);
Comment on lines +11 to +16
<a
routerLink="/org/memberships"
class="text-sm text-blue-600 hover:text-blue-700 hover:underline focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:outline-none rounded"
data-testid="org-overview-foundations-and-projects-view-all-link"
>View all on Memberships page ›</a
>
Comment on lines +104 to +132
<tr
class="border-b border-gray-100"
[class.cursor-pointer]="project.isLfProject"
[class.hover:bg-white]="project.isLfProject"
[class.cursor-default]="!project.isLfProject"
[attr.role]="project.isLfProject ? 'button' : null"
[attr.tabindex]="project.isLfProject ? 0 : null"
(click)="onProjectRowClick(project)"
(keydown)="onProjectRowKeydown($event, project)"
[attr.data-testid]="
'org-overview-foundations-and-projects-row-' +
(row.foundationSlug || row.foundationId) +
'-project-' +
projectSlugTestId(project.projectSlug, project.projectId)
">
<td class="py-2">
<span class="inline-flex items-center gap-2">
<span
class="inline-block w-2 h-2 rounded-full"
[class]="influenceDotClasses(project.influence)"
[attr.aria-label]="project.influence + ' influence'"></span>
<span class="text-gray-900">{{ project.projectName }}</span>
</span>
</td>
<td class="py-2 text-right text-gray-700">{{ project.maintainers.toLocaleString() }}</td>
<td class="py-2 text-right text-gray-700">{{ project.contributors.toLocaleString() }}</td>
<td class="py-2 text-right text-gray-700">{{ project.collaborators.toLocaleString() }}</td>
<td class="py-2 text-right text-gray-700">{{ project.commits.toLocaleString() }}</td>
</tr>
Comment on lines +9 to +15
* Rank-banded mapping into 4 LFX semantic families:
* - Top-4 (premium): Platinum / Premier / Founding / Strategic → violet
* - Mid (sponsor): Gold / Steering / Silver → amber
* - Standard: General / Associate → blue
* - Free / observer: End User / Academic / Contributor / Other → gray
* - Non-member LF row: → amber outline
* - Outside LF row: → gray
Comment on lines +62 to +71
* Subtitle pill text. Binds to the canonical `membershipTierClass`
* (NOT `membershipTierDisplayName`) so the badge renders the canonical
* tier label.
*/
protected readonly subtitleText = computed<string>(() => {
const r = this.row();
if (r.rowKind === 'outside_lf') return 'Outside LF';
if (r.rowKind === 'non_member') return 'Non-member';
const tier = r.membershipTierClass ?? 'Member';
return `${tier} Member`;
@@ -178,6 +179,7 @@ app.use('/api/committees', committeesRouter);
app.use('/api/mailing-lists', mailingListsRouter);
app.use('/api/meetings', meetingsRouter);
Comment on lines +28 to +63
public async getFoundationsAndProjects(req: Request, res: Response, next: NextFunction): Promise<void> {
const accountId = req.params['accountId'];
const startTime = logger.startOperation(req, 'get_org_lens_foundations_and_projects', {
account_id: accountId,
});

try {
if (!accountId || typeof accountId !== 'string') {
throw ServiceValidationError.forField('accountId', 'accountId path parameter is required', {
operation: 'get_org_lens_foundations_and_projects',
});
}

if (!ACCOUNT_ID_PATTERN.test(accountId)) {
throw ServiceValidationError.forField('accountId', 'Invalid Salesforce accountId format', {
operation: 'get_org_lens_foundations_and_projects',
});
}

const response = await this.service.getFoundationsAndProjects(accountId);

const projectCountTotal = response.rows.reduce((sum, row) => sum + row.projects.length, 0);

logger.success(req, 'get_org_lens_foundations_and_projects', startTime, {
account_id: accountId,
row_count: response.rows.length,
project_count_total: projectCountTotal,
});

// No PII or tokens in logs. account_id is already a Salesforce
// opaque ID, safe.
res.setHeader('Cache-Control', 'no-store');
res.json(response);
} catch (error) {
next(error);
}
ahmedomosanya added a commit that referenced this pull request May 14, 2026
…files

Six files introduced by PR #706 (LFXV2-1680 foundations and projects
section) had minor whitespace / line-length drift flagged by the CI
prettier check. No logic changes — pure prettier --write output to
keep the line-length and self-closing-tag rules consistent with the
rest of the org lens code.

- foundation-row.component.html
- foundations-stat-strip.component.html
- org-overview-foundations-and-projects.component.html
- shared/services/org-lens-foundations.service.ts (client proxy)
- server/routes/orgs.route.ts
- server/services/org-lens-foundations.service.ts

Mirrors the precedent set by 2577089 ("style(org-lens): fix prettier
formatting on org lens files") which fixed the same class of CI drift
on the prior org lens batch.

Generated with [Cursor Composer](https://cursor.com/composer)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
ahmedomosanya added a commit that referenced this pull request May 19, 2026
#731)

Flip three table references in the server-side org lens queries from
the personal dev schema back to the prod platinum schema, now that
lf-dbt#2406 has merged and materialised the three org lens models in
ANALYTICS.PLATINUM_LFX_ONE (97k / 237k / 3.2M rows as of 2026-05-18).

No logic change — pure schema-qualifier flip.

- org-lens-foundations.service.ts: FROM and LEFT JOIN switched from
  ANALYTICS_DEV.LF_AOPEYEMI_PLATINUM_LFX_ONE.ORG_LENS_FOUNDATIONS_AND_PROJECTS
  and ORG_LENS_FOUNDATION_PROJECTS_DETAIL to ANALYTICS.PLATINUM_LFX_ONE.*
- organization.service.ts: ORG_LENS_ACCOUNT_CONTEXT restored to
  ANALYTICS.PLATINUM_LFX_ONE — same qualifier the original org lens
  live-data PR (#667) shipped; it had been temporarily demoted to dev
  in #706 for demo consistency with the two new tables

Generated with [Cursor Composer](https://cursor.com/composer)

Signed-off-by: ahmedomosanya <aopeyemi@contractor.linuxfoundation.org>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted A task or activity that was supported by AI, such as CoPilot, ChatGPT, or other AI technology. enhancement New feature or request WIP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants