feat(org-lens): org lens project detail page UI#1028
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds an Org Lens project-detail route, API, demo payloads, page rendering logic, and E2E coverage, plus a Valkey JSON cache miss handling tweak. ChangesOrg Project Detail
Valkey typing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
🚀 Deployment StatusYour branch has been deployed to: https://ui-pr-1028.dev.v2.cluster.linuxfound.info Deployment Details:
The deployment will be automatically removed when this PR is closed. |
There was a problem hiding this comment.
Pull request overview
Adds the Org Lens Project Detail page under /org/projects/:projectSlug, introducing shared payload contracts plus a frontend demo-data seam and new UI (tabs, charts, drawer details) with query-param state. The PR also wires a new org route and updates navigation/telemetry touchpoints, and adds Playwright coverage.
Changes:
- Add shared
OrgLensProjectDetailResponsecontracts and export them from@lfx-one/shared/interfaces. - Implement the Org Project Detail page UI (tabs, influence cards, leaderboards, stacked trend chart, card-detail drawer) backed by demo fixtures and a new Angular service.
- Add org routing + E2E tests; minor Valkey cache read typing tweak.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/interfaces/org-lens-project-detail.interface.ts | Adds shared API contracts for Org Lens Project Detail payloads. |
| packages/shared/src/interfaces/index.ts | Re-exports the new Project Detail interfaces from the interfaces barrel. |
| apps/lfx-one/src/server/services/valkey.service.ts | Tightens getJson typing for Valkey reads and null handling. |
| apps/lfx-one/src/app/shared/services/org-lens-project-detail.service.ts | Adds a frontend service seam for loading project detail (currently demo-backed). |
| apps/lfx-one/src/app/shared/services/org-lens-project-detail.demo-data.ts | Adds large deterministic demo fixture generator for the project detail page. |
| apps/lfx-one/src/app/modules/dashboards/org/org-project-detail/org-project-detail.component.ts | Implements page state machine, query-param persistence, leaderboard ranking, and chart dataset construction. |
| apps/lfx-one/src/app/modules/dashboards/org/org-project-detail/org-project-detail.component.html | Implements page shell, hero, tabs, influence cards, leaderboards, trend chart, and drawer markup with testids. |
| apps/lfx-one/src/app/modules/dashboards/org/org-project-detail/org-project-detail-tab-bar.component.ts | Adds the shared tab/metric/range control bar with keyboard navigation. |
| apps/lfx-one/src/app/modules/dashboards/org/org-project-detail/org-project-detail-tab-bar.component.html | Tab bar template (tablist + metric toggle + time-range pills). |
| apps/lfx-one/src/app/modules/dashboards/org/components/org-overview-foundations-and-projects/org-overview-foundations-and-projects.component.ts | Adds telemetry + navigation handlers intended to open project detail from overview rows. |
| apps/lfx-one/src/app/layouts/main-layout/main-layout.component.ts | Re-enables “Projects” entry in Org Lens sidebar section. |
| apps/lfx-one/src/app/app.routes.ts | Adds the /org/projects/:projectSlug route entry. |
| apps/lfx-one/e2e/org-project-detail.spec.ts | Adds Playwright E2E coverage for project detail page behaviors and URL persistence. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address review comments from copilot-pull-request-reviewer, copilot-swe-agent: - org-project-detail.component.ts: make scoreColumnLabel dynamic using TIME_RANGE_MONTHS[timeRange()] so Activity column shows correct period for 2y/all ranges, not hard-coded (12mo) (per copilot-pull-request-reviewer) - org-project-detail.component.ts: fix All others stacked trend to use the SUM of scores, not average — average understates the aggregate (per copilot-swe-agent) - org-overview-foundations-and-projects.component.html: restore project row click/keydown handlers now that the detail drilldown page is built; remove the deferred-until-ready comment (per copilot-pull-request-reviewer) - org-lens-project-detail.service.ts: remove Generated-with comment (per copilot-pull-request-reviewer) - e2e/org-project-detail.spec.ts: move project-detail-trend-group assertion to the leaderboards test where it is actually rendered (per copilot-swe-agent) - e2e/org-project-detail.spec.ts: remove stale ?score=/ from JSDoc — only ?metric= is used (per copilot-swe-agent) Resolves 5 review threads; 2 threads addressed via responses only (breadcrumb/CTA route and SSR endpoint are intentional by design). Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
Review Feedback AddressedChanges Made
Threads Resolved7 of 7 unresolved threads addressed in this iteration. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Dano Qualls <mynameisdano@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Dano Qualls <mynameisdano@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Dano Qualls <mynameisdano@gmail.com>
Address review comments from copilot-pull-request-reviewer: - org-project-detail.component.ts: add drawerTimeRangeLabel computed signal that derives the month count from TIME_RANGE_MONTHS[timeRange()] so the drawer subtitle reflects the active time window (per copilot-pull-request-reviewer) - org-project-detail.component.html: replace hard-coded "Last 12 months" in drawer subtitle with drawerTimeRangeLabel() (per copilot-pull-request-reviewer) - org-project-detail.component.html: change [alt]="row.orgName + ' logo'" to alt="" on both leaderboard org logo images — they are decorative (org name shown as adjacent text) so aria-hidden="true" + empty alt is correct per WCAG; the previous mixed semantics (aria-hidden + non-empty alt) were flagged as inconsistent (per copilot-pull-request-reviewer) Resolves 3 review threads. Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
Review Feedback AddressedCommit: 200039d Changes Made
Threads Resolved3 of 3 unresolved threads addressed in this iteration. |
Address review comments from copilot-pull-request-reviewer: - org-project-detail.component.ts: replace activityFor() synthetic formula with entry.row.activityCount from the wire contract in buildBoard() — the formula (score * 46 * months/12) was reading the dimensional score instead of combined and applying a scaling factor not present in the payload, diverging from OrgLensProjectLeaderboardRow .activityCount which is already computed on the server (per copilot-pull-request-reviewer, lines 277 and 293) - org-project-detail.component.ts: remove the now-unused activityFor() private method Resolves 2 review threads. Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
Review Feedback AddressedCommit: 3300654 Changes Made
Threads Resolved2 of 2 unresolved threads addressed in this iteration. |
…t files Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
Address review comments from copilot-pull-request-reviewer: - org-project-detail.component.ts: removed unused `sourceUrl` computed signal (per copilot-pull-request-reviewer) Resolves 1 review thread. Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
Review Feedback AddressedCommit: 895a694 Changes Made
Threads Resolved1 of 1 unresolved threads addressed in this iteration. |
Audit: PR #1028 — feat(org-lens): org lens project detail page UI (LFXV2-1885)@danoqualls · head Scope: Org Lens Project Detail page ( Lanes run: lint/format/types/build (worktree) · secrets (6a) · ui-reviewer (6b) · architecture (6c) · v2-knowledge drift questions (6d) 1. Why this changeDelivers LFXV2-1885: a per-project Org Lens view so a company can see detailed influence analytics and cross-org leaderboards. UI-only with server-side TypeScript demo fixtures; Snowflake integration is explicitly deferred. Restores Overview row click/keyboard navigation into the new page. 2. How it was done
Matches the established Org Lens BFF seam (validation + 3. Is it correctGates re-run (isolated worktree after
Prior review threads — resolved @
Architecture: No provable ADR violations. Aligns with ADR 0017 - account-scoped reads hosted at the data owner, ADR 0008 - CQRS OpenSearch derived copy (N/A here), ADR 0021 - public HTTPS query API. Security: No medium+ findings — session auth on 4. Missing / gaps
5. Q&A this PR resolves
6. Business rules & ADRsBusiness rules encoded:
ADR status: none added/updated for merge. 7. Open questions & confirmations
Verdict: 🔴 Required changes Merge readiness: (Comment-only review — not requesting changes via GitHub review state.) |
luismoriguerra
left a comment
There was a problem hiding this comment.
Inline annotations for the blocking activity-metric ranking gap and two convention nits. Full audit report is in the top-level comment above.
| private buildBoard(dimension: LeaderboardDimension, search: string) { | ||
| const valued = (this.detail()?.leaderboard ?? []).map((row) => ({ row, score: row.scores[dimension] })); | ||
| valued.sort((a, b) => b.score - a.score || a.row.orgName.localeCompare(b.row.orgName)); | ||
| const ranked = valued.map((entry, i) => { |
There was a problem hiding this comment.
🔴 Blocking — activity metric does not re-rank rows
buildBoard() always sorts by row.scores[dimension], but when ?metric=activity the table shows activityCount while # rank still reflects influence scores. The shared contract says rank is derived from the active metric (OrgLensProjectLeaderboardRow JSDoc).
Suggested fix: when this.metric() === 'activity', sort by row.activityCount instead; only compute bandLabel / bandSeverity in influence mode.
Consider an e2e assertion that toggling to Activity Count changes rank order when counts and influence scores diverge.
| protected onProjectRowClick(project: OrgLensFoundationRow['projects'][number]): void { | ||
| if (!project.isLfProject) return; | ||
| this.onProjectClick({ projectId: project.projectId, projectName: project.projectName }); | ||
| void this.router.navigate(['/org/projects', project.projectSlug]); |
There was a problem hiding this comment.
🟡 Non-blocking — slug fallback mismatch
The template testid already falls back to projectId when projectSlug is empty, but navigation always uses project.projectSlug. Before Snowflake wiring, align nav with the same fallback (projectSlug || projectId) so a row click cannot land on a route the fetch pipeline rejects (filter(!!slug)).
| </div> | ||
| @if (technicalBoard().length === 0) { | ||
| <p class="py-8 text-center text-sm text-gray-500" data-testid="project-detail-leaderboard-technical-empty"> | ||
| {{ techSearch().trim() ? 'No organizations match your search.' : 'No ranked organizations for this project yet.' }} |
There was a problem hiding this comment.
🟡 Non-blocking — method call in template
techSearch().trim() (and the ecosystem twin at line 333) violates the repo convention against function calls in template bindings. Prefer computed signals such as techSearchTrimmed / ecoSearchTrimmed.
Business case: company can view their involvement in a project in a detailed way that adds up to influence in the project and compares influence to other companies. UI only with demo data. Includes both influence detail tab and leaderboards tab.
This pull request introduces the Org Lens Project Detail sub-page (LFXV2-1885) to the application, enabling users to view detailed analytics and leaderboard data for individual projects within an organization. It includes the new route and navigation entry, a complete UI with tabbed navigation, metric and time range toggles, demo data integration, and comprehensive E2E tests. Additionally, it restores interactivity for project rows in the Org Overview, allowing users to drill down into project details.
Org Lens Project Detail Feature Implementation:
/org/projects/:projectSlugroute, navigation entry, and associated metadata, enabling direct navigation to project detail pages. [1] [2]OrgProjectDetailServiceto provide demo data for the project detail page, designed for easy replacement with a real backend API in the future.OrgProjectDetailTabBarComponentfor tabbed navigation, including keyboard accessibility, metric toggles, and time range selection. [1] [2]Org Overview Interactivity Restoration:
Supporting and Miscellaneous Updates:
Summary
/org/projects/:projectSlug, opened from the Overview's Foundations & Projects tableorg-lens-project-detail.demo-data.ts) via a new service + controller + route — demo company data only; real Snowflake integration is a separate story. Note: there is no static JSON fixture; all demo payloads are generated server-side in TypeScript.?tab=,?metric=,?range=)Our Influence tab:
Leaderboards tab:
Shell:
<p-skeleton>loading states andlfx-empty-stateerror/not-found panelsdata-testidattributes; E2E coverage addedPR size rationale
This PR is ~2,500 lines. The branch builds the complete Project Detail page end-to-end (scaffold → hero → influence cards → trend chart → leaderboards → e2e → polish). The intermediate commits are not independently shippable — routing and the data layer land without visible UI until the component is complete. Splitting at a natural seam (e.g., scaffold-only or data-layer-only) would merge dead routes that don't render. The full page is the atomic unit of work here, consistent with how the sibling Membership Detail page (PR #921) was delivered.
Test plan
/org/overview, expand a foundation, click an LF project row → lands on/org/projects/<slug>?tab=and survives page reload?range=query param and persist on reload/org/projectsyarn e2e🤖 Generated with Claude Code