Skip to content

[CSM Portal] one canonical person reference per case field, nested product reference, and ABT team-picker family filter - #1342

Merged
Rashmika998 merged 5 commits into
wso2-open-operations:mainfrom
rksk:case-detail-field-cleanup
Aug 3, 2026
Merged

Rashmika998 merged 5 commits into
wso2-open-operations:mainfrom
rksk:case-detail-field-cleanup

Conversation

@rksk

@rksk rksk commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc.

Two independent CSM portal improvements, stacked in this PR:

  1. The case detail/search API grew duplicate person and product fields as the canonical UserReference shape was added alongside older ones, and one existing field (relatedCase) was missing data a sibling field (parentCase) already carried.
  2. The ABT dashboard's team picker lists every team in the registry regardless of the dashboard's own type, so a CRE-type dashboard's picker shows every CRE team including ones that have nothing to do with ABT, alongside the real ABT teams.

No related issue for either.

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

Case response cleanup:

  • One canonical person reference per field (createdBy, assignedEngineer) instead of a field plus a *User sibling carrying the same data, across case detail, case search, comments, attachments, activities, and conversation messages.
  • The product catalogue reference now hangs off the deployed product it was deployed from (deployedProduct.product), instead of sitting beside it as a same-looking but distinct top-level field.
  • relatedCase now carries a type discriminator (case/incident/change_request/problem), matching what parentCase already provides, so a consumer can route to the correct record kind.

ABT team-picker family filter:

  • POST /teams/search accepts an optional filters.family so a caller can ask for only the teams belonging to a given family.
  • The ABT dashboard header derives the right family from the current dashboard's own type and requests only that family's teams.

Approach

Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here.

Case response cleanup, bottom-up:

  1. Go entity-service — collapsed the duplicate fields into a single UserReference {id, email, name} per site across both backing data sources; nested the product catalogue entry under the deployed product, keeping id/displayName nullable so a case that names a catalogue product with no deployed instance of it still returns the product rather than dropping it; added the missing type mapping on relatedCase. openapi.yaml updated to match, with a new test pinning the "product without a deployed product" edge case.
  2. CSM webapp — updated the backend response types and every call site (case detail, search, comments, attachments, activity feed, conversation-message mapper) to the new shape.
  3. CSM portal backend (BFF) — this API forwards the entity-service's case-detail/search responses as raw bytes, so no backend code changes; only its own openapi.yaml doc updated to match, including the conversation-messages endpoint which shares the same underlying comment shape.

This is a breaking response-shape change to an internal API with a single consumer (the CSM webapp, updated in the same PR), so it ships as a straight replacement rather than an additive migration.

ABT team-picker family filter:

  • Backend: filters.family on POST /teams/search does a case-insensitive exact match against each team's configured family, excluding teams with no family at all when a family filter is given.
  • Frontend: AbtDashboardHeader derives the family from the current dashboard's type via a new abtFamilyForDashboardType helper (cre → cre-abt, sre → sre-abt) and passes it through useTeams. The dashboard page's own separate useTeams call — used only to resolve the signed-in user's own team into a group id — is deliberately left unfiltered, since a user's real team can be outside the dashboard's own family; it no longer shares a query key with the header's now-scoped query.
  • openapi.yaml updated for the new request/response fields.

User stories

Summary of user stories addressed by this change

N/A — both are internal API/UX cleanups, not distinct user stories.

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Case responses now return one person reference per role (creator, assignee) and nest the product catalogue reference under the deployed product; related-case references now include their record type. The ABT dashboard's team picker now shows only the teams that belong to that dashboard's own family, instead of every team in the registry.

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter "N/A" plus brief explanation of why there's no doc impact

N/A — both changes are documented entirely in this PR's openapi.yaml updates; no external product docs describe either internal API/behavior.

Training

N/A — no training content covers either change.

Certification

N/A — no certification exam covers either change.

Marketing

N/A — internal API/UX cleanups, no user-facing feature to promote.

Automation tests

  • Unit tests

    New/updated Go tests for the entity-service mapping (including the nested-product edge case) and the team-family filter, plus updated webapp unit tests for the new case response shape and the ABT team picker. go test ./..., go vet ./..., gofmt -l . and npx tsc -b --noEmit all clean across entity-service and the CSM backend; npx vitest run passes (920/930 — the 10 failures are pre-existing MUI menu-rendering issues in CaseActionBar.test.tsx, confirmed unrelated to and unaffected by this diff).

  • Integration tests

    N/A — no integration test suite exists for these services; verified via unit tests and, for the case-response change, a manual read-back of the shape.

Security checks

Samples

N/A

Related PRs

None.

Migrations (if applicable)

N/A — no data migration. The case-response change is a response-shape change; the team-family filter is an additive request/response field.

Test environment

Local development environment (macOS), Go 1.26+, Node/npm per apps/csm-portal/webapp/package.json.

Learning

N/A

rksk added 3 commits August 3, 2026 22:08
Case responses carried each person twice: createdBy alongside createdByUser,
assignedEngineer alongside assignedEngineerUser, built from the same upstream
data in the same block. Collapse each pair into a single field holding the
canonical UserReference {id, email, name}, on case detail, case search, case
comments, case attachments, case activity entries and generic comments, across
both data sources. UserRef and CommentUserRef go with them; AssignedEngineerRef
stays, still used by acknowledgedBy and by the update response's assignedTo.

The product catalogue reference now hangs off the deployed product instead of
sitting beside it as a second top-level field. They are different records with
different ids, and the flat shape gave no clue which was which. A case can name
a catalogue product with no deployed instance of it, so DeployedProductRef.id
and displayName are nullable and the reference is still returned in that case,
carrying the product alone.

relatedCase silently dropped its record kind while the adjacent parentCase
mapped it. Both references can point at something other than a case, so a
consumer was left guessing on one of them.
The Go entity-service (4cf093d) collapsed createdBy/createdByUser and
assignedEngineer/assignedEngineerUser into one canonical UserReference each,
and renested the product catalogue entry under deployedProduct.product
instead of a sibling top-level product field. Update the webapp's backend
types and every call site (case detail, search, announcements, comments,
attachments, activity feed) to read the new shape, including the comment/
activity author-name fallbacks that depended on the old flat fields.
Mirrors entity-service commit 4cf093d in the BFF's own openapi.yaml. The
BFF forwards these responses as raw bytes (no Go decode of these fields
exists), so this is documentation only: createdBy/assignedEngineer become
single UserReference objects on case detail, case search, comments,
attachments, activities, and conversation messages; deployedProduct nests
the product catalogue entry and is itself nullable at the field level.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR standardizes actor data on nullable UserReference values and combines deployed-instance and catalogue-product data under DeployedProductRef. Entity-service mappings, OpenAPI schemas, CSM Portal types, mappers, and tests adopt the revised contracts.

Changes

Canonical API and domain contracts

Layer / File(s) Summary
Reference contracts and schemas
entity-service/internal/domain/entity.go, entity-service/openapi.yaml, apps/csm-portal/backend/openapi.yaml, apps/csm-portal/webapp/src/api/backend/types.ts
Legacy actor fields are replaced with canonical UserReference values. DeployedProductRef now supports nullable instance fields and an optional catalogue product.
Repository and service mapping
entity-service/internal/repository/case_repo.go, entity-service/internal/service/sn_case_service.go, entity-service/internal/service/sn_comment_service.go, entity-service/internal/service/*test.go
Repositories and services construct and expose canonical creator, assignee, actor, uploader, and product references. Tests cover related cases, product nesting, null identifiers, and serialized user references.
Portal mapper integration
apps/csm-portal/webapp/src/api/backend/mappers.ts, apps/csm-portal/webapp/src/features/csm-announcements/api/useSearchAnnouncements.ts, apps/csm-portal/webapp/src/features/csm-cases/api/*
Frontend mappings read nested createdBy references, apply name and email fallbacks, map activity actors, and consume nested catalogue-product data. Mapper tests cover null authors and chatbot detection.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: Type/Improvement

Suggested reviewers: rashmika998, dilshanfardil

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly identifies the canonical reference, nested product, and ABT team-picker changes.
Description check ✅ Passed The description follows the repository template and provides clear purpose, goals, approach, testing, security, documentation, and migration details.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

rksk added 2 commits August 3, 2026 22:51
… family

POST /teams/search accepts a case-insensitive filters.family; SearchTeams
excludes any team whose Family doesn't exactly match, including teams with
no family at all. AbtDashboardHeader now derives the family to request from
the current dashboard's type (cre -> cre-abt, sre -> sre-abt) via the new
abtFamilyForDashboardType, so a cre-type dashboard's picker offers only
cre-abt teams instead of every team in the registry (previously every
non-ABT cre team, e.g. "Americas CS Team - Integraion", showed up
alongside the real ABT teams).

CsmDashboardPage's own useTeams call for resolving the signed-in user's
own team into a groupId stays unfiltered on purpose: that user's team can
be outside the current dashboard's family (a non-ABT cre team member
viewing a cre dashboard), so it can no longer share a query key/cache
entry with the header's now family-scoped picker query.
@rksk rksk changed the title [CSM Portal] one canonical person reference per case field, nested product reference [CSM Portal] one canonical person reference per case field, nested product reference, and ABT team-picker family filter Aug 3, 2026

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
entity-service/internal/repository/case_repo.go (1)

134-134: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Populate email/name for the assignee, created comment, and search creator instead of hardcoding empty strings.

Three UserReference constructions in this file leave Email or Name as an empty string, even though UserReference is documented as "email and name are populated everywhere" (entity.go, entity-service/openapi.yaml). An empty string here is an absence sentinel, which the domain guideline explicitly disallows.

  1. GetCaseByID assignee (lines 134, 159, 184-187): ae is LEFT JOIN users ae ON ae.id = c.assigned_engineer — the same users table already joined and queried for u.email on the creator. The comment "this data source stores no email on the assignee join" does not hold: ae.email is simply not selected.
  2. CreateCaseComment (lines 207-226): the INSERT ... RETURNING clause only returns the author's id, so Email/Name are hardcoded to "".
  3. SearchCases (lines 543-548, 573-575): the query selects only u.email for the creator (no name concatenation, unlike the assignee's ae.first_name || ' ' || ae.last_name), so Name stays "".

Consumers rendering the assignee, a freshly created comment's author, or a search result's creator name will see a blank field even though the schema promises it is always populated.

🛠️ Proposed fixes
 	var (
 		aeID, aeName                         *string
+		aeEmail                              *string
 		pcID, pcNum                          *string
 		rcID, rcNum                          *string
 		        a.id, a.name, a.tier,
-		        ae.id, ae.first_name || ' ' || ae.last_name,
+		        ae.id, ae.first_name || ' ' || ae.last_name, ae.email,
 		        pc.id, pc.number,
-		&aeID, &aeName,
+		&aeID, &aeName, &aeEmail,
 		&pcID, &pcNum,
 	if aeID != nil {
-		// This data source stores no email on the assignee join, only id and name.
-		cv.AssignedEngineer = domain.NewUserReference(*aeID, "", *aeName)
+		cv.AssignedEngineer = domain.NewUserReference(*aeID, *aeEmail, *aeName)
 	}
-	const query = `
-		INSERT INTO case_comments (case_id, type, content, created_by)
-		VALUES ($1, $2::comment_type_enum, $3, $4)
-		RETURNING id, case_id, type, content, created_by, created_at`
+	const query = `
+		WITH inserted AS (
+			INSERT INTO case_comments (case_id, type, content, created_by)
+			VALUES ($1, $2::comment_type_enum, $3, $4)
+			RETURNING id, case_id, type, content, created_by, created_at
+		)
+		SELECT i.id, i.case_id, i.type, i.content, i.created_by, u.email,
+		       TRIM(u.first_name || ' ' || u.last_name), i.created_at
+		FROM inserted i JOIN users u ON u.id = i.created_by`

 	var c domain.CaseComment
-	var createdByID string
+	var createdByID, createdByEmail, createdByName string
 	err := r.db.QueryRow(ctx, query,
 		req.CaseID, string(req.Type), req.Content, req.CreatedBy,
-	).Scan(&c.ID, &c.CaseID, &c.Type, &c.Content, &createdByID, &c.CreatedOn)
+	).Scan(&c.ID, &c.CaseID, &c.Type, &c.Content, &createdByID, &createdByEmail, &createdByName, &c.CreatedOn)
-	// The insert returns only the author's id; email and display name would need
-	// a further join, so the reference carries the id alone.
-	c.CreatedBy = domain.NewUserReference(createdByID, "", "")
+	c.CreatedBy = domain.NewUserReference(createdByID, createdByEmail, createdByName)
 		        u.email,
+		        TRIM(u.first_name || ' ' || u.last_name),
 		        p.id, p.name,
-			var creatorEmail string
+			var creatorEmail, creatorName string
 			if err := rows.Scan(
 				&cv.ID, &cv.Number, &cv.InternalID,
 				&caseType, &subject, &description, &severity, &issueType, &cv.State,
 				&engagementType, &workState, &createdAt, &updatedAt,
-				&creatorEmail,
+				&creatorEmail, &creatorName,
-			cv.CreatedBy = domain.NewUserReference("", creatorEmail, "")
+			cv.CreatedBy = domain.NewUserReference("", creatorEmail, creatorName)

Also applies to: 159-159, 184-187, 207-226, 543-548, 573-575

🤖 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 `@entity-service/internal/repository/case_repo.go` at line 134, Populate
UserReference email and name in GetCaseByID, CreateCaseComment, and SearchCases
instead of using empty strings: select ae.email alongside the assignee fields,
return the created comment author’s email and name from the INSERT RETURNING
clause, and select the creator’s concatenated first_name/last_name in
SearchCases. Wire these returned values into each UserReference construction
while preserving existing creator and assignee mappings.

Source: Path instructions

🤖 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.

Outside diff comments:
In `@entity-service/internal/repository/case_repo.go`:
- Line 134: Populate UserReference email and name in GetCaseByID,
CreateCaseComment, and SearchCases instead of using empty strings: select
ae.email alongside the assignee fields, return the created comment author’s
email and name from the INSERT RETURNING clause, and select the creator’s
concatenated first_name/last_name in SearchCases. Wire these returned values
into each UserReference construction while preserving existing creator and
assignee mappings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a8da522c-f097-4ffc-875f-80cd60116bcf

📥 Commits

Reviewing files that changed from the base of the PR and between 4e00c44 and 207f3b4.

📒 Files selected for processing (15)
  • apps/csm-portal/backend/openapi.yaml
  • apps/csm-portal/webapp/src/api/backend/mappers.test.ts
  • apps/csm-portal/webapp/src/api/backend/mappers.ts
  • apps/csm-portal/webapp/src/api/backend/types.ts
  • apps/csm-portal/webapp/src/features/csm-announcements/api/useSearchAnnouncements.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/useCsmCaseActivities.test.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/useCsmCaseActivities.ts
  • apps/csm-portal/webapp/src/features/csm-cases/api/useGetCsmCaseDetail.ts
  • entity-service/internal/domain/entity.go
  • entity-service/internal/repository/case_repo.go
  • entity-service/internal/service/sn_case_service.go
  • entity-service/internal/service/sn_case_service_test.go
  • entity-service/internal/service/sn_comment_service.go
  • entity-service/internal/service/sn_user_reference_test.go
  • entity-service/openapi.yaml

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.

2 participants