Skip to content

feat(surveys): add permission check on committee Create Survey CTA click#1000

Open
MRashad26 wants to merge 5 commits into
mainfrom
feat/LFXV2-2252-committee-survey-permission-check
Open

feat(surveys): add permission check on committee Create Survey CTA click#1000
MRashad26 wants to merge 5 commits into
mainfrom
feat/LFXV2-2252-committee-survey-permission-check

Conversation

@MRashad26

Copy link
Copy Markdown
Contributor

Summary

  • Stale permission guard on Create Survey CTA: The committee surveys tab shows the "Create Survey" button based on canEdit() — derived from committee.writer at page-load time. If the member's role is downgraded from Manager to Member after the page loads, the stale signal still shows the button.
  • Click handler with fresh permission check: Both Create Survey buttons (table-actions slot and empty-state) now call onCreateSurvey(), which fetches fresh committee permissions via getCommittee() before navigating. On denial, redirects to the lens-appropriate overview (/foundation/overview or /project/overview) with _notice=surveys so AppComponent.initAccessDeniedToast() shows the "Access Denied" toast — consistent with the writerGuard denial flow and lens-aware (unlike the equivalent fix in feat(meetings): add access-denied toast and fix meeting coordinator permissions #992 for meetings).
  • The writerGuard on /surveys/create remains as the final safety net for direct URL access.

Changed files

File Change
committee-surveys.component.ts Inject CommitteeService, LensService, Router; add onCreateSurvey() click handler with fresh permission check and lens-aware deny redirect
committee-surveys.component.html Replace [routerLink] + [queryParams] on both Create Survey buttons with (click)="onCreateSurvey()"

References

Test plan

  • Log in as a committee Manager — Create Survey button is visible and clicking it navigates to /surveys/create with committee_uid and project query params
  • Downgrade the member to Member role without refreshing the page — Create Survey button remains visible (stale canEdit()) but clicking it redirects to the project overview with an "Access Denied" toast
  • Repeat the above under the foundation lens — redirect should go to /foundation/overview, not /project/overview
  • As a user with no committee write access navigating directly to /surveys/create?committee_uid=...writerGuard blocks and shows the toast

Replace routerLink on both Create Survey buttons (table-actions slot and
empty-state) with an onCreateSurvey() click handler that fetches fresh
committee permissions via getCommittee() before navigating. If the member's
role was downgraded from Manager to Member since the page loaded, the stale
canEdit() signal would still show the button; the click handler catches this
and redirects to the lens-appropriate overview (/foundation/overview or
/project/overview) with _notice=surveys so AppComponent shows the "Access
Denied" toast — consistent with the writerGuard denial flow.

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
@MRashad26 MRashad26 requested a review from a team as a code owner June 21, 2026 08:35
Copilot AI review requested due to automatic review settings June 21, 2026 08:35
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e60c33f0-9e31-4e05-adda-557c466594ff

📥 Commits

Reviewing files that changed from the base of the PR and between d7ee433 and 443bff8.

📒 Files selected for processing (2)
  • apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts
  • apps/lfx-one/src/app/modules/surveys/components/surveys-table/surveys-table.component.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/lfx-one/src/app/modules/surveys/components/surveys-table/surveys-table.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts

Walkthrough

Create Survey actions now run through a permission check before navigation, and the surveys table edit action now uses router links with configurable query parameters.

Changes

Survey navigation and edit routing updates

Layer / File(s) Summary
Create Survey permission gate
apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts, apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.html
Adds Router, committeeService, and lensService injections; implements onCreateSurvey() to fetch the latest committee, redirect with _notice=surveys plus optional project on denied or failed access, and navigate to /surveys/create on write access; both Create Survey buttons switch to (click)="onCreateSurvey()".
Survey edit query params
apps/lfx-one/src/app/modules/surveys/components/surveys-table/surveys-table.component.ts, apps/lfx-one/src/app/modules/surveys/components/surveys-table/surveys-table.component.html
Imports RouterLink, adds it to the surveys table component metadata, introduces the editQueryParams input, and updates the DRAFT edit action to navigate with [routerLink] and [queryParams].

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding a permission check to the committee Create Survey CTA click.
Description check ✅ Passed The description accurately summarizes the survey permission-check and related navigation changes in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/LFXV2-2252-committee-survey-permission-check

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a “fresh permission” check to the Committee Surveys tab’s Create Survey CTA so that stale, page-load-time canEdit() state can’t incorrectly allow navigation when committee write access has been revoked mid-session. This aligns the UX with the existing writerGuard denial flow by redirecting with _notice=surveys for the Access Denied toast, and makes the redirect lens-aware (foundation vs project).

Changes:

  • Introduces onCreateSurvey() that re-fetches committee permissions via CommitteeService.getCommittee() before routing to /surveys/create.
  • On denied/errored permission refresh, redirects to /foundation/overview or /project/overview with _notice=surveys (and project when available) to trigger the standard Access Denied toast.
  • Replaces [routerLink] navigation on both Create Survey buttons with the new click handler.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts Adds fresh committee permission check on CTA click and lens-aware denial redirect before navigating to survey creation.
apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.html Routes Create Survey CTAs through (click)="onCreateSurvey()" instead of direct routerLink navigation.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts (1)

47-70: Button double-click during permission check — consistent with meetings component pattern.

The onCreateSurvey() method implements the same permission check pattern as the onScheduleMeeting() method in the meetings component, and both lack loading state protection. While multiple API calls won't cause functional issues (Angular Router handles duplicate navigations gracefully), consider adding a loading signal to disable the button during the check for better UX consistency.

🤖 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/committees/components/committee-surveys/committee-surveys.component.ts`
around lines 47 - 70, The onCreateSurvey method lacks loading state protection,
allowing users to trigger multiple API calls by double-clicking the button
during the permission check. Add a loading signal (similar to the meetings
component's onScheduleMeeting pattern) that is set to true before initiating the
getCommittee call and set to false in both the next and error callback handlers.
Bind this loading signal to disable the create survey button to prevent
duplicate submissions during the permission verification.
🤖 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.

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts`:
- Around line 47-70: The onCreateSurvey method lacks loading state protection,
allowing users to trigger multiple API calls by double-clicking the button
during the permission check. Add a loading signal (similar to the meetings
component's onScheduleMeeting pattern) that is set to true before initiating the
getCommittee call and set to false in both the next and error callback handlers.
Bind this loading signal to disable the create survey button to prevent
duplicate submissions during the permission verification.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29baa182-25ea-4fbb-84d3-b3921f65a3a1

📥 Commits

Reviewing files that changed from the base of the PR and between c2fea04 and 38154af.

📒 Files selected for processing (2)
  • apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts

@github-actions

Copy link
Copy Markdown

🚀 Deployment Status

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

Deployment Details:

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

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

Wire up the non-functional survey Edit button and pass project +
committee_uid queryParams so writerGuard receives the committee context
when a committee manager navigates to edit a draft survey — mirrors the
meetings fix (PR #1025).

- surveys-table: add RouterLink import + editQueryParams input (defaults
  to {}; non-breaking for all existing usages outside committee context)
- surveys-table: wrap Edit button in <a [routerLink]="/surveys/:id/edit">
  and bind [queryParams]="editQueryParams()" (button was previously
  non-functional with no routerLink)
- committee-surveys: add editSurveyQueryParams computed signal via
  buildCommitteeCreateQueryParams; pass to [editQueryParams] binding

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
@MRashad26

Copy link
Copy Markdown
Contributor Author

Edit permission handling added (commit `867a750e7`)

Follow-up to the create-survey permission check: wired up the non-functional Edit button and added committee context to its navigation, consistent with the meetings fix (PR #1025).

Changes

  • surveys-table: added RouterLink import + editQueryParams input (Record<string, string>, defaults to {}) — non-breaking for all non-committee usages. Edit button (previously had no routerLink at all) now wrapped in <a [routerLink]="/surveys/:id/edit" [queryParams]="editQueryParams()">.
  • committee-surveys: added editSurveyQueryParams computed signal (same buildCommitteeCreateQueryParams as create); passed as [editQueryParams] to lfx-surveys-table.

Effect

When a committee manager clicks Edit on a draft survey in the committee tab, the navigation to /surveys/:id/edit now carries ?project=<slug>&committee_uid=<uid> — giving writerGuard the project slug it needs to resolve the project check, and committee_uid for any future guard extension. Previously the Edit button had no routerLink at all (it rendered but did nothing).

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
Copilot AI review requested due to automatic review settings June 26, 2026 15:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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/surveys/components/surveys-table/surveys-table.component.html`:
- Around line 182-185: The Edit action inside the selectable row is bubbling the
click into the row selection flow, which triggers
SurveysTableComponent.onRowSelect and the rowClick handler in
committee-surveys.component.html. Update the Edit link/button in
surveys-table.component.html to stop event propagation before navigation so
clicking Edit only follows the edit route and does not also fire the row
navigation. Use the existing routerLink and lfx-button markup as the place to
apply the fix.
🪄 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: 45b7918d-debb-446b-a7fe-4dc2e4cd61a0

📥 Commits

Reviewing files that changed from the base of the PR and between 38154af and 867a750.

📒 Files selected for processing (4)
  • apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts
  • apps/lfx-one/src/app/modules/surveys/components/surveys-table/surveys-table.component.html
  • apps/lfx-one/src/app/modules/surveys/components/surveys-table/surveys-table.component.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/lfx-one/src/app/modules/committees/components/committee-surveys/committee-surveys.component.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Address review comments from CodeRabbit (PRRT_kwDOPMiRQ86MjyOQ):

- committee-surveys.component.ts: switch getCommittee() to fetchCommittee()
  in onCreateSurvey() to avoid unintended shared service state mutation
  during permission-only checks
- committee-surveys.component.ts: alias editSurveyQueryParams to
  createSurveyQueryParams instead of duplicating the computed
- surveys-table.component.html: add stopPropagation on edit anchor to
  prevent row-select firing alongside route navigation in selectionMode table

Resolves 1 review thread.

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
@luismoriguerra

Copy link
Copy Markdown
Contributor

Audit: PR #1000 — feat(surveys): add permission check on committee Create Survey CTA click

MRashad26 · feat/LFXV2-2252-committee-survey-permission-checkmain · OPEN · 4 files, +42/−7 · LFXV2-2252

Scope: Fresh committee.writer check on Create Survey click; lens-aware _notice=surveys deny; draft Edit link wired with editQueryParams.

Lanes run: yarn lint · yarn check-types · ui-reviewer · bugbot · secrets grep


1. Why this change

Same stale-canEdit() problem as votes/meetings: Create Survey button remains visible after mid-session committee-writer revocation.

2. How it was done

onCreateSurvey() mirrors #997: fresh getCommittee(), lens-aware deny with _notice=surveys, (click) replaces [routerLink]. Draft Edit now navigates via <a routerLink> with committee query params (previously a non-navigating button).

3. Is it correct

Yes — merge-ready. Gates green. Edit click does not bubble to rowClick/onRowSelectlfx-table skips clicks inside a/button (table.component.ts target.closest('button, a, …')). CodeRabbit "stop propagation" thread appears not applicable given built-in table guard.

Prior review: CodeRabbit loading guard → open (optional). Duplicate-computed thread → open (non-blocking). PR test-plan foundation-lens checkbox → unchecked in body.

4. Missing / gaps

  1. Non-blockingeditSurveyQueryParams duplicates createSurveyQueryParams. Bind [editQueryParams]="createSurveyQueryParams()".
  2. Non-blocking — JSDoc on onCreateSurvey() could collapse to one line per repo convention.

5. Q&A this PR resolves

6. Business rules & ADRs

7. Open questions & confirmations


Verdict: 🟡 Minor comments
Merge readiness: safe-to-merge

@ahmedomosanya ahmedomosanya left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Audit result: 🔴 Required changes

The referenced Jira looks too broad for this PR. LFXV2-2252 is the parent epic for Org Lens parity; the specific ticket appears to be LFXV2-2471 (“Allow committee writers to create/manage surveys for their committee”). Please update the PR description to reference LFXV2-2471 directly, optionally keeping LFXV2-2252 as the parent epic.

Blocking issue:

  • The Survey Edit action still wraps <lfx-button> in an <a [routerLink]> inside a selectable table row. The latest commit added stopPropagation() and switched the permission probe to fetchCommittee(), which addresses part of the earlier feedback, but the nested interactive markup remains invalid HTML/accessibility-wise. lfx-button already supports [routerLink] and [queryParams]; please move those bindings directly onto lfx-button.

Additional notes:

  • The error path still treats every fetch failure as access denied, which can misreport transient failures as permission loss.
  • The broader permission contract still needs confirmation: writerGuard accepts committee writer access only for meetings today; surveys still require project writer access.

Local checks passed: yarn lint:check, yarn check-types, and yarn format:check.

@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commits: 443bff8, 333d82967

Changes Made

  • committee-surveys.component.ts: switched getCommittee()fetchCommittee() in onCreateSurvey() to avoid mutating shared committee service state on permission-only checks (per CodeRabbit + Copilot)
  • committee-surveys.component.ts: aliased editSurveyQueryParams to createSurveyQueryParams instead of duplicating the computed (per Copilot — already resolved)
  • surveys-table.component.html: removed <a> wrapper around Edit button; bound [routerLink], [queryParams], and (onClick)=$event.stopPropagation() directly on <lfx-button>ButtonComponent already supports these inputs, and the wrapper created nested interactive elements (invalid HTML)
  • surveys-table.component.ts: removed now-unused RouterLink import

Threads Resolved

3 of 3 unresolved threads addressed in this iteration.

Copilot AI review requested due to automatic review settings June 27, 2026 00:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment on lines +182 to +184
<a [routerLink]="['/surveys', survey.uid, 'edit']" [queryParams]="editQueryParams()" (click)="$event.stopPropagation()">
<lfx-button label="Edit" severity="primary" size="small" [text]="true" [attr.data-testid]="'surveys-edit-' + rowId"> </lfx-button>
</a>

@dealako dealako left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey @MRashad26 — solid direction here. The lens-aware deny path is a genuine improvement over the meetings pattern, and fetchCommittee() (vs getCommittee()) is the right choice to avoid mutating shared state. Two items need attention before this is ready to merge.

🔴 Blocking: 2 issues

  • Nested <a> + <lfx-button> — invalid HTML, accessibility violation (see inline)
  • Client/server permission scope mismatch — committee-only writer passes the client gate, gets denied by writerGuard (see inline)

🟡 Minor: 3 issues

  • No loading/disabled state during async fetchCommittee() call — double-click triggers multiple in-flight requests
  • Multi-line JSDoc on onCreateSurvey() violates the repo "one short line max" rule
  • Redundant take(1)fetchCommittee() already pipes take(1) internally

⚪ Nit: 1 issue

  • RouterLink import in surveys-table.component.ts becomes orphaned once the <a> wrapper is removed

🔴 Needs changes before approval


Revision history

Round Finding Status
Prior CodeRabbit: missing stopPropagation on Edit link ✅ Addressed (via (click) on <a>)
Prior Copilot: <a> wrapping <lfx-button> — nested interactive elements ⚠️ Not resolved — commit 333d82967 referenced in your comment does not appear in the branch; <a> wrapper is still present
Prior Copilot: editSurveyQueryParams duplication ✅ Resolved (correctly aliased)

@if (hasPMOAccess() && survey.displayStatus === SurveyStatus.DRAFT) {
<lfx-button label="Edit" severity="primary" size="small" [text]="true" styleClass="" [attr.data-testid]="'surveys-edit-' + rowId">
</lfx-button>
<a [routerLink]="['/surveys', survey.uid, 'edit']" [queryParams]="editQueryParams()" (click)="$event.stopPropagation()">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[blocking] The <a> wrapper around <lfx-button> creates nested interactive elements — invalid per the HTML spec and a screen-reader/focus hazard.

ButtonComponent already exposes [routerLink], [queryParams], and (onClick) inputs (button.component.ts lines 53–57), so the wrapper is unnecessary.

<!-- Before -->
<a [routerLink]="['/surveys', survey.uid, 'edit']" [queryParams]="editQueryParams()" (click)="$event.stopPropagation()">
  <lfx-button label="Edit" ...> </lfx-button>
</a>

<!-- After -->
<lfx-button
  label="Edit"
  severity="primary"
  size="small"
  [text]="true"
  [routerLink]="['/surveys', survey.uid, 'edit']"
  [queryParams]="editQueryParams()"
  (onClick)="$event.stopPropagation()"
  [attr.data-testid]="'surveys-edit-' + rowId">
</lfx-button>

Also remove the now-unused RouterLink import from surveys-table.component.ts (CodeRabbit had flagged this already; Copilot left two unresolved comments on it — this needs to land).

.pipe(take(1))
.subscribe({
next: (fresh) => {
if (fresh?.writer !== true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[blocking] Client gates on fresh?.writer !== true (committee writer), but the server-side writerGuard for writeFeature: 'surveys' only accepts project.writer — the committee-writer code path in the guard is gated to 'meetings' only.

Consequence: a committee Manager who is not a project writer will pass this client check, then be denied by writerGuard and shown the access-denied toast — a confusing double-redirect UX loop that makes it look like the permission check is broken.

Two options:

  1. Align the client check to project.writer — gate here on fresh?.project?.writer === true (or equivalent field on the committee response) to match the server rule.
  2. Update writerGuard to accept committee writers for surveys — if the intent is that committee writers should be able to create surveys, extend the guard accordingly.

Worth confirming which is the intended design with the team before the fix (ref LFXV2-2252).

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.

5 participants