Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/csm-portal/microapp/src/components/support/CaseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export function CaseCard({ item }: { item: CaseSummary }) {

<Stack direction="row" alignItems="center" gap={1} flexWrap="wrap">
<StatusChip state={item.state} />
<SeverityChip severity={item.severity} />
{/* Only "case"-type items carry a severity; service requests/security reports/etc. don't. */}
{item.severity && <SeverityChip severity={item.severity} />}
</Stack>

<Stack direction="row" justifyContent="space-between" alignItems="center" mt={0.5}>
Expand Down
29 changes: 17 additions & 12 deletions apps/csm-portal/microapp/src/components/support/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,32 +56,37 @@ export const TAB_CONFIG: Record<CaseType, { title: string; subtitle: string; emp
},
};

// Labels/colors mirror the webapp's STATE_LABEL/STATE_COLOR
// (apps/csm-portal/webapp/src/features/csm-dashboard/utils/abtDashboard.ts). `reopened` has
// no webapp equivalent (its 6-state UI enum omits it) so it keeps a distinct color of its own.
export const STATE_LABELS: Record<CaseState, string> = {
open: "Open",
work_in_progress: "In Progress",
work_in_progress: "Work in progress",
waiting_on_wso2: "Waiting on WSO2",
awaiting_info: "Awaiting Info",
awaiting_info: "Awaiting info",
reopened: "Reopened",
solution_proposed: "Solution Proposed",
solution_proposed: "Solution proposed",
closed: "Closed",
};

export const STATE_CHIP_COLOR_CONFIG: Record<CaseState, NonNullable<ChipProps["color"]>> = {
open: "info",
work_in_progress: "primary",
work_in_progress: "info",
waiting_on_wso2: "warning",
awaiting_info: "warning",
awaiting_info: "default",
reopened: "error",
solution_proposed: "success",
closed: "default",
solution_proposed: "default",
closed: "success",
};

// Format/order mirrors the webapp's SeverityChip withLabel mode ("S2 (High)", code first) and
// SEVERITY_LABEL (S4 is "Low / Query", not just "Low").
export const SEVERITY_LABELS: Record<CaseSeverity, string> = {
catastrophic: "Catastrophic (S0)",
critical: "Critical (S1)",
high: "High (S2)",
medium: "Medium (S3)",
low: "Low (S4)",
catastrophic: "S0 (Catastrophic)",
critical: "S1 (Critical)",
high: "S2 (High)",
medium: "S3 (Medium)",
low: "S4 (Low / Query)",
};

export const SEVERITY_CHIP_COLOR_CONFIG: Record<CaseSeverity, NonNullable<ChipProps["color"]>> = {
Expand Down
3 changes: 2 additions & 1 deletion apps/csm-portal/microapp/src/pages/CaseDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ function CaseSummarySection({ caseDetail }: { caseDetail: CaseDetail }) {

<Stack direction="row" gap={1} flexWrap="wrap">
<StatusChip state={caseDetail.state} />
<SeverityChip severity={caseDetail.severity} />
{/* Only "case"-type items carry a severity; service requests/security reports/etc. don't. */}
{caseDetail.severity && <SeverityChip severity={caseDetail.severity} />}
</Stack>

<Typography variant="body1" color="text.primary" sx={{ whiteSpace: "pre-wrap" }}>
Expand Down
18 changes: 12 additions & 6 deletions apps/csm-portal/microapp/src/types/case.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,15 @@ export interface CaseSearchViewDto {
wso2Id: string;
subject: string;
description: string;
severity: CaseSeverity;
issueType: CaseIssueType;
state: CaseState;
// Only meaningful for the "case" type — null for service_request/security_report_analysis/etc.
// Loosely typed when present: the backend sends either the canonical word ("high") or a legacy
// ServiceNow priority code ("P2", "High (P2)") depending on data source — see
// normalizeSeverity() in case.model.ts.
severity: string | null;
issueType: CaseIssueType | null;
// Loosely typed: e.g. "Work In Progress" (labeled, space-separated) rather than the snake_case
// enum — see normalizeState() in case.model.ts.
state: string;
workState: CaseWorkState;
type: string;
createdOn?: string;
Expand Down Expand Up @@ -139,9 +145,9 @@ export interface CaseViewDto {
wso2Id: string;
subject: string;
description: string;
severity: CaseSeverity;
issueType: CaseIssueType;
state: CaseState;
severity: string | null;
issueType: CaseIssueType | null;
state: string;
workState: CaseWorkState;
type: string | null;
engagementType: string | null;
Expand Down
37 changes: 31 additions & 6 deletions apps/csm-portal/microapp/src/types/case.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,38 @@ import type {
} from "./case.dto";
import { parseBackendTimestamp, parseOptionalBackendTimestamp } from "@utils/dateTime";

// severity/issueType are only meaningful for the "case" type — the backend's create-payload docs
// say as much ("Required for case type") — so service_request/security_report_analysis/etc. cases
// come back with severity null. Beyond that, the backend's severity field is a best-effort value:
// depending on data source it can be the canonical word ("high"), a legacy ServiceNow priority
// code ("P2"), or a labeled priority ("High (P2)"). Mirrors
// apps/csm-portal/webapp/src/api/backend/mappers.ts severityFromPriority, which defaults to the
// mid severity on a garbled-but-present value rather than throwing.
function normalizeSeverity(raw: string | null | undefined): CaseSeverity | null {
if (!raw) return null;
const s = raw.toLowerCase();
if (s.includes("p0") || s === "catastrophic") return "catastrophic";
if (s.includes("p1") || s === "critical") return "critical";
if (s.includes("p2") || s === "high") return "high";
if (s.includes("p3") || s === "medium") return "medium";
if (s.includes("p4") || s === "low") return "low";
return "medium";
}

// The backend's state field is sometimes a labeled string ("Work In Progress") instead of the
// snake_case enum. Mirrors apps/csm-portal/webapp/src/api/backend/mappers.ts uiStateFromBe.
function normalizeState(raw: string): CaseState {
if (!raw) return "open";
return raw.trim().toLowerCase().replace(/\s+/g, "_") as CaseState;
}

export interface CaseSummary {
id: string;
number: string;
wso2Id: string;
subject: string;
description: string;
severity: CaseSeverity;
severity: CaseSeverity | null;
state: CaseState;
workState: CaseWorkState;
type: CaseType;
Expand All @@ -62,7 +87,7 @@ export interface CaseDetail {
wso2Id: string;
subject: string;
description: string;
severity: CaseSeverity;
severity: CaseSeverity | null;
state: CaseState;
workState: CaseWorkState;
type: CaseType | null;
Expand Down Expand Up @@ -98,8 +123,8 @@ export function toCaseSummary(dto: CaseSearchViewDto): CaseSummary {
wso2Id: dto.wso2Id,
subject: dto.subject,
description: dto.description,
severity: dto.severity,
state: dto.state,
severity: normalizeSeverity(dto.severity),
state: normalizeState(dto.state),
workState: dto.workState,
type: dto.type as CaseType,
// The search/detail views don't always populate updatedOn (e.g. a case that hasn't been
Expand All @@ -125,8 +150,8 @@ export function toCaseDetail(dto: CaseViewDto): CaseDetail {
wso2Id: dto.wso2Id,
subject: dto.subject,
description: dto.description,
severity: dto.severity,
state: dto.state,
severity: normalizeSeverity(dto.severity),
state: normalizeState(dto.state),
workState: dto.workState,
type: dto.type as CaseType | null,
engagementType: dto.engagementType,
Expand Down