feat(org-lens): add projects page UI#921
Conversation
Adds the Org Lens Projects page (`/org/projects`): header + freshness label, Workspace/Foundation/Employee filters, Export to CSV + Find project toolbar, the 9-column projects table with health/influence columns, avatar stacks, row kebab, and URL-persisted sort/filter/ pagination (LFXV2-1883). Adds the Influence Summary section above the table with Most Influential/Gains/Decreases pill tabs and ranked project cards (LFXV2-1884). Data is served from a demo-fixture seam (OrgLensProjectsService) so the real Snowflake/LFX Insights integration can drop in later without component changes. Replaces the /org/projects placeholder route. Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
Refines the Org Lens Projects page (LFXV2-1883) on top of the initial scaffold: removes the Influence Summary section, replaces influence band chips with signal-strength bar icons (lighter-tint unfilled bars, Non-LF slash), adds GitHub-avatar project logos, a health-detail hover popover, wider column tooltips, blue active-sort indicators, and a shared-workspaces dropdown with add / rename / delete. Default sort is contributors then participants; columns are Contributors + Participants (Maintainers/Foundation removed). Still demo-company data only — real Snowflake/Insights integration is a separate story. LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
- Precompute signal-bar geometry + trend tooltip HTML into a row view-model (OrgProjectsTableRow) so the table template reads properties instead of calling builder methods every change-detection cycle; lift the influence-column tooltip to a static field. - Make the workspace dialog visibility a model() with [(visible)] instead of a signal + split [visible]/(visibleChange) binding. - Clear the health-popover hide timer on DestroyRef.onDestroy so a pending setTimeout can't fire after teardown. - Make the Influence Trend sparkline tooltip keyboard-accessible (focusable host with role + aria-label trend summary). LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
Adds a Playwright e2e spec for the Org Lens Projects page (LFXV2-1883) following the org-events-dashboard pattern: stubs the personas endpoint for org context, skips gracefully when auth / the org-lens flag is unavailable, and covers demo-data table render, column-header sort + URL persistence, the workspace dropdown / add-workspace dialog, and the health-detail hover popover. The repo has no unit-test harness (no Karma/Jasmine/Jest, no test target); Playwright e2e is the established convention for these pages. LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
|
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:
WalkthroughImplements the Org Lens Projects page: new contracts/constants, demo data and service, CSV utilities, component template and logic (filters, sorting, pagination, workspaces, CSV export, popovers), styling/tailwind tweaks, route lazy-loading, and Playwright e2e tests. ChangesOrg Lens Projects Feature
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)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds the Org Lens Projects page at /org/projects, including shared contracts/constants and a demo-data-backed Angular implementation (plus a Playwright e2e spec). The page introduces a sortable projects table with influence/health visualizations, workspace UI, URL-persisted state, and CSV export support.
Changes:
- Added shared Org Lens Projects interfaces + constants (influence bands, health labels, sort defaults/validation, workspace defaults).
- Implemented the Org Projects Angular page (table, sorting/pagination, workspace popover + dialog, health popover, CSV export) backed by a demo-data service.
- Added global styling and Tailwind safelist entries to support tooltip sizing and dynamic SVG
fill-*classes; added Playwright e2e coverage for the new page.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/utils/file.utils.ts | Adds CSV building + download helpers (used by the Projects page export). |
| packages/shared/src/interfaces/org-lens-projects.interface.ts | Introduces shared data contracts for Org Lens Projects (projects, influence, health, workspaces, sorting). |
| packages/shared/src/interfaces/index.ts | Re-exports the new Org Lens Projects interfaces from the shared barrel. |
| packages/shared/src/constants/org-lens-projects.constants.ts | Adds shared UI/behavior constants for the Projects page (labels, ranks, sort defaults, workspace defaults). |
| packages/shared/src/constants/index.ts | Re-exports the new Org Lens Projects constants from the shared barrel. |
| apps/lfx-one/tailwind.config.js | Safelists dynamic fill-* classes used by influence “signal strength” SVG bars. |
| apps/lfx-one/src/styles.scss | Adds global PrimeNG tooltip width overrides needed by Projects column tooltips. |
| apps/lfx-one/src/app/shared/services/org-lens-projects.service.ts | Adds demo-data service seam for the Projects page (simulated latency). |
| apps/lfx-one/src/app/shared/services/org-lens-projects.demo-data.ts | Adds demo fixture dataset for the Projects page (projects, logos, descriptions, health metrics). |
| apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.ts | Implements Projects page state management (URL sync, sorting, pagination, workspace CRUD UI, export). |
| apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.html | Implements Projects page template (table, popovers/tooltips, workspace UI, empty/error states). |
| apps/lfx-one/src/app/app.routes.ts | Wires /org/projects route to the new component (replacing placeholder). |
| apps/lfx-one/e2e/org-projects.spec.ts | Adds Playwright e2e tests for table render, sorting URL persistence, workspace dialog, and health popover. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
🚀 Deployment StatusYour branch has been deployed to: https://ui-pr-921.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.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/lfx-one/src/app/shared/services/org-lens-projects.demo-data.ts`:
- Around line 370-375: The orgSlug assignment currently falls back to orgUid
only when orgName is falsy, so a punctuation-only orgName like "!!!" sanitizes
to an empty string and does not fall back; change the logic in the orgSlug
computation (referencing orgSlug, orgName, orgUid) to first produce a sanitized
slug from orgName (apply toLowerCase, replace(/[^a-z0-9]+/g,'-'),
replace(/(^-|-$)/g,'')) and then use the sanitized value if non-empty, otherwise
use orgUid as the fallback.
🪄 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: 084b64fc-6a4f-4447-8230-b028234c2943
📒 Files selected for processing (13)
apps/lfx-one/e2e/org-projects.spec.tsapps/lfx-one/src/app/app.routes.tsapps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.htmlapps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.tsapps/lfx-one/src/app/shared/services/org-lens-projects.demo-data.tsapps/lfx-one/src/app/shared/services/org-lens-projects.service.tsapps/lfx-one/src/styles.scssapps/lfx-one/tailwind.config.jspackages/shared/src/constants/index.tspackages/shared/src/constants/org-lens-projects.constants.tspackages/shared/src/interfaces/index.tspackages/shared/src/interfaces/org-lens-projects.interface.tspackages/shared/src/utils/file.utils.ts
Addresses a CodeRabbit finding: a punctuation-only org name sanitizes to an empty slug, so compute the slug first and fall back to orgUid only when the result is empty (not just when orgName is falsy). LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.ts`:
- Around line 465-475: The current employees array comes directly from
formValue() (URL) and can contain stale IDs that filter out all projects; before
using it, compute the set of person IDs present in the current projects (e.g.
from all.flatMap(p => [...p.maintainers, ...p.contributors,
...p.participants]).map(person => person.id)) and then replace employees with
employees.filter(id => projectPersonIdSet.has(id)) (keeping the existing falsy
filter). Then use that normalized employees array in the existing chain that
uses hiddenSlugs(), matchesWorkspace(), and ALL_FOUNDATIONS so stale/unknown IDs
are ignored rather than producing an empty result.
🪄 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: dab25f71-f61d-46b1-b21f-f0911fb6203b
📒 Files selected for processing (4)
apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.htmlapps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.tsapps/lfx-one/src/app/shared/services/org-lens-projects.demo-data.tspackages/shared/src/utils/file.utils.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/lfx-one/src/app/shared/services/org-lens-projects.demo-data.ts
- apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.html
- packages/shared/src/utils/file.utils.ts
- Add an 'Add project(s)' dialog (shared-company banner, searchable multi-select with project logos, chips, confirm) that adds projects to the current workspace; selections merge into the table from a demo catalog exposed via the service seam. - Add an optional optionImage input to the lfx-multi-select wrapper so dropdown options can show a logo (backward-compatible). - Reduce the row kebab to a single 'Hide project from workspace' action; remove the now-unused pin feature. - Always show 'Delete workspace' in the workspace settings modal; re-seed the default if the last workspace is removed. - Widen the column-header hover tooltips (min-width floor on the container to beat PrimeNG's inline fit-content) so the explainer text reads on one block. LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
- Ignore stale/unknown employee ids from the URL before filtering so a shared deep link can't filter out every project (CodeRabbit). - downloadCsv: guard on the full Blob-URL API (not just document) and sanitize the filename via sanitizeFilename (Copilot). - Clearly mark OrgProjectsSignalBar/OrgProjectsTableRow as client-only view models (not API wire contracts); note trendTooltipHtml must never come from the wire (Copilot). LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.ts`:
- Around line 125-136: Change addedProjects and hiddenSlugs from global
collections to maps keyed by OrgProjectsWorkspaceId and read/write only the
entry for the current workspace (this.selectedWorkspaceId()). Specifically:
convert protected readonly addedProjects = signal<OrgLensProject[]>([]) into a
signal<Record<OrgProjectsWorkspaceId, OrgLensProject[]>> (or Map-like structure)
and convert protected readonly hiddenSlugs = signal<ReadonlySet<string>>(new
Set()) into a signal<Record<OrgProjectsWorkspaceId, ReadonlySet<string>>> (or
Map), then update filteredProjects(), the add-projects handler, the hide/unhide
project handlers, and any other places reading/writing these collections
(references around filteredProjects(), the add/hide methods, and usages near the
other noted blocks) to only append, remove, or check the entry for
this.selectedWorkspaceId(); ensure you initialize per-workspace entries lazily
when missing and preserve original semantics for non-workspace keys where
needed.
- Around line 304-315: The deleteWorkspace() logic updates the in-memory list
but doesn't update the URL when the deleted workspace was the one encoded in the
query param, because selectedWorkspaceId() already falls back; fix by capturing
the currently routed workspace id (read the 'workspace' query param from
ActivatedRoute/Router or save the selected id before mutating), then after
this.workspaces.set(next) check if that routed id === editing.id and if so call
this.selectWorkspace(next[0].id) or update the router query param to the new
workspace id; reference functions/fields: deleteWorkspace(), editingWorkspace(),
this.workspaces.set(...), this.selectedWorkspaceId(), and
this.selectWorkspace(...).
🪄 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: 80ba307f-407b-4f95-9d3e-634ce52ebce2
📒 Files selected for processing (7)
apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.htmlapps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.tsapps/lfx-one/src/app/shared/components/multi-select/multi-select.component.htmlapps/lfx-one/src/app/shared/components/multi-select/multi-select.component.tsapps/lfx-one/src/app/shared/services/org-lens-projects.demo-data.tsapps/lfx-one/src/app/shared/services/org-lens-projects.service.tsapps/lfx-one/src/styles.scss
✅ Files skipped from review due to trivial changes (1)
- apps/lfx-one/src/app/shared/components/multi-select/multi-select.component.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/lfx-one/src/styles.scss
- apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.html
…on delete - Key the added-projects and hidden-slugs collections by workspace id so hiding or adding a project in one workspace no longer bleeds into the others (CodeRabbit). - deleteWorkspace now clears/replaces the ?workspace= param when the deleted workspace was active, instead of leaving a stale id in the URL (CodeRabbit). LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
… aria-label - escapeCsvCell only prefixes the formula-injection guard for string cells, so numeric columns (e.g. trend deltas, negative values) stay numeric in spreadsheets (Copilot). - Health cell exposes the full rating + sub-scores via aria-label (role=img, focusable) so keyboard/screen-reader users get the popover content textually; removed the focus-open/blur-hide that flashed an unreachable popover (Copilot). LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.ts (1)
305-318:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDelete should also purge the workspace-scoped client state.
After Line 315,
hiddenByWorkspaceandaddedByWorkspacestill retainediting.id. SinceuniqueWorkspaceId()can reuse that slug once it disappears fromthis.workspaces(), recreating the same workspace name will resurrect its old hidden/added projects. Clear both entries during deletion.🧹 Proposed fix
const remaining = this.workspaces().filter((w) => w.id !== editing.id); const next = remaining.length > 0 ? remaining : [...DEFAULT_ORG_PROJECTS_WORKSPACES]; this.workspaces.set(next); + this.hiddenByWorkspace.update(({ [editing.id]: _removed, ...rest }) => rest); + this.addedByWorkspace.update(({ [editing.id]: _removed, ...rest }) => rest); if (wasActive) { this.selectWorkspace(next[0].id); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.ts` around lines 305 - 318, In deleteWorkspace(), after computing `editing` and before/after mutating `this.workspaces`, also remove any entries keyed by the deleted workspace id from the workspace-scoped client state: delete or clear `hiddenByWorkspace[editing.id]` and `addedByWorkspace[editing.id]` so that when `uniqueWorkspaceId()` may later reuse the same slug it doesn't resurrect old hidden/added project state; update the logic in the `deleteWorkspace()` method to purge those two maps for `editing.id`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.ts`:
- Around line 257-264: The foundation/employee option builders still pull from
this.response()?.projects only, causing dropdowns to miss workspace-local
additions present in filteredProjects() which includes addedByWorkspace()[ws];
update those builders (the foundation option builder and the employee option
builder functions used to build dropdown lists and the logic at/around where
employee ids are validated) to derive their source project list the same way
confirmAddProjects() and filteredProjects() do — combine
[...(this.response()?.projects ?? []), ...(this.addedByWorkspace()[ws] ?? [])]
(or reuse a helper that returns the current-workspace project set) so option
lists include addedByWorkspace entries and employee id validation uses the same
set.
---
Outside diff comments:
In
`@apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.ts`:
- Around line 305-318: In deleteWorkspace(), after computing `editing` and
before/after mutating `this.workspaces`, also remove any entries keyed by the
deleted workspace id from the workspace-scoped client state: delete or clear
`hiddenByWorkspace[editing.id]` and `addedByWorkspace[editing.id]` so that when
`uniqueWorkspaceId()` may later reuse the same slug it doesn't resurrect old
hidden/added project state; update the logic in the `deleteWorkspace()` method
to purge those two maps for `editing.id`.
🪄 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: e3f0a25b-3f8d-46e1-bcac-6c6564a06138
📒 Files selected for processing (4)
apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.htmlapps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.tspackages/shared/src/interfaces/org-lens-projects.interface.tspackages/shared/src/utils/file.utils.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/shared/src/interfaces/org-lens-projects.interface.ts
- packages/shared/src/utils/file.utils.ts
- apps/lfx-one/src/app/modules/dashboards/org/org-projects/org-projects.component.html
Type baseByHealth as Record<HealthScore, number> so missing/misspelled keys are caught at compile time (Copilot). LFXV2-1883 Signed-off-by: daniel qualls <dqualls@linuxfoundation.org>
| * Escape a single CSV cell per RFC 4180 and neutralize formula-injection prefixes. | ||
| * Cells starting with =, +, -, @, TAB, or CR are prefixed with a leading apostrophe. | ||
| * @param value - Raw cell value |
Business case: Org Lens projects page, UI only. show companies which projects they are involved in and how influential they are.
This pull request introduces the new Org Lens Projects page, including its routing, demo data, service layer, and supporting test and style updates. The most important changes are grouped below.
Org Lens Projects Page Implementation
/org/projectsinapp.routes.ts, pointing to theOrgProjectsComponentinstead of the placeholder.OrgLensProjectsServiceandorg-lens-projects.demo-data.tsto provide demo data for the Org Lens Projects page, simulating network latency and complex project data. [1] [2]Testing
org-projects.spec.tsto verify the Org Projects page renders, sorts, opens dialogs, and displays tooltips as expected.Styling and Configuration
styles.scssto support wider tooltips for Org Lens projects, ensuring explanatory tooltips render correctly.tailwind.config.jsto include fill color classes used for Org Lens project influence bars.Constants
org-lens-projects.constantsfrom the shared constants index for use across the app.Summary
Adds the Org Lens Projects page (
/org/projects, LFXV2-1883) — the open source projects an organization influences, contributes to, and depends on. Rendered from demo company data; real Snowflake / LFX Insights integration is a separate story.What's included
Scope & decisions
OrgLensProjectsServicereturns client-side fixtures; the live Snowflake / LFX Insights wiring drops into the same@lfx-one/sharedcontracts without component changes.<p-dialog>convention trade-off. The workspace add/settings modal uses<p-dialog>directly rather thanDialogService.open()(frontend-checklist §3). This intentionally matches the existing Org Lens module pattern (3 other<p-dialog>usages already present); a module-wide migration toDialogServiceis a reasonable follow-up rather than diverging this one dialog.testtarget), so coverage is a Playwright e2e spec (e2e/org-projects.spec.ts) following theorg-events-dashboardpattern.Known follow-ups
distinctUntilChangedon the accountuidprojection in the data refetch to avoid a redundant fetch when the account re-emits with the same uid.catchErrorsets the error state but doesn't log — wire it to the logger when real data lands.Testing
yarn check-types,yarn lint,yarn buildall pass.e2e/org-projects.spec.ts(run viayarn e2e): table render with demo data, column-header sort + URL persistence, workspace dropdown / add-workspace dialog, and the health hover popover.🤖 Generated with Claude Code