Skip to content

feat(votes): add permission check on committee Create Vote CTA click#997

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

feat(votes): add permission check on committee Create Vote CTA click#997
MRashad26 wants to merge 2 commits into
mainfrom
feat/LFXV2-2252-committee-vote-permission-check

Conversation

@MRashad26

@MRashad26 MRashad26 commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stale permission guard on Create Vote CTA: The committee votes tab shows the "Create Vote" 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. Clicking it without this fix would navigate directly to /votes/create and rely solely on the writerGuard redirect, with no immediate UI feedback.
  • Click handler with fresh permission check: Both Create Vote buttons (table-actions slot and empty-state) now call onCreateVote(), which fetches fresh committee permissions via getCommittee() before navigating. On denial, redirects to /project/overview?project=<slug>&_notice=votes so AppComponent.initAccessDeniedToast() shows the contextual "Access Denied" toast — consistent with the writerGuard denial flow from feat(meetings): add access-denied toast and fix meeting coordinator permissions #992.
  • The writerGuard on /votes/create remains as the final safety net for direct URL access.

Changed files

File Change
committee-votes.component.ts Inject CommitteeService + Router; add onCreateVote() click handler with fresh permission check and deny redirect
committee-votes.component.html Replace [routerLink] + [queryParams] on both Create Vote buttons with (click)="onCreateVote()"

References

Test plan

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

Replace routerLink on both Create Vote buttons (table-actions slot and
empty-state) with an onCreateVote() click handler that fetches fresh
committee permissions 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 /project/overview?_notice=votes so AppComponent
shows the "Access Denied" toast — consistent with the writerGuard
denial flow used for meetings.

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

coderabbitai Bot commented Jun 20, 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: 2c66aa0d-8c4c-41ae-94c0-074d3bf03d8e

📥 Commits

Reviewing files that changed from the base of the PR and between 4f240f6 and 1b0e903.

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

Walkthrough

Both "Create Vote" buttons in CommitteeVotesComponent are changed from [routerLink]/createVoteQueryParams() to (click)="onCreateVote()". The new handler injects CommitteeService and Router, fetches a fresh committee record, validates the writer flag, and either navigates to the create-vote route or redirects to /project/overview?_notice=votes on failure.

Changes

Create Vote permission-guarded navigation

Layer / File(s) Summary
Imports, injected services, and template wiring
apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.ts, apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.html
Adds Router, CommitteeService, and finalize imports; injects committeeService and router alongside existing services; replaces [routerLink]/createVoteQueryParams() with (click)="onCreateVote()" on both "Create Vote" buttons.
onCreateVote() permission check and navigation
apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.ts
Implements onCreateVote(): computes redirect query parameters based on active lens and project slug, fetches the committee by UID, checks writer === true, navigates to the create-vote route on success, and redirects to /project/overview?_notice=votes on permission denial or request error.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • linuxfoundation/lfx-self-serve#952: Updated the same committee-votes component to include project in the create-vote query parameters via buildCommitteeCreateQueryParams, which is the navigation target that onCreateVote() now guards with a permission check.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a permission check on the Create Vote button click handler, which is the core improvement addressing the stale permission issue.
Description check ✅ Passed The description provides comprehensive context about the stale permission problem, the solution approach, affected files, test plan, and related issues—all directly relevant to the changeset.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-vote-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 and usage tips.

@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-votes/committee-votes.component.ts (1)

45-67: 💤 Low value

Consider disabling the button during the permission check to prevent duplicate requests.

While the implementation correctly fetches fresh permissions and handles denial/error cases, rapid clicks could trigger multiple concurrent API calls before navigation completes. Adding a brief loading state would improve UX.

♻️ Optional: Add loading guard
+ public creatingVote = signal<boolean>(false);
+
  public onCreateVote(): void {
+   if (this.creatingVote()) return;
+   this.creatingVote.set(true);
    const committee = this.committee();
    const denyParams: Record<string, string> = { _notice: 'votes' };
    if (committee.project_slug) denyParams['project'] = committee.project_slug;
-   const deny = () => void this.router.navigate(['/project/overview'], { queryParams: denyParams });
+   const deny = () => {
+     this.creatingVote.set(false);
+     void this.router.navigate(['/project/overview'], { queryParams: denyParams });
+   };

    this.committeeService
      .getCommittee(committee.uid)
      .pipe(take(1))
      .subscribe({
        next: (fresh) => {
          if (fresh?.writer !== true) {
            deny();
            return;
          }
          void this.router.navigate(['/votes', 'create'], { queryParams: this.createVoteQueryParams() });
        },
        error: () => deny(),
      });
  }

Then in template: [disabled]="creatingVote()"

🤖 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-votes/committee-votes.component.ts`
around lines 45 - 67, To prevent duplicate API requests from rapid button clicks
during the permission check in the onCreateVote method, add a loading state
signal (named something like creatingVote) initialized to false. Set this signal
to true before the getCommittee API call starts subscribing, then set it back to
false in both the next and error callbacks of the subscribe handler to ensure it
resets regardless of outcome. Finally, update the template to disable the button
by binding [disabled]="creatingVote()" to the create vote button element.
🤖 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-votes/committee-votes.component.ts`:
- Around line 45-67: To prevent duplicate API requests from rapid button clicks
during the permission check in the onCreateVote method, add a loading state
signal (named something like creatingVote) initialized to false. Set this signal
to true before the getCommittee API call starts subscribing, then set it back to
false in both the next and error callbacks of the subscribe handler to ensure it
resets regardless of outcome. Finally, update the template to disable the button
by binding [disabled]="creatingVote()" to the create vote button element.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 98e32e74-2730-4773-8207-41ce2e0eecd4

📥 Commits

Reviewing files that changed from the base of the PR and between c2fea04 and 4f240f6.

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

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown

🚀 Deployment Status

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

Deployment Details:

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

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

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 when a user clicks the Committee “Create Vote” CTA so that if their committee writer access is revoked after page load, they’re redirected through the same _notice=votes access-denied toast flow rather than relying solely on the route guard.

Changes:

  • Added onCreateVote() to re-fetch committee permissions via CommitteeService.getCommittee() before navigating to the create vote route.
  • Updated both “Create Vote” buttons to call (click)="onCreateVote()" instead of using [routerLink] + [queryParams].

Reviewed changes

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

File Description
apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.ts Adds click handler that re-checks committee writer permission and redirects with _notice=votes on denial.
apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.html Routes both CTAs through the new click handler.

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

…rect

Inject LensService and derive the overview path from the active lens so
the access-denied redirect lands on /foundation/overview when the component
is rendered under the foundation lens (/foundation/groups/...) rather than
always redirecting to /project/overview — consistent with writerGuard's
lens-aware denial behavior (per @copilot-pull-request-reviewer).

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

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 1b0e903

Changes Made

  • committee-votes.component.ts: Injected LensService; deny redirect now derives overviewPath from activeLens()/foundation/overview for the foundation lens, /project/overview otherwise. Matches writerGuard's lens-aware behavior exactly (per @copilot-pull-request-reviewer)

No Change Needed

  • CodeRabbit nitpick (loading guard for double-click prevention): Marked "💤 Low value / Optional" by CodeRabbit — navigation completes after the first successful/failed check, making a double-fire harmless in practice. Skipped.

Threads Resolved

1 of 1 unresolved threads addressed in this iteration.

Follow-up

committee-meetings.component.ts (onScheduleMeeting) has the same hard-coded /project/overview — shipped in #992. Will fix in a follow-up.

Copilot AI commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

@MRashad26 I've opened a new pull request, #998, to work on those changes. Once the pull request is ready, I'll request review from you.

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.

3 participants