Skip to content

feat(akrites): implement stewardship assignment with real member picker#1001

Open
gaspergrom wants to merge 4 commits into
mainfrom
feat/CM-1268-akrites-stewardship-assignment
Open

feat(akrites): implement stewardship assignment with real member picker#1001
gaspergrom wants to merge 4 commits into
mainfrom
feat/CM-1268-akrites-stewardship-assignment

Conversation

@gaspergrom

@gaspergrom gaspergrom commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace placeholder userId text input in akrites-assign-steward-modal with a searchable member picker that loads from the LFX Akrites committee endpoint (GET /api/committees/:id/members)
  • Wire the assign-steward modal into the package drawer (Assign steward / Reassign button for unassigned/open/assessing/escalated/inactive statuses)
  • Wire the assign-steward modal into the triage tab footer actions for unassigned and inactive columns
  • Update AkritesAssignStewardRequest / CdpStewardSummary / AkritesSteward with required username and displayName fields to satisfy the CDP assign endpoint contract

Changes

Shared package (@lfx-one/shared)

  • AkritesSearchStewardResult — new interface mapping CommitteeMember to an assignable steward shape
  • AkritesAssignStewardRequest — adds required username: string and displayName: string
  • CdpStewardSummary / AkritesSteward — adds username and displayName fields
  • AKRITES_STEWARD_COMMITTEE_UID — constant pointing to the LFX Akrites working group (8bffb08a-3707-4f8f-9e1c-0cbca8a4dfb6)

Backend (apps/lfx-one/src/server)

  • validation.helper.tsparseAssignStewardBody now validates required username and displayName
  • akrites.service.tsmapStewards populates username and name from the CDP response

Frontend

  • akrites.service.ts — adds searchStewards(): fetches committee members and maps them to AkritesSearchStewardResult[]
  • akrites-assign-steward-modal — full rewrite: initials-avatar list, client-side search filter (name/username/org), single-select with blue border + checkmark, role radio buttons, Move to Assessing toggle; all driven by signals
  • akrites-package-drawer — Assign steward / Reassign button shown for actionable statuses; modal wired to onAssignStewardConfirm (open-then-assign chain for packages without a stewardship row)
  • akrites-triage-tab — Assign steward modal wired to unassigned and inactive column actions

JIRA

CM-1268

Replace placeholder userId text input in assign-steward modal with a
searchable steward picker that loads from the LFX Akrites committee
endpoint (GET /api/committees/:id/members).

- Add AkritesSearchStewardResult interface and AKRITES_STEWARD_COMMITTEE_UID constant
- Update AkritesAssignStewardRequest with required username/displayName fields
- Update CdpStewardSummary and AkritesSteward with username/displayName fields
- Update backend validation helper to require username and displayName
- Update akrites.service (server) mapStewards to populate username/displayName
- Add searchStewards() to frontend AkritesService with CommitteeMember mapping
- Rewrite assign-steward modal: member list with avatar initials, search filter,
  role selection, and moveToAssessing checkbox — all driven by signals
- Wire assign-steward modal into package drawer with Assign/Reassign button
  visible for unassigned/open/assessing/escalated/inactive statuses
- Wire assign-steward modal into triage tab for unassigned and inactive columns

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
@gaspergrom gaspergrom requested a review from a team as a code owner June 21, 2026 14:07
Copilot AI review requested due to automatic review settings June 21, 2026 14:07

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.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

@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: d679a7fd-5848-46c4-bc12-c393ae5d1731

📥 Commits

Reviewing files that changed from the base of the PR and between 9625824 and 37af1df.

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

Walkthrough

The PR refactors the assign-steward modal from reactive forms to Angular signals, adds a searchable steward picker that fetches live committee members, and integrates the modal into both the triage tab and package drawer with full assignment confirmation flows. Shared interfaces, server validation, and server-side mapping are extended to carry username and displayName.

Changes

Akrites Steward Assignment Flow

Layer / File(s) Summary
Shared interfaces and committee constant
packages/shared/src/interfaces/akrites.interface.ts, packages/shared/src/constants/akrites.constants.ts
Adds AkritesSearchStewardResult; extends CdpStewardSummary, AkritesAssignStewardRequest, and AkritesSteward with username/displayName; exports AKRITES_STEWARD_COMMITTEE_UID.
Frontend AkritesService.searchStewards()
apps/lfx-one/src/app/shared/services/akrites.service.ts
Adds searchStewards() that fetches committee members for the AKRITES steward committee, maps them to AkritesSearchStewardResult[], and returns an empty array on failure.
Server validation and steward mapping
apps/lfx-one/src/server/helpers/validation.helper.ts, apps/lfx-one/src/server/services/akrites.service.ts
Expands parseAssignStewardBody to require and return trimmed username and displayName; populates username and name in mapListItem/mapStewards from CDP response fields.
Assign-steward modal refactor and steward picker UI
apps/lfx-one/src/app/modules/akrites/components/akrites-assign-steward-modal/akrites-assign-steward-modal.component.ts, ...component.html
Replaces FormBuilder/reactive forms with signals and computed; adds async steward loading, search filtering, row selection, and button-based moveToAssessing toggle; updates template with searchable steward listbox and signal-driven confirm disable logic.
Triage tab wiring and assignment confirmation flow
apps/lfx-one/src/app/modules/akrites/components/akrites-triage-tab/akrites-triage-tab.component.ts, ...component.html
Registers the assign-steward modal; adds assignModalVisible, assignTargetPackage, and actionLoading signals; reworks onAction to open the modal for unassigned/inactive packages; adds onAssignStewardConfirm with openStewardship+switchMap fallback, toast feedback, and stewardshipChanged emit.
Package drawer wiring and reassign label logic
apps/lfx-one/src/app/modules/akrites/components/akrites-package-drawer/akrites-package-drawer.component.ts, ...component.html
Registers the modal; updates canAssignSteward to a status whitelist; adds assignStewardLabel computed signal returning "Reassign" for assessing/escalated/inactive; wires the button and modal into the drawer footer.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AkritesTriageTabComponent
  participant AssignStewardModal
  participant AkritesService as AkritesService (frontend)

  rect rgba(100, 149, 237, 0.5)
    note over User, AssignStewardModal: Modal open + steward search
    User->>AkritesTriageTabComponent: clicks action (unassigned/inactive pkg)
    AkritesTriageTabComponent->>AssignStewardModal: open (assignModalVisible=true)
    AssignStewardModal->>AkritesService: searchStewards()
    AkritesService-->>AssignStewardModal: AkritesSearchStewardResult[]
    User->>AssignStewardModal: types search query
    AssignStewardModal->>AssignStewardModal: filteredStewards recomputed
    User->>AssignStewardModal: selects steward row
    AssignStewardModal->>AssignStewardModal: selectedSteward signal set
  end

  rect rgba(60, 179, 113, 0.5)
    note over User, AkritesService: Assignment confirmation
    User->>AssignStewardModal: clicks Assign steward
    AssignStewardModal->>AkritesTriageTabComponent: emits AkritesAssignStewardRequest
    alt pkg already has stewardshipId
      AkritesTriageTabComponent->>AkritesService: assignSteward(stewardshipId, body)
    else no stewardshipId
      AkritesTriageTabComponent->>AkritesService: openStewardship(purl)
      AkritesService-->>AkritesTriageTabComponent: new stewardshipId
      AkritesTriageTabComponent->>AkritesService: assignSteward(stewardshipId, body)
    end
    AkritesService-->>AkritesTriageTabComponent: success
    AkritesTriageTabComponent->>AkritesTriageTabComponent: hide modal, show toast, emit stewardshipChanged
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • linuxfoundation/lfx-self-serve#990: Created the akrites-triage-tab component and its initial onAction/button handling that this PR directly extends with the assign-steward modal flow.

Suggested labels

ai-assisted, enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(akrites): implement stewardship assignment with real member picker' accurately describes the main objective of the PR—implementing stewardship assignment with a real member picker instead of a placeholder.
Description check ✅ Passed The description comprehensively covers the changeset, detailing the replacement of the placeholder input with a member picker, integration into package drawer and triage tab, and all interface/service updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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/CM-1268-akrites-stewardship-assignment

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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/lfx-one/src/app/modules/akrites/components/akrites-triage-tab/akrites-triage-tab.component.ts (1)

83-133: ⚡ Quick win

Consider refactoring to eliminate duplicated subscription logic.

The current implementation defines an assign function that subscribes internally, then duplicates the same subscribe logic in the openStewardship chain. This can be simplified by branching early in a single observable stream.

♻️ Cleaner RxJS pattern
  protected onAssignStewardConfirm(body: AkritesAssignStewardRequest): void {
    const pkg = this.assignTargetPackage();
    if (!pkg || this.actionLoading()) return;
    this.actionLoading.set(true);

-   const assign = (id: number) =>
-     this.akritesService
-       .assignSteward(id, body)
-       .pipe(take(1), takeUntilDestroyed(this.destroyRef))
-       .subscribe({
-         next: () => {
-           this.assignModalVisible.set(false);
-           this.assignTargetPackage.set(null);
-           this.actionLoading.set(false);
-           this.messageService.add({ severity: 'success', summary: 'Assigned', detail: `Steward assigned to ${pkg.name}.` });
-           this.stewardshipChanged.emit();
-         },
-         error: () => {
-           this.actionLoading.set(false);
-           this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Could not assign steward. Please try again.' });
-         },
-       });
-
-   if (pkg.stewardshipId !== null) {
-     assign(pkg.stewardshipId);
-   } else {
-     this.akritesService
-       .openStewardship(pkg.purl)
-       .pipe(
-         switchMap((res) => {
-           const id = parseInt(res.stewardship.id, 10);
-           return this.akritesService.assignSteward(id, body);
-         }),
-         take(1),
-         takeUntilDestroyed(this.destroyRef)
-       )
-       .subscribe({
-         next: () => {
-           this.assignModalVisible.set(false);
-           this.assignTargetPackage.set(null);
-           this.actionLoading.set(false);
-           this.messageService.add({ severity: 'success', summary: 'Assigned', detail: `Steward assigned to ${pkg.name}.` });
-           this.stewardshipChanged.emit();
-         },
-         error: () => {
-           this.actionLoading.set(false);
-           this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Could not assign steward. Please try again.' });
-         },
-       });
-   }
+   const stewardshipId$ = pkg.stewardshipId !== null
+     ? of(pkg.stewardshipId)
+     : this.akritesService.openStewardship(pkg.purl).pipe(
+         map((res) => parseInt(res.stewardship.id, 10))
+       );
+
+   stewardshipId$
+     .pipe(
+       switchMap((id) => this.akritesService.assignSteward(id, body)),
+       take(1),
+       takeUntilDestroyed(this.destroyRef)
+     )
+     .subscribe({
+       next: () => {
+         this.assignModalVisible.set(false);
+         this.assignTargetPackage.set(null);
+         this.actionLoading.set(false);
+         this.messageService.add({ severity: 'success', summary: 'Assigned', detail: `Steward assigned to ${pkg.name}.` });
+         this.stewardshipChanged.emit();
+       },
+       error: () => {
+         this.actionLoading.set(false);
+         this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Could not assign steward. Please try again.' });
+       },
+     });
  }
🤖 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/akrites/components/akrites-triage-tab/akrites-triage-tab.component.ts`
around lines 83 - 133, The onAssignStewardConfirm method contains duplicated
subscription logic in two places: once inside the assign function and again in
the else block for the openStewardship chain. Both have identical next and error
handlers. Refactor by creating a single observable stream that branches based on
whether pkg.stewardshipId is null using the iif operator or a conditional
switchMap. This allows you to define the assign subscription logic once and
apply it to both paths, eliminating the duplication of the next and error
handler blocks while maintaining the same functionality.
🤖 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/akrites/components/akrites-assign-steward-modal/akrites-assign-steward-modal.component.html`:
- Around line 29-35: The input field in the akrites-assign-steward-modal
template lacks an accessible name, making it inaccessible to screen readers. The
visible "Select steward" text is not associated with the input through a label
element, and placeholder text alone is insufficient for accessibility. Add an
aria-label attribute to the input element with the value "Select steward" to
provide a stable accessible name that matches the visible text in the interface,
or alternatively create a proper label element with a for attribute linked to
the input's id.
- Around line 121-125: The button element that controls the moveToAssessing
toggle is not exposing its state to assistive technology. Add an aria-pressed
attribute bound to the moveToAssessing() signal to the button element (the one
with data-testid="akrites-assign-move-assessing"), so that screen readers can
properly communicate the toggle's current state to users.

In
`@apps/lfx-one/src/app/modules/akrites/components/akrites-assign-steward-modal/akrites-assign-steward-modal.component.ts`:
- Around line 49-51: The onSearchInput method updates the searchQuery signal
when the user types a filter, but it does not clear the selectedSteward signal.
This causes the selectedSteward to remain set even after the search query hides
that steward from the visible list, allowing the confirm button to assign a
steward that is no longer visible. Modify the onSearchInput method to clear the
selectedSteward selection whenever the searchQuery is updated, ensuring that the
selection state stays synchronized with the currently filtered and visible
stewards in the picker.

---

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/akrites/components/akrites-triage-tab/akrites-triage-tab.component.ts`:
- Around line 83-133: The onAssignStewardConfirm method contains duplicated
subscription logic in two places: once inside the assign function and again in
the else block for the openStewardship chain. Both have identical next and error
handlers. Refactor by creating a single observable stream that branches based on
whether pkg.stewardshipId is null using the iif operator or a conditional
switchMap. This allows you to define the assign subscription logic once and
apply it to both paths, eliminating the duplication of the next and error
handler blocks while maintaining the same functionality.
🪄 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: 5aef58a9-708a-4457-a8a4-40465606581a

📥 Commits

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

📒 Files selected for processing (11)
  • apps/lfx-one/src/app/modules/akrites/components/akrites-assign-steward-modal/akrites-assign-steward-modal.component.html
  • apps/lfx-one/src/app/modules/akrites/components/akrites-assign-steward-modal/akrites-assign-steward-modal.component.ts
  • apps/lfx-one/src/app/modules/akrites/components/akrites-package-drawer/akrites-package-drawer.component.html
  • apps/lfx-one/src/app/modules/akrites/components/akrites-package-drawer/akrites-package-drawer.component.ts
  • apps/lfx-one/src/app/modules/akrites/components/akrites-triage-tab/akrites-triage-tab.component.html
  • apps/lfx-one/src/app/modules/akrites/components/akrites-triage-tab/akrites-triage-tab.component.ts
  • apps/lfx-one/src/app/shared/services/akrites.service.ts
  • apps/lfx-one/src/server/helpers/validation.helper.ts
  • apps/lfx-one/src/server/services/akrites.service.ts
  • packages/shared/src/constants/akrites.constants.ts
  • packages/shared/src/interfaces/akrites.interface.ts

Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
@gaspergrom gaspergrom requested a review from MRashad26 June 21, 2026 16:08
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
Copilot AI review requested due to automatic review settings June 21, 2026 16:37

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.

Copilot encountered an error: Your billing is not configured or you have Copilot licenses from multiple standalone organizations or enterprises. To use premium requests, select a billing entity via the GitHub site, under Settings > Copilot > Features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants